【Linux】-- 进程终止进程等待

news2024/11/29 5:37:06

目录

深入理解fork

进程终止

进程常见退出场景

退出码

总结

进程等待

进程等待必要性

wait与waitpid

阻塞等待

非阻塞等待

总结


深入理解fork

        在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#问fork创建子进程的时候操作系统做了什么?

        fork创建子进程,等于系统中多了一个子进程。而进程 = 内核数据结构 + 进程代码和数据,内核数据结构来源与操作系统,进程代码和数据一般来源于磁盘(C/C++程序加载后的结果)。

        由于进程具有独立性,所以创建子进程需给子进程分配对应的内核结构。而对于一个进程来说,子进程应该也要有自己的代码和数据,但是对于fork之后创建的子进程并没有加载的过程,而是创建就立马运行。也就是说:子进程没有自己的代码和数据,子进程只能 “使用” 父进程的代码和数据。

融汇贯通的理解:

        fork创建子进程的特性是父进程的副本,父子进程代码共享。这与我们C语言中所学的常量字符串是类似的:
 

const char* a = "12345";
const char* b = "12345";

        对于相同的字符串常量,由于只能读不能写,在并不可能改变的情况下,采取两个空间存储一样的数据,无疑是对于空间的浪费。

        同样的道理,fork创建子进程的时候就直接进行代码和数据的拷贝分离,并不能保证子进程会的使用这些代码和数据,更或者用的到,也有可能只是读取。

新的问题在于是会有需要使用更改的地方:

  • 代码:都是运行即不可被写的,只能读取,所以父子共享没有问题。
  • 数据:可能被修改,所以必须分离

        由于操作系统无法知道:什么数据必须拷贝、什么数据值得拷贝、什么数据会被子或父进行写入。而且就算拷贝了,也不能保证数据会被立马使用。所以操作系统采用写时拷贝技术,进行对父子进程数据的分离

写时拷贝技术的意义:

  • 用的时候,再分配,高效使用内存。
  • 操作系统无法提前预知空间访问,采用立马用立马拷贝进行访问。

(写时拷贝:是一种延时申请技术,可以提高整机内存的使用率) 


#问:fork之后,父子进程代码共享是所有,还是fork之后? 

         由于,我们的代码由编译器汇编之后,会有很多行代码。其会有自己的虚拟地址,也会有加载到内存中的物理地址。物理地址与虚拟地址会根据映射关系放在页表当中,虚拟地址会放在程序地址空间中,给CPU进行使用,CPU所能看见的仅仅是地址空间。也就是说CPU只知道虚拟地址,并不知道物理地址

        因为,进程可能随时在并未执行完的时候被中断。而下次回来执行,还必须从之前中断的位置继续执行。所以执行的位置需要CPU随时记录,所以CPU中会有对应的寄存器数据(EIP)来记录当前进程需执行的位置。

融汇贯通的理解:

        其实CPU也不是很聪明,甚至很笨。它只会执行:取指令、分析指令、执行指令(分析指令需要认识大量的指令。所以CUP很笨,但是不得不说它很强)

        而取指令就是CPU的等待任务安排,由寄存器中的上下文数据提供。

        CPU执行的内容靠EIP提供地址找到地址空间,再以虚拟地址通过页表映射找到物理内存中的数据,随后将数据给与CPU进行分析命令、执行命令。而EIP通过加减此次使用数据的大小到达下一个数据的位置。

        虽然父子进程各自调度,各自修改EIP,但是不重要,因为子进程认为EIP起始值就是需要执行的起始点(fork之后的代码)。所以,是共享所有

进程终止

#问:进程终止时,操作系统做了什么?
        进程终止时,要释放进程申请的相关内核数据结构和数据的代码。本质就是释放系统资源。

进程常见退出场景

#问:进程终止的常见方式?

  • 运行成功
    • 代码跑完,结果正确
    • 代码跑完,结果不正确
  • 运行失败
    • 代码没有跑完,程序崩溃了

退出码

