• 子进程信号继承;kill+raise+alarm+pause+信号发生接收和处理+信号屏蔽


    子进程对父进程信号继承情况

    fork创建子进程,但子进程没有exec

    在fork子进程之前:

    如果父进程调用signal设置了某个信号的处理方式的话,那么fork出的子进程会继承父进程对该信号设置的处理

    强调:只有在fork之前,父进程所设置的信号处理方式,才会被子进程继承

    #include
    #include
    #include 
    #include 
    #include
    //pid_t fork(void);
    int main(void){
        pid_t ret=0;
        /*在创建子进程前,设置信号忽略*/
        signal(SIGINT,SIG_IGN);
        ret=fork();
        if(ret>0){//父进程
    
        }
        else if(ret==0){//子进程
    
        }
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    该信号被忽略后,父子进程都忽略

    #include
    #include
    #include 
    #include 
    #include
    void signal_fun(int sig){
       printf("pid=%d\t,singo=%d\n",getpid(),sig);
    }
    int main(void){
        pid_t ret=0;
        /*在创建子进程前,设置信号忽略*/
        signal(SIGINT,signal_fun);
        ret=fork();
        if(ret>0){//父进程
    
        }
        else if(ret==0){//子进程
    
        }
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    ^Cpid=5992      ,singo=2
    pid=5993        ,singo=2
    ^Cpid=5992      ,singo=2
    pid=5993        ,singo=2
     //父子进程都会设置该信号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 为什么捕获函数在子进程里面依然有效

    • 因为子进程复制了父进程的代码和数据,子进程自然也会包含信号处理函数的代码,所在子进程中依然有效。

    父子进程各自设置信号
    #include
    #include
    #include 
    #include 
    #include
    void singnal_fun1(int sig){
        printf("1---Pid=%d\n",getpid());
    }
    void singnal_fun2(int sig){
        printf("2---Pid=%d\n",getpid());
    }
    int main(void){
        pid_t ret=0;
        /*在创建子进程前,设置信号忽略*/
        signal(SIGINT,SIG_DFL);
        ret=fork();
        if(ret>0){//父进程
           signal(SIGINT,singnal_fun1);
        }
        else if(ret==0){//子进程
           signal(SIGINT,singnal_fun2);//这里的只设置子进程
        }
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork_signal.c -o exe
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./exe
    ^C1---Pid=6766
    2---Pid=6767
    
    • 1
    • 2
    • 3
    • 4
    有调用exec加载新程序
    fork之前,父进程设置的处理方式是忽略或默认

    exec加载新程序后,忽略和默认设置依然有效

    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc,char** argv,char** environ){
        pid_t ret=0;
        signal(SIGINT,SIG_IGN);
    
        ret=fork();
        if(ret>0){//父进程
          
        }
        else if(ret==0){//子进程
           execve("./child",argv,environ);
        }
    
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20


    ctrl+c对父子进程均不起作用

    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int argc,char** argv,char** environ){
        pid_t ret=0;
        signal(SIGINT,SIG_DFL);
    
        ret=fork();
        if(ret>0){//父进程
          
        }
        else if(ret==0){//子进程
           execve("./child",argv,environ);
        }
    
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20


    默认:父子进程均有效

    fork之前,父进程设置处理方式是捕获时
    • 新程序的代码会覆盖子进程中原有的父进程的代码,信号捕获函数的代码也会被覆盖

    • 既然捕获函数已经不存在了,捕获处理方式自然也就没有意义了,所以信号的处理方式会被还原为默认处理方式。

    如果子进程所继承的信号处理方式是捕获的话,exec加载新程序后,捕获处理方式会被还原为默认处理方式。

    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int sigo){
        printf("hello world!\n");
    }
    int main(int argc,char** argv,char** environ){
        pid_t ret=0;
        signal(SIGINT,signal_fun);
    
        ret=fork();
        if(ret>0){//父进程
          
        }
        else if(ret==0){//子进程
           execve("./child",argv,environ);
        }
    
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o main
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
    ^Chello world!
    
    • 1
    • 2
    • 3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K7HeHS95-1668522074854)(/home/guojiawei/.config/Typora/typora-user-images/image-20221115160041547.png)]
    父进程捕获,但是子进程被杀死了

    子程序只能在自己的程序定义捕获
    #include
    #include
    #include
    void signal_fun(int sigo){
        printf("child\n");
    }
    int main(){
        signal(SIGINT,signal_fun);
        while(1);
        return 0;
    }
    //编译成./child
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int sigo){
        printf("\nfather\n");
    }
    int main(int argc,char** argv,char** environ){
        pid_t ret=0;
        signal(SIGINT,signal_fun);
    
        ret=fork();
        if(ret>0){//父进程
          
        }
        else if(ret==0){//子进程
           execve("./child",argv,environ);
        }
    
        while(1);//防止进程太早结束
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    总结

    1.仅fork时
    			 子进程会继承父进程fork之前所设置的信号处理方式。
    2.当有exec加载新程序时
    			(1)子进程继承的处理方式是忽略或默认处理方式时,exec新程序后设置依然有	
    			(2)如果子进程继承是捕获处理方式时,exec新程序后将被还原为默认处理方式
    
    • 1
    • 2
    • 3
    • 4
    • 5

    kill(),raise()

    #include 
    #include 
    //kill:向PID所指向的进程发送指定的信号
    int kill(pid_t pid, int sig);
    //成功返回0,失败返回-1,errno被设置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #include 
    //raise:向当前进程发送指定信号
    int raise(int sig);
    //成功返回0,失败返回非0
    
    • 1
    • 2
    • 3
    • 4
    kill(getpid(),SIGUSR1);
    raise(SIGKILL);
    
    • 1
    • 2

    当多个进程协同工作时,kill函数有时还是会用到的

    比如向其它进程发送某信号,通知其某件事情发生了,其它进程收到这个信号后,就会调用信号处理函数进行相应的处理,以实现协同工作

    alarm(),pause()

    #include 
    //设置一个定时时间,当所设置的时间到后,内核会向调用alarm的进程发送SIGALRM信号
    //SIGALRM的默认处理方式是终止
    unsigned int alarm(unsigned int seconds);
    /*返回值:返回上一次调用alarm时所设置时间的剩余值*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #include 
    //调用该函数的进程会永久挂起(阻塞或者休眠),直至被信号(任意一个信号)唤醒为止
    int pause(void);
    /*返回值:
    当被信号唤醒后会返回-1,表示失败了,errno的错误号被设置EINTR(表示函数被信号中断)*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    void signal_fun(int sigo){
        printf("time up!\n");
    }
    int main(){
        signal(SIGALRM,signal_fun);
        alarm(5);//5秒
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    返回值

    int main(){
        unsigned int res=0;
        alarm(4);//5秒
        sleep(2);
        res=alarm(3);//上次剩余时间
        printf("res=%d\n",res);
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
    res=2
    闹钟
    //因为4秒睡眠2秒,还剩3秒结束
    
    • 1
    • 2
    • 3
    • 4
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int signo){}
    int main(){
        unsigned int res=0;
        signal(SIGINT,signal_fun);
        pause();
        printf("\nhello\n");
        while(1);
        return 0;
    }
    /*
    guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o main
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
    ^C
    hello
    ^\退出 (核心已转储)
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 在开发中往往会使用pause()函数来帮助调试
    • 不想继续休眠时使用信号唤醒即可
    abort()~raise(SIGABRT)
    
    • 1

    使用信号唤醒休眠函数

    调用sleep、pause等函数时,这些函数会使进程进入休眠状态

    如果不想继续休眠时,可以使用信号将其唤醒

    • 唤醒的方法-----给信号登记一个空捕获函数即可

    唤醒的过程
    当信号发送给进程后,会中断当前休眠的函数,然后去执行捕获函数,捕获函数执行完毕返回后,不再调用休眠函数,而是执行休眠函数之后的代码,这样函数就被唤醒了

    #include 
    void signal_fun(int signo){
        printf("execute signal_fun\n");
    }
    int main(){
        signal(SIGINT,signal_fun);
        pause();//sleep(10);
        printf("pause code!\n");
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    既要捕获信号,又想继续执行pause或sleep

    手动重启

    pause()返回值

    ​ pause() returns only when a signal was caught and the
    ​ signal-catching function returned.

    In this case, pause()returns -1, and errno is set to EINTR.

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int signo){
        printf("execute signal_fun\n");
    }
    int main(){
        int res=0;
        signal(SIGINT,signal_fun);
        lable: res=pause();
        /*手动重启*/
        if(res==-1&&errno==EINTR){
            goto lable;
        }
        printf("pause code!\n");//这句话总是执行不出
        while(1);
        return 0;
    }
    /*
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./main
    ^Cexecute signal_fun
    ^Cexecute signal_fun
    ^Cexecute signal_fun
    ^Cexecute signal_fun
    ^\退出 (核心已转储)
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    sleep()返回值
    1. Zero if the requested time has elapsed

    1. the number of seconds left to sleep, if the call was interrupted by a signal handler.
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int signo){
        printf("execute signal_fun\n");
    }
    int main(){
        int res=0;
        signal(SIGINT,signal_fun);
          /*手动重启*/
        res=10;
        lable_sleep: res=sleep(res);
        if(res!=0){
            printf("res=%d\n",res);
            goto lable_sleep;
        }
        printf("pause code!\n");//这句话总是执行不出
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    guojiawei@ubantu-gjw:~/Desktop/signal$ gcc fork.c -o mmm
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm
    ^Cexecute signal_fun
    res=8
    ^Cexecute signal_fun
    res=6
    ^Cexecute signal_fun
    res=5
    ^Cexecute signal_fun
    res=4
    ^Cexecute signal_fun
    res=3
    ^Cexecute signal_fun
    res=1
    pause code!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    休眠函数自启动

    使用read从键盘读取数据,当键盘没有输入任何数据时,read会休眠,不过函数被信号唤醒后,会自动重启read的调用

    read函数读数据时,并不一定会休眠

    • 读硬盘上的普通文件时,不管文件有没有数据,read都不会休眠,而是会返回继续向下运行
    • 如果read读的是键盘的话,如果键盘没有数据时read就会休眠。
    void signal_fun(int signo){
        printf("execute signal\n");
    }
    int main(){
        int res=0;
        signal(SIGINT,signal_fun);
        char buf[100]={0};
        read(0,buf,sizeof(buf));
        printf("have read!\n");
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm
    ^Cexecute signal
    ^Cexecute signal
    mmmm
    have read!
    ^\退出 (核心已转储)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也就是有些休眠函数自动启动,有些休眠函数手动启动

    信号发生,接收和处理过程

    信号屏蔽字

    作用

    用来屏蔽信号的

    每个进程能够接收的信号有62种,信号屏蔽字的每一位记录了每个信号是被屏蔽的还是被打开的

    • 如果是打开的就立即处理
    • 如果是屏蔽的就暂不处理
    屏蔽字放在了哪里

    每一个进程都有一个信号屏蔽字,它被放在了进程表(task_struct结构体变量)中

    什么是屏蔽字
    • 简单地认为屏蔽字就是一个64位的unsigned int数,每一位对应着一个信号
      • 如果这一位为0,表示信号可以被立即处理
      • 如果为1表示该信号被屏蔽了,暂不处理
    	1   2   3   4   5          61  62  63  64             
        *   *   *   *   *  ......   *   *   *   * 
        ------------------------------------------
    第1位:对应编号为1(SIGHUP)的信号,该位为
    		1)0:表示1(SIGHUP)这个信号是打开的,可以被立即处理
    		2)1:表示信号被屏蔽了,暂时不能处理 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    有对应的API用于修改信号屏蔽字的-----实现某个信号的打开和屏蔽

    在默认情况下,信号屏蔽字中所有的位都为0

    未处理信号集

    作用:

    跟屏蔽字一样,也一个64位的无符号整形数,专门用于记录未处理的信号

    “未处理信号集”同样也是被放在了进程的进程表中(task_struct)

    什么时候会记录

    ​ 信号来了,当进程的信号处理机制,检查该信号在屏蔽字中的对应位时发现是1,表示该信号被屏蔽了,暂时不能被处理,此时就会将“未处理信号集”中该信号编号所对应的位设置为1,这个记录就表示,有一个信号未被处理。

    ​ 如果该信号发送了多次,但是每一次都因为被屏蔽了而无法处理的话,在“未处理信号集”中只记录一次

    什么时候处理未处理信号

    当屏蔽字中该信号的位变成0时(被打开了),此时就回去检查“未处理信号”,处理表中的信号

    信号处理的完整过程

    屏蔽字+未处理信号集

    • 内核接收信号,首先去查看信号处理方式这张表,看是捕获默认还是终止
      • 如果是忽视,那么直接丢弃信号
      • 如果是捕获或默认或终止,会查看信号屏蔽字这张表
    • 检查相应的信号屏蔽字是0还是1
      • 如果是0,然后把0设置成1,就执行相应的捕获默认终止的操作
      • 如果是1,表名正在执行信号,就记录到未处理信号集
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int signo){
        printf("hello\n");
        sleep(3);
        printf("world\n");
    }
    int main(){
        pid_t ret=0;
        signal(SIGINT,signal_fun);
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./mmm
    ^Chello
    ^C^C^C^C^Cworld
    hello
    world
    ^Chello
    ^C^C^C^C^C^C^C^C^C^C^Cworld
    hello
    world
    //按下很多次,但只记录了一次
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    修改信号屏蔽字

    修改的原理

    1. 定义一个64位的与屏蔽字类似的变量

    2. 将该变量设置为想要的值

      将某信号对应的位设置为0或者为1

    3. 使用这个变量中的值来修改屏蔽字

      1. 完全的替换
        使用变量的值去完全替换掉屏蔽字

        ​ 比如:屏蔽字 = 变量(1111111…11111)

      2. 使用|操作,将对应的位设置为1,只屏蔽某个或者某两个信号

        ​ 比如:屏蔽字 = 屏蔽字 | 0000…10

      3. 第三种:使用位&操作,将对应的位清0,打开信号

        ​ 比如:屏蔽字 = 屏蔽字 & (~0000…10)
        ​ 屏蔽字 = 屏蔽字 & 1111…01

    SIGKILL和SIGSTOP信号是不能被屏蔽,就算在屏蔽字中它们对应的位设置为了1,也不会起到屏蔽的作用

    设置变量API
           #include 
           //将变量set的64位全部设置为0
           int sigemptyset(sigset_t *set);
           //将变量set的64位全部设置为1
           int sigfillset(sigset_t *set);
           //将变量set中,signum(信号编号)对应的那一位设置为1,其它为不变
           int sigaddset(sigset_t *set, int signum);
           //将变量set的signum(信号编号)对应的那一位设置为0,其它位不变
           int sigdelset(sigset_t *set, int signum);
    /*	调用成功返回0,失败返回-1,并且errno被设置*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    设置变量修改屏蔽字API
           #include 
           /* Prototype for the glibc wrapper function */
    //使用设置好的变量set去修改信号屏蔽字
           int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    
    • 1
    • 2
    • 3
    • 4
    1)how:修改方式,前面说过有三种修改方式
    		(a)SIG_BLOCK:屏蔽某个信号----屏蔽字=屏蔽字 | set
    		(b)SIG_UNBLOCK:对屏蔽字的某位进行清0操作-----屏蔽字=屏蔽字&(~set)
    		(c)SIG_SETMASK:直接使用set的值替换掉屏蔽字
    2)set:set的地址
    3)oldset:保存修改之前屏蔽字的值,如果写为NULL的话,就表示不保存
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    代码:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void signal_fun(int signo){
        sigset_t set;
        printf("hello\n");
        /*得到:010000*/
        sigemptyset(&set);//取地址
        sigaddset(&set,SIGINT);
        /*修改屏蔽字*/
        //SIG_UNBLOCK:取反相与
        sigprocmask(SIG_UNBLOCK,&set,NULL);//不保存旧值
        sleep(3);
        printf("world\n");
    }
    int main(){
        pid_t ret=0;
        signal(SIGINT,signal_fun);
        while(1);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    guojiawei@ubantu-gjw:~/Desktop/signal$ ./mm
    ^Chello
    ^Chello
    ^Chello
    ^Chello
    ^Chello
    ^Chello
    world
    world
    world
    world
    world
    world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    算法竞赛入门【码蹄集新手村600题】(MT1551-1600)
    算法通过村第十六关-滑动窗口|白银笔记|经典题目讲解
    怎样加密才能保证通信安全?
    国产分布式数据库在证券行业的应用及实践
    海思平台水印功能实现之二定时器Timer
    python计算点到面的距离
    计算机毕业设计Java蛋糕网店(源码+系统+mysql数据库+lw文档)
    【NLP】基于CBOW实现Word2Vec
    在组件的描述文档中没有找见属性能修改样式的时候如何修改组件样式——样式穿透
    初步了解认识正则表达式(Regex)
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/127875396