#问:用代码,如何终结一个进程?什么是一个正确的终结?

  • 0:成功,正确。
  • 非0:标识的是运行的结果不正确。

融汇贯通的理解:

        在C/C++语言的书写上,对于main函数的return 0; 学语法的时候是说由于是int main(),返回值是int类型,所以需要返回一个整数(返回0就行了)。但是在学操作系统时,返回的是什么值就尤为重要了

        main函数内:return语句,就是终止进程的!(return 退出码)

写一个main函数return 0,并运行,可以发现:

命令:echo $?


最近(上一次)进程的退出码

        因为对于运行结果我们关心的永远是:它错了究竟错在哪里、而不是它对了究竟对在哪里。所以用无数的非0值标识不同错误的原因。给我们的程序在运行结束之后,对于结果不正确时,方便定位错误的原因细节。 

main函数返回

总体来说,mian函数返回值的意义是:返回给上一级进程,用来评判该进程执行的结果。

        我们可以利用一下代码将该进程的退出码打印出来。(每一个进程的错误码是不一定相同的,我们可以使用这些退出码和含义。但是。如果自己想定义,也可以自己设计一套退出方案。)

#include<stdio.h>
#include<string.h>
int main()
{
    for(int i = 0; i <= 134; ++i)
    {
        printf("%d: %s\n", i, strerror(i));
    }
    return 0;
}

        以上,就是main函数内的return语句,用于终止进程。并且return只有对于main函数来说是返回退出码。对于main内的函数,return语句用于跳出该函数,在需要时,也将函数返回值到调用表达式中。

exit函数与_exit函数

        exit在代码的任何地方都可以调用,都表示直接终止进程。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("你可以看见我吗?"); //注意此处没有写"\n"
    sleep(1);
    exit(100);
    return 0;
}

        _exit在代码的任何地方都可以调用,都表示直接终止进程。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("你可以看见我吗?");
    sleep(1);
    _exit(100);
    return 0;
}

        我们们可以发现一样的逻辑代码,只是使用了不同的exit函数与_exit函数,main函数确实在中途终止,并返回了我们随意写的退出码100。但是一个打印了语句,一个没有打印语句。这就是二者的区别。

知识回顾:

        在C语言中printf函数有一个特点,其需打印的数据是先放在缓冲区的,通过缓冲才打印出,日常我们所写的进程,是会结束时自动冲刷缓冲区的。而"\n"的是将存储在缓冲区的数据冲刷出,同时也打印后换行。

exit与_exit的区别

  • exit()是库函数(语言,应用)
  • _exit()是系统接口(操作系统)

        通过此我们也可以更近一步的知道。所谓的缓冲区一定不是由操作系统维护的,而是由C标准库维护的。因为如果是操作系统维护的,那么缓冲区_exit()也能进行刷新。

总结

        return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
        退出码是出现于代码跑完,运行成功的时候。而退出码是程序员对于自身代码逻辑的判断。所谓好的程序员是代码高内聚,低耦合、代码框架、对象描述是极简并清晰的。但是资深程序员,具备以上的同时,应该对于自身所写的代码逻辑是无比清晰的,可以预判到自己所写的代码可能会出现的错误,并用退出码标识出来。这样可以使得,其对于bug代码的处理无需寻找问题再处理,而是直接处理问题。这就是高效的编写代码。
        
        所以退出码是程序员对自身代码逻辑的一种错误预判标识。

进程等待

        利用wait与waitpid操作系统接口进行进程等待。

进程等待必要性

  1. 子进程退出,父进程不管子进程,子进程就要处于僵尸状态 -- 导致内存泄漏
  2. 父进程创建子进程,要是让子进程办事的,所以子进程任务完成的结果是重要的,父进程关心的。(需要结果,如何得知?不需要结果,如何处理?)
  3. 子进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

总体来说:为什么要进行进程等待?
        父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。父进程也可以不等待子进程,但是需要学习信号完才行。

融汇贯通的理解:
#问:什么是进程等待?

        答:通过系统接口,让用户等待子进程的一种方案(wait/waitpid)

#问:为什么进程等待?

        答:回收进程,父进程或者系统获取子进程的退出结果。


        父进程或者系统需要获取子进程的退出结果,进而就需要子进程维持住僵尸状态,读取结果。由于子进程结束成为并维持住了僵尸状态,就会出现僵尸进程的问题,所以需要通过进程等待的方式回收,否者就会导致内存泄漏。

        所以说,这是相互关联相互联系的,这也就是进程等待的作用。

        父进程需要获取子进程退出信息,而子进程任务完成的结果进程常见退出场景所说,分为:

  • 运行成功
    • 代码跑完,结果正确
    • 代码跑完,结果不正确
  • 运行失败
    • 代码没有跑完,程序崩溃了

wait与waitpid

命令:man 2 wait

命令:man 2 waitpid


查看wait与waitpid的英文文档

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

阻塞等待

wait方法

  • 返回值:
    • 成功返回被等待进程pid,失败返回-1
  • 参数:
    • status:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL(不在wait讲解,在后面的waitpid讲解)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 2;
        while(cnt--)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
        }
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        pid_t ret = wait(NULL); //阻塞式的等待!
    }
    return 0;
}

命令:while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "----------------------------"; done 


每个一秒循环打印一个表格的头标(表格中数据的名称),并查找里面的myproc并去除grep后的进程最后打印"----------------------------"(用于显示的分割)。

        父进程会一直处于阻塞等待,直到等待到子进程结束,父进程才执行后续任务。

融汇贯通的理解:

        阻塞的本质就是,当前进程调用某些接口,让自身处于某种等待资源条件的状态,当底层的条件没有就绪的时候,就要将自己处于某种等待队列当中,其中将自身的内核控制块当中的状态从R设置为S或者是D状态,处于等待某种资源的状态,当特定资源就绪时,就会立马从等待队列当中唤醒重新调用。

        简单来说就是:进程阻塞就是在系统函数内部,将进程放入阻塞队列当中。

        此处由于wait进入而阻塞等待,即内核数据结构从运行队列换出到某个阻塞队列中,等特定资源就绪就会换入到运行队列进行执行。

waitpid方法

  • 返回值:
    • 当正常返回的时候waitpid返回收集到的子进程的进程ID
    • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
    • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  • 参数:
    • pid:pid=-1等待任一个子进程,与wait等效;pid>0等待其进程IDpid相等的子进程。
    • status:输出型参数,是一个32bit划分的参数(下面讲解)
    • options:默认为0:表示父进程阻塞等待;WNOHANG:表示父进程非阻塞等待。

        waitpid(pid, NULL, 0) == wait(NULL)

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int code = 0;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt--)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
        }
    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        int status = 0; 

        // 只有子进程退出的时候,父进程才会waitpid函数,进行返回!![父进程依旧还活着呢!!]
        // waitpid/wait 可以在目前的情况下,让进程退出具有一定的顺序性!
        // 将来可以让父进程进行更多的收尾工作。
        pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!
        if(ret > 0)
        {
            printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\
                    ret, status & 0x7F ,(status >> 8)&0xFF); 
        }
    }
}

        输出型参数status并不是按照整数来整体使用的。而是按照比特位的方式,将32比特位进行划分,对于应用只需要学习低16位。

        次8位是进程的退出状态,低8位的第1位是core dump标志(gdb调试崩溃程序信号,此文用不到,先不讲解)。剩下的7位是终止信号。

其实waitpid的输出型参数status,对于退出状态、终止信号为了方便提取,都有其对应的提取方式:

  • WIFEXITED(status):提取退出状态。
  • WEXITSTATUS(status):提取终止信号。
if(WIFEXITED(status))
{
    //子进程是正常退出的
    printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status));
}
else
{
    printf("子进程异常退出: %d\n", WIFEXITED(status));
}

对于进程结果的判断需要有先后:

  • 终止信号为0(运行成功),退出状态有效,可以看(结果是否正确)。
  • 终止信号为非0(运行失败),退出状态没有意义,不用看。

        进程信号:是进程是否成功运行完的关键。进程异常退出,或者崩溃,本质就是操作系统通过发送信号的方式杀掉了进程。

命令:kill -l


可以查看操作系统用于发送以杀死进程的终止信号(对于应用层,了解前31个即可)

向子进程内加入一个野指针:

int* i = NULL;
*i = 100;

        此时终止为非0,那么进程是被操作系统在中途杀死的,所以退出码没有意义。

外部杀死进程:

命令:kill -9 5748(子进程的pid)


将正在运行的子进程,通过命令行传入终止信号9杀死。

        程序异常,不光光是内部代码有问题,也有可能是外力直接杀死。

非阻塞等待

waitpid方法

        这就要使用waitpid中的参数options:WNOHANG选项,代表父进程非阻塞等待。

#问:为什么是WNOHANG而不是数字?

        在代码中没有具体含义的数字。叫做魔鬼数字/魔术数字,因为就一个数字放在那里,并无法直观的知道让他含义。所以采用WNOHANG的方式表明。

Linux是C语言写的 -> 系统调用接口 -> 操作系统提供的接口 -> 就是C语言写的 -> 系统一般提供的大写标记位,如:WNOHANG就是宏定义的:#define WNOHANG 1

便于的理解:

        WNOHANG可以理解为:Wait No HANG(夯),在编程上有一个口头说法:夯住了。也就是打开一个软件或者是APP系统没有任何的反应,也就是系统并未对该进程进行CPU调度。要么是在阻塞队列中,要么是等待被调度。

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include<vector>
#include<iostream>
#include <sys/types.h>
#include <sys/wait.h>

typedef void (*headler_t)();

std::vector<headler_t> handlers;

void fun_one()
{
    printf("这是一个临时任务1\n");
}

void fun_two()
{
    printf("这是一个临时任务2\n");
}

// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法!
void Load()
{
    handlers.push_back(fun_one);
    handlers.push_back(fun_two);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt)
        {
            printf("我是子进程: %d\n",cnt--);
            sleep(1);
        }
    }
    else
    {
        int quit = 0;
        while(!quit)
        {
            int status = 0;
            pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
            if(res > 0)
            {
                //等待操作成功 && 等待到子进程结束
                printf("等待等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
                quit = 1;
            }
            else if(res == 0)
            {
                //等待操作成功 && 等待到子进程结束
                printf("进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情\n");
                if(handlers.empty()) Load();
                for(auto iter : handlers)
                {
                    //执行处理其他任务
                    iter();
                }
            }
            else
            {
                //等待操作失败
                printf("wait失败!\n");
                quit = 1;
            }
            sleep(1); //每隔1s询问一次子进程是否结束
        }
    }
    return 0;
}

融汇贯通的理解:
#问:
为什么要用到wait/waitpid?

        答:进程具有独立性,数据就会发生写实拷贝,父进程无法拿到数据。需要wait/waitpid进行获取。

#问:wait/waitpid为什么能拿到子进程数据?

        答:子进程为提供给父进程提取数据,会维持僵尸进程。而僵尸进程保留了该进程的PCB信息,其中的task_struct里面保留了任何进程退出时的退出结果信息,wait/waitpid的本质就是读取进程task_struct结构。 

融汇贯通的理解:

  • 孤儿进程:

(子进程死亡晚于父进程)父进程结束了、死亡了。而子进程还在运行,此时子进程就会被1号init进程领养

  • 僵尸进程:

(子进程死亡早于父进程)父进程还在运行。而子进程结束了、死亡了,而子进程为了向父进程提供其需获取的数据,从而进入持续僵尸进程。但是父进程并未管他,进行回收,那么就会出现内核数据结构未释放,因为不会像new申请堆一样结束自动释放空间,于是导致内存泄漏。

  • 孤儿进程与僵尸进程的区别:

子进程死亡与父进程死亡的相对性早晚。

总结

等待主要就是阻塞等待与非阻塞等待。

  • 阻塞等待:一般都是在内核中阻塞,伴随着切换,等待被唤醒。

  • 非阻塞等待:父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid的这个系统调用会立马返回。

补充:

        我们需要明白,回收资源,获取子进程退出结果,不是我们做的,而是我们使用接口完成这个操作的。而接口是由操作系统做的,我们通过接口让操作系统去做,而且这个操作只能操作系统去做,因为操作系统是管理者。资源管理,资源调度……只有管理者才能调用。我们是处于最顶层

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/152838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rhce第三次作业

1. 基于同一IP的不同端口访问不同的网站(可以通过域名去访问)ipport1 -> 对应一个域名ipport2 -> 对应一个域名使用域名1我应该访问到 ipport1对应的内容使用域名2我应该访问到 ipport2对应的内容/etc/hosts文件下写入ip和域名&#xff0c;进行域名解析写入192.168.137.1…

vue3学习笔记之TransitionTransitionGroup

文章目录Transition&TransitionGroup动画组件1. Transition1.1 Transition API1.2 基于CSS的过渡效果自定义过渡class Animate动画库同时使用 transition 和 animation深层级过渡与显式过渡时长duration&#xff08;总持续时间&#xff09;1.3 JavaScript 钩子 GreenSock…

FPGA知识汇集-GPGPU与FPGA

虽然FPGA的功能很强大&#xff0c;但是并非所有的设计都会使用到这样的芯片。很多系统架构师在开始的时候会斟酌到底是使用FPGA还是其他的代替方案&#xff0c;例如GPGPU&#xff08;通用的图形处理单元&#xff09;。 由于GPGPU特有的架构和处理能力&#xff0c;在很多现代的设…

SpringBoot+VUE前后端分离项目学习笔记 - 【20 权限菜单 上】

数据库 新建sys_role角色表 与sys_menu菜单表 CREATE TABLE sys_role (id int NOT NULL AUTO_INCREMENT COMMENT id,name varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 名称,description varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 描述…

【C++】list模拟实现

看源码观察结构 由源码可以得知,list的底层是带头双向循环链表 — 结点类模拟实现 list实际上是一个带头双向循环链表,要实现list,则首先需要实现一个结点类,而一个结点需要存储的信息为:数据、前驱指针、后继指针 而对于该结点类的成员函数来说,我们只需实现一个构造函数即…

Linux磁盘根目录扩容

Linux磁盘根目录扩容 1.输入命令:df -hl 红色框标记的呢就是服务器的主目录&#xff0c;我们能看到总容量17G &#xff0c;已使用2.1G 可用15G 我们要扩张磁盘空间的就是挂载点为:/ (的这个) 2.查询磁盘分区 命令&#xff1a;fdisk -l 我们找到/dev/sdb 这个磁盘名称就是…

Linux常用命令——nethogs命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) nethogs 终端下的网络流量监控工具 补充说明 有很多适用于Linux系统的开源网络监视工具。比如说&#xff0c;你可以用命令iftop来检查带宽使用情况。netstat用来查看接口统计报告&#xff0c;还有top监控系统当…

CMake快速入门

介绍 之前讲的Makefile的配置跟你当前的系统非常强的相关&#xff0c;例如在Linux或苹果下配置完Makefile&#xff0c;放到Windows下就会有问题&#xff0c;因为编译器会不同、路径会不同等。 如果要做跨平台的软件&#xff0c;要给不同的操作系统&#xff0c;不同的编译环境…

靶机测试Connect-the-dots笔记

靶机描述DescriptionBack to the TopLevel: Beginner-IntermediateUser flag: user.txtRoot flag: root.txtDescription: The machine is VirtualBox compatible but can be used in VMWare as well (not tested but it should work). The DHCP will assign an IP automaticall…

一款兼容双系统、为代码而生的机械键盘--Keychron K3

&#x1f525;前言 从去年的9月份记得就有小伙伴发私信问我有没有值得推荐的键盘&#xff0c;前段时间又有几个小伙伴在发私信询问。于是我写下这篇文章去给大家推荐一款十分好用的矮轴机械键盘 > keychron K3蓝牙无线矮轴超薄机械键盘,从而让大家更好的去敲代码&#xff0c…

【PWA学习】4. 使用 Push API 实现消息推送

引言 在接下来的内容里&#xff0c;我们会探究 PWA 中的另一个重要功能——消息推送与提醒(Push & Notification)。这个能力让我们可以从服务端向用户推送各类消息并引导用户触发相应交互 Web Push 效果Push API 和 Notification API 其实是两个独立的技术&#xff0c;完全…

很好用的URL工具类

&#x1f4e2; &#x1f4e2; &#x1f4e2; &#x1f4e3; &#x1f4e3; &#x1f4e3;哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步 &#x1f91d; &#x1f91d;一位上进心十足的【Java ToB端…

【C++】deque双端队列

目录deque的原理介绍deque的优点和缺陷deque的原理介绍 1.deque(双端队列)&#xff1a;是一种双开口的“连续”空间的数据结构&#xff0c;双开口的含义是&#xff1a;在头尾两端都可以进行插入和删除操作&#xff0c;且时间复杂度为O(1)。 需要注意的是deque并不是真正连续的…

制作一个微信小程序步骤_分享个人微信小程序开发步骤

微信小程序的功能不断提高&#xff0c;以及用户对小程序的使用的增加&#xff0c;使得新一批的流量融入小程序中&#xff0c;越开越多的企业开始开发小程序&#xff0c;想要从中分一碗羹&#xff0c;今天内容就从如何制作一个微信小程序说起&#xff0c;希望对你有帮助。微信小…

Vue鼠标移入移出事件(冒泡问题)

一、定义1、mouseenter&#xff1a;在鼠标光标从元素外部首次移动到元素范围之内时触发&#xff0c;这个事件不冒泡。2、mouseleave&#xff1a;在位于元素上方的鼠标光标移动到元素范围之外时触发&#xff0c;这个事件不冒泡。3、mouseover&#xff1a;在鼠标指针位于一个元素…

Flink学习31:Table API SQL之注册表

table API tableAPI & flink SQL flink SQL是最上层的封装&#xff0c;封装了流处理的DataStream API 和 批处理的DataSet API。 但是DataStream API 、DataSet API、table API、flink SQL 这几层并没有分到不同层中&#xff0c;所以应用程序可以同时使用这几层。 Table A…

合合信息——用智能文字识别技术赋能古彝文原籍数字化

文章目录1. 背景介绍&#xff1a;古彝文是什么&#xff1f;为什么要保护它&#xff1f;如何保护它&#xff1f;2. 传统方法保护古彝文&#xff1a;原籍难获、翻译困难2.1. 古彝文原籍的破损与古法保存2.2 古彝文原籍的保护与翻译2.2.1 获取古彝文原籍2.2.2 修复古彝文原籍2.2.3…

ansible作业一

配置ansible学习环境实现以下要求 1.控制主机和受控主机通过root用户通过免密验证方式远程控住受控主机实施对应&#xff08;普通命令&#xff0c;特权命令&#xff09;任务 2.控制主机连接受控主机通过普通用户以免密验证远程控住受控主机实施指定&#xff08;普通命令&#x…

Makefile快速入门

介绍 这里以一个例子来演示利用Makfile进行多文件编译 一共有四个源程序&#xff1a;main.cpp&#xff0c;printhello.cpp&#xff0c;factorial.cpp&#xff0c;functions.h 首先是main.cpp内容 #include<iostream> #include"functionals.h"using namespa…

Apache与Nginx虚拟机的三种访问+非简单请求+跨域知识点整理

Apache 在D:\project\web\index.html中写入 <h1>welcome useing apache!</h1>基于ip访问 打开phpstudy_pro\Extensions\Apache2.4.39\conf\extra\httpd-vhosts.conf写入 <VirtualHost 192.168.1.4:80>ServerAdmin 88888888163.com #管理员邮箱DocumentRoo…