Linux系统编程(四)——signal信号处理

news2025/7/10 6:18:10

目录

0x01 信号

0x02 信号相关的函数 

 一、kill函数 

二、alarm()函数

三、setitimer()

四、signal()

0x03 信号集

一、信号集的处理过程  ​编辑

二、关于信号集处理的函数 

0x04 内核实现信号捕捉的过程

0x05 SIGCHLD信号


0x01 信号

  • 信号是Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时候也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件,处理完后就继续处理前面的事情。

  • 发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:

    • 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号,比如输入Ctrl+C通常会给进程发送一个中断信号。

    • 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被0除,或者引用了无法访问的内存区域。

    • 系统状态变化,比如alarm定时器到期将引出SIGALRM信号,进程执行的CPU时间超限,或者该进程的某个子进程退出。

    • 运行kill命令或调用kill函数

  • 使用信号的两个主要目的:

    • 让进程知道已经发生了一个特定的事情。

    • 强迫进程执行它自己代码中的信号处理程序。

  • 信号的特点:

    • 简单

    • 不能携带大量信息

    • 满足某个特定条件才发送

    • 优先级比较高

  • 查看系统定义的信号列表:kill -l

  • 前31个信号为常规信号,其余为实时信号:

 那么对于上述的信号描述可以看到如下:

SIGHUP       1          /* Hangup (POSIX).  */                          终止进程     终端线路挂断
SIGINT       2          /* Interrupt (ANSI).  */                        终止进程     中断进程 Ctrl+C
SIGQUIT      3          /* Quit (POSIX).  */                            建立CORE文件终止进程,并且生成core文件 Ctrl+\
SIGILL       4          /* Illegal instruction (ANSI).  */              建立CORE文件,非法指令
SIGTRAP      5          /* Trace trap (POSIX).  */                      建立CORE文件,跟踪自陷
SIGABRT      6          /* Abort (ANSI).  */
SIGIOT       6          /* IOT trap (4.2 BSD).  */                      建立CORE文件,执行I/O自陷
SIGBUS       7          /* BUS error (4.2 BSD).  */                     建立CORE文件,总线错误
SIGFPE       8          /* Floating-point exception (ANSI).  */         建立CORE文件,浮点异常
SIGKILL      9          /* Kill, unblockable (POSIX).  */               终止进程     杀死进程
SIGUSR1      10         /* User-defined signal 1 (POSIX).  */           终止进程     用户定义信号1
SIGSEGV      11         /* Segmentation violation (ANSI).  */           建立CORE文件,段非法错误
SIGUSR2      12         /* User-defined signal 2 (POSIX).  */           终止进程     用户定义信号2
SIGPIPE      13         /* Broken pipe (POSIX).  */                     终止进程     向一个没有读进程的管道写数据
SIGALARM     14         /* Alarm clock (POSIX).  */                     终止进程     计时器到时
SIGTERM      15         /* Termination (ANSI).  */                      终止进程     软件终止信号
SIGSTKFLT    16         /* Stack fault.  */
SIGCLD       SIGCHLD    /* Same as SIGCHLD (System V).  */
SIGCHLD      17         /* Child status has changed (POSIX).  */        忽略信号     当子进程停止或退出时通知父进程
SIGCONT      18         /* Continue (POSIX).  */                        忽略信号     继续执行一个停止的进程
SIGSTOP      19         /* Stop, unblockable (POSIX).  */               停止进程     非终端来的停止信号
SIGTSTP      20         /* Keyboard stop (POSIX).  */                   停止进程     终端来的停止信号 Ctrl+Z
SIGTTIN      21         /* Background read from tty (POSIX).  */        停止进程     后台进程读终端
SIGTTOU      22         /* Background write to tty (POSIX).  */         停止进程     后台进程写终端
SIGURG       23         /* Urgent condition on socket (4.2 BSD).  */    忽略信号     I/O紧急信号
SIGXCPU      24         /* CPU limit exceeded (4.2 BSD).  */            终止进程     CPU时限超时
SIGXFSZ      25         /* File size limit exceeded (4.2 BSD).  */      终止进程     文件长度过长
SIGVTALRM    26         /* Virtual alarm clock (4.2 BSD).  */           终止进程     虚拟计时器到时
SIGPROF      27         /* Profiling alarm clock (4.2 BSD).  */         终止进程     统计分布图用计时器到时
SIGWINCH     28         /* Window size change (4.3 BSD, Sun).  */       忽略信号     窗口大小发生变化
SIGPOLL      SIGIO      /* Pollable event occurred (System V).  */
SIGIO        29         /* I/O now possible (4.2 BSD).  */              忽略信号     描述符上可以进行I/O
SIGPWR       30         /* Power failure restart (System V).  */
SIGSYS       31         /* Bad system call.  */
SIGUNUSED    31

对于从34~64的信号,其实它是LINUX的实时信号,他们并没有固定的含义,可以由用户自定义,他们的结果都是终止进程。

  • 查看信号的详细信息:man 7 signal

  • 信号的五种默认处理动作:

    • Term 终止进程

    • Ign 当前进程忽略掉这个信号

    • Core 终止进程,并生成一个Core文件

    • Stop 暂停当前进程

    • Cont 继续执行当前被暂停的进程

  • 信号的几种状态:产生、未决、递达

  • SIGKILL和SIGSTOP信号不能被捕捉、阻塞或忽略,只能执行默认动作。

0x02 信号相关的函数 

 一、kill函数 

    #include <sys/types.h>
    #include <signal.h>

    int kill(pid_t pid, int sig);
        - 功能:给任何的进程或进程组pidpid,发送任何信号sig
        - 参数:
            - sig:需要发送信号的编号或者宏值,0表示不发送任何信号。
            - pid:
                - >0 将信号发送给指定的进程
                - =0 将信号发送给当前的进程组
                - =-1 将信号发送给每一个有权限接收这个信号的进程
                < -1  这个pid=某个进程组的ID取反(-12345)-》(12345)
        kill(getppid(),9);
        kill(getpid(),9);

    int raise(int sig);
        - 功能:给当前进程发送信号
        - 参数:
            - sig:要发送的信号
        -返回值
            0 成功
            非0  失败
        kill(getpid(),sig);

    void abort(void);
        - 功能:发送SIGABRT信号给当前的进程,杀死当前进程
        kill(getpid(),SIGABRT);

那么其使用可以看如下代码:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
int main()
{
    pid_t pid = fork();

    if(pid==0)
    {
        int i=0;
        for(i=0;i<5;i++)
        {
            sleep(1);
            printf("child process\n");
        }
    }
    else if(pid>0)
    {
        printf("parent process\n");
        sleep(2);
        printf("kill child process now\n");
        kill(pid,SIGINT);
    }

    return 0;
}

二、alarm()函数

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
        -功能:设置定时器(闹钟),函数调用开始倒计时,倒计时为0时,函数会给当前的进程发送信号:SIGALARM
        -参数:
            seconds:倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
                    可用于取消一个定时器,通过alarm(0)。
        -返回值:
            之前没有定时器,返回0
            之前有定时器,返回之前的定时器剩余的时间
        -SIGALARM:默认终止当前的进程,每一个进程都有且只有一个唯一的定时器。
                   alarm(10); -> 0
                   过了一秒
                   alarm(5);//直接覆盖 倒计时五秒  -> 9

        alarm(100)该函数不阻塞。

下面这个代码验证了alarm函数的不阻塞性:

#include <unistd.h>
#include <stdio.h>

int main()
{
    int seconds = alarm(5);

    printf("seconds = %d\n",seconds);  //0

    sleep(2);

    seconds = alarm(2);

    printf("seconds = %d\n",seconds);  //3

    while(1)
    {

    }

    return 0;
}

三、setitimer()

    #include <sys/time.h>

    int setitimer(int which, const struct itimerval *new_value,
                    struct itimerval *old_value);
        - 功能:设置定时器,可以替代alarm函数,精度比alarm()高,精度为us,可实现周期性定时。
        - 参数:
            - which:定时器以什么时间计时
                ITIMER_REAL:真实时间,时间到啊,发送SIGALRM 常用
                ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM
                ITIMER_PROF:以该进程在用户态和内核台下所消耗的时间来计算,时间到达,发送SIGPROF。

            - new_value:设置定时器的属性
                struct itimerval {                  //定时器结构体
                    struct timeval it_interval;     //每个阶段的时间,间隔时间
                    struct timeval it_value;        //延迟多长时间执行定时器
                };

                struct timeval {                    //时间结构体
                    time_t      tv_sec;             //秒数    
                    suseconds_t tv_usec;            //微秒     
                };

            - old_value:记录上一次的定时的时间参数,一般不使用,指定NULL
        - 返回值
            0 成功
            -1 失败 并设置错误号

那么下面我们实现一个过了三秒后,每隔两秒定时一次的定时器:

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

//过三秒中 每个两秒定时一次
int main()
{
    struct itimerval new_value;

    //设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    //设置延迟时间,3秒后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL,&new_value,NULL);       //非阻塞、
    printf("clock start!!\n");
    if(ret == -1)
    {
        perror("setitimer");
        exit(0);
    }

    getchar();

    return 0;
}

四、signal()

    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
        - 功能:
            设置某个信号的捕捉行为
        - 参数:
            signum:要捕捉的信号(写宏值,在不同的操作系统上不一样)
            handler:捕捉到信号要如何处理
                - SIG_INGN:忽略信号
                - SIG_DFL:信号默认的行为
                - 回调函数:这个函数是内核调用的,程序员只负责写。捕捉到信号后如何去处理信号。
                    回调函数:
                        需要程序员实现,提前准备号的,函数的类型根据实际需求,看函数指针定义。
                        不是程序员调用,当信号产生由内核调用。
                        函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置。
        - 返回值:
            成功:返回上一次注册的信号处理函数的地址,第一个调用返回NULL
            失败:返回SIG_ERR,设置错误号

        - 注意:SIGKILL SIGSTOP不能被捕捉,不能被忽略

那么我们就可以实现对上述定时信号的捕捉:

#include <sys/time.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

void myalarm(int num)
{
    printf("the signal nums is : %d\n",num);
    printf("xxxxxx\n");
}

//过三秒中 每个两秒定时一次
int main()
{
    //注册信号捕捉
    //signal(SIGALRM,SIG_IGN);    //可以实现定时重复执行,总是回到这一句,进程并不会终止
    //signal(SIGALRM,SIG_DFL);    //会终止进程
    //指定回调函数 
    signal(SIGALRM,myalarm);


    struct itimerval new_value;

    //设置间隔时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    //设置延迟时间,3秒后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL,&new_value,NULL);       //非阻塞
    printf("clock start!!\n");
    if(ret == -1)
    {
        perror("setitimer");
        exit(0);
    }

    getchar();

    return 0;
}

那么这个alarm与setitimer的区别在于,alarm只能定时一次,而setitimer可以实现周期性的定时。setitimer如何实现周期性,这个时候就需要信号捕捉signal。但是这个signal函数需要使用sigaction来替换。具体是为什么,可以先学学信号集这个概念。

0x03 信号集

  • 许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为sigset_t

  • 在PCB中有两个非常重要的信号集,一个称为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的(也就是使用二进制位来进行实现)。但操作系统不允许我们直接对这两个信号集进行操作,而需自定义另一个集合,借助信号集操作函数来对PCB中的这两个信号集做修改。

  • 信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间。

  • 信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

  • 信号的阻塞就是让系统暂时保留信号留待以后发送,由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

一、信号集的处理过程  

  • 用户通过键盘Ctrl C,产生一个2号信号SIGINT(信号被创建)

  • 信号产生但是没有被处理(处于一个未决状态)

    • 在内核中将所有的没有被处理的信号存储在一个集合中(未决信号集)

    • SIGINT信号,状态是被存储在第二个标志位上。(也就是理解为现在有一个64位的寄存器,对寄存器的值进行修改),这个标志位的值为0,表示信号并不是未决状态,当这个标志位的值为1时,说明信号处于未决状态。

  • 未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集)进行比较。

    • 阻塞信号集默认不阻塞任意一个信号,但是我们也可以设置为阻塞。然后不会去处理,所以我们就是操作这个地方。

    • 如果想要阻塞某些信号,需要用户调用系统的API。

  • 在处理的时候和这个阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了。

    • 如果没有阻塞,那么这个信号就被处理。

    • 如果阻塞了,这个信号继续处于未决状态,直到阻塞解除,这个信号就被处理。

二、关于信号集处理的函数 

    int sigemptyset(sigset_t *set);
        - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
        - 参数:set,传出参数,需要操作的信号集
        - 返回值:成功返回0, 失败返回-1

    int sigfillset(sigset_t *set);
        - 功能:将信号集中的所有的标志位置为1
        - 参数:set,传出参数,需要操作的信号集
        - 返回值:成功返回0, 失败返回-1

    int sigaddset(sigset_t *set, int signum);
        - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
        - 参数:
            - set:传出参数,需要操作的信号集
            - signum:需要设置阻塞的那个信号
        - 返回值:成功返回0, 失败返回-1

    int sigdelset(sigset_t *set, int signum);
        - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
        - 参数:
            - set:传出参数,需要操作的信号集
            - signum:需要设置不阻塞的那个信号
        - 返回值:成功返回0, 失败返回-1

    int sigismember(const sigset_t *set, int signum);
        - 功能:判断某个信号是否阻塞
        - 参数:
            - set:需要操作的信号集
            - signum:需要判断的那个信号
        - 返回值:
            1 : signum被阻塞
            0 : signum不阻塞
            -1 : 失败

以上的函数,都是对自定义的信号集进行操作,其使用可以看看如下函数:

#include <signal.h>
#include <stdio.h>

int main() 
{

    // 创建一个信号集
    sigset_t set;

    // 清空信号集的内容
    sigemptyset(&set);

    // 判断 SIGINT 是否在信号集 set 里
    int ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }

    // 添加几个信号到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    // 判断SIGINT是否在信号集中
    ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }

    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }

    // 从信号集中删除一个信号
    sigdelset(&set, SIGQUIT);

    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }

    return 0;
}

那么如果我们需要实现对内核信号集的修改,这个时候就需要一些系统调用函数:

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
        - 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
        - 参数:
            - how : 如何对内核阻塞信号集进行处理
                SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变(按位添加)
                    假设内核中默认的阻塞信号集是mask, mask | set
                SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
                    mask &= ~set
                SIG_SETMASK:覆盖内核中原来的值(直接替换)
            
            - set :已经初始化好的用户自定义的信号集
            - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
        - 返回值:
            成功:0
            失败:-1
                设置错误号:EFAULT、EINVAL

    int sigpending(sigset_t *set);
        - 功能:获取内核中的未决信号集
        - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。

那么对于信号集,到目前为止,我们出现了未决信号集、阻塞信号集、自定义信号集,那么他们之间的关系到底是什么:

 阻塞信号集和未决信号集在内核PCB中,因此我们无法操作,但是可以操作自定义信号集,然后将其通过函数映射给阻塞信号集来间接操作,信号集的本质,也就是位图。

我们现在先实现一个对内核的信号2与3进行处理,将其阻塞后使用自己的键盘来产生这些信号,并且把所有的常规信号的未决状态打印到屏幕:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main() {

    // 设置2、3号信号阻塞
    sigset_t set;
    sigemptyset(&set);
    // 将2号和3号信号添加到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    // 修改内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &set, NULL);

    int num = 0;

    while(1) {
        num++;
        // 获取当前的未决信号集的数据
        sigset_t pendingset;
        sigemptyset(&pendingset);
        sigpending(&pendingset);

        // 遍历前32位
        for(int i = 1; i <= 31; i++) {
            if(sigismember(&pendingset, i) == 1) {
                printf("1");
            }else if(sigismember(&pendingset, i) == 0) {
                printf("0");
            }else {
                perror("sigismember");
                exit(0);
            }
        }

        printf("\n");
        sleep(1);
        if(num == 10) {
            // 解除阻塞
            sigprocmask(SIG_UNBLOCK, &set, NULL);
        }

    }


    return 0;
}

这个如果没有使用num来解除阻塞,就需要进行kill -9来杀死。加入num后他会自动结束这个进程。

那么接下来是信号捕捉函数sigaction():

    #include <signal.h>
    int sigaction(int signum, const struct sigaction *act,
                            struct sigaction *oldact);

        - 功能:检查或者改变信号的处理。信号捕捉
        - 参数:
            - signum : 需要捕捉的信号的编号或者宏值(信号的名称)
            - act :捕捉到信号之后的处理动作
            - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL(保存上一次)
        - 返回值:
            成功 0
            失败 -1

     struct sigaction {
        // 函数指针,指向的函数就是信号捕捉到之后的处理函数(回调函数)
        void     (*sa_handler)(int);
        // 不常用,保存形参信号相关信息
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
        sigset_t   sa_mask;
        // 使用哪一个信号处理对捕捉到的信号进行处理
        // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
        int        sa_flags;
        // 被废弃掉了
        void     (*sa_restorer)(void);
    };

那么改改上面所实现的定时器:

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}

// 过3秒以后,每隔2秒钟定时一次
int main() {

    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = myalarm;
    sigemptyset(&act.sa_mask);  // 清空临时阻塞信号集
   
    // 注册信号捕捉
    sigaction(SIGALRM, &act, NULL);

    struct itimerval new_value;

    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");

    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }

    // getchar();
    while(1);

    return 0;
}

最好使用sigaction是因为它是基于posix的原则,在其他系统上的兼容性较强。、

0x04 内核实现信号捕捉的过程

 0x05 SIGCHLD信号

SIGCHLD信号产生的条件:

  • 子进程终止时
  • 子进程接收到SIGSTOP信号停止时
  • 子进程处在停止态,接收到SIGCONT后唤醒时

 以上三种条件都会给父进程发送SIGCHLD信号,父进程默认会忽略该信号。但是他可以解决僵尸进程的问题,对于向wait这种函数,是阻塞的,还不如用信号来处理子进程的结束后的回收。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

void myFun(int num) {
    printf("捕捉到的信号 :%d\n", num);
    // 回收子进程PCB的资源
    // while(1) {
    //     wait(NULL);     //未决信号集只能接收一个 其他为阻塞 所以用wait不可以处理所有子进程,所以需要写一个死循环
           //但是这样你的父进程无法处理其他的事情
    // }
    while(1) {
       int ret = waitpid(-1, NULL, WNOHANG);        //指定为非阻塞
       if(ret > 0) {
           printf("child die , pid = %d\n", ret);
       } else if(ret == 0) {
           // 说明还有子进程 需要退出回到父进程
           break;
       } else if(ret == -1) {
           // 没有子进程 子进程都回收完毕
           break;
       }
    }
}

int main() {

    // 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);

    // 创建一些子进程
    pid_t pid;
    for(int i = 0; i < 20; i++) {
        pid = fork();
        if(pid == 0) {
            break;
        }
    }

    if(pid > 0) {
        // 父进程

        // 捕捉子进程死亡时发送的SIGCHLD信号
        struct sigaction act;
        act.sa_flags = 0;
        // 这里是执行对应的回调函数 
        act.sa_handler = myFun;
        sigemptyset(&act.sa_mask);
        sigaction(SIGCHLD, &act, NULL);

        // 注册完信号捕捉以后,解除阻塞
        sigprocmask(SIG_UNBLOCK, &set, NULL);

        while(1) {
            printf("parent process pid : %d\n", getpid());
            sleep(2);
        }
    } else if( pid == 0) {
        // 子进程
        printf("child process pid : %d\n", getpid());
    }

    return 0;
}

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

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

相关文章

通过FNN算法进行特征组合的商品推荐详细教程 有代码+数据

案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。CTR表示Clic…

【2021 MCM】 Problem A: Fungi by 2100454

【2021 MCM】 Problem A: Fungi by 2100454 文章目录【2021 MCM】 Problem A: Fungi by 2100454一、题目分析1.1 问题总述1.2 具体任务1.3 需要提交的内容二、论文解读2.1 摘要2.2 目录2.3 简介2.4 假设2.5 缩写和定义2.6 The GAME Model2.6.1 Gause’s Model for Predicting F…

防火墙基本概念

防火墙是一款具有安全防护功能的网络设备&#xff0c;保护一个网络区域避免另一个网络区域的攻击和入侵。 物理防火墙&#xff08;物理设备&#xff09;、软件防火墙&#xff08;Windows自带firewall&#xff09; 其本职工作是隔离网络 基本功能 会话管理内网安全管控入侵…

[附源码]java毕业设计中医药系统论文2022

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

痞子衡嵌入式:MCUXpresso IDE下高度灵活的FreeMarker链接文件模板机制

大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家分享的是MCUXpresso IDE下高度灵活的FreeMarker链接文件模板机制。 痞子衡之前写过一篇文章 《MCUXpresso IDE下工程链接文件配置管理与自动生成机制》&#xff0c;这篇文章介绍了 MCUXpresso ID…

网页前端知识汇总(三)——网页前端利用二维码插件qrcode生成在线二维码

最近几年二维码的广泛应用&#xff0c;方便了很多行业&#xff0c;如支付宝&#xff0c;微信&#xff0c;小程序扫码之类的&#xff0c;这个在二十年前&#xff0c;想都不敢想这么方便&#xff0c;那时候有书刊编码扫一扫都感觉是高科技了&#xff0c;如今&#xff0c;二维码的…

RNA-seq 详细教程:实验设计(2)

学习目标 了解设置重复对于 RNA-seq 分析的重要性了解生物重复次数、测序深度和鉴定到的差异表达基因之间的关系了解如何设计RNA-seq 实验&#xff0c;以避免批次效应1. 注意事项 了解 RNA 提取和 RNA-seq 文库制备实验过程中的步骤&#xff0c;有助于设计 RNA-seq 实验&#x…

PyTorch学习笔记-常用函数与数据加载

1. PyTorch常用函数 &#xff08;1&#xff09;路径相关的函数 假设我们数据集的目录结构如下&#xff1a; 首先需要 import os&#xff0c;在 os 中常用的路径相关的函数有&#xff1a; os.listdir(path)&#xff1a;将 path 目录下的内容列成一个 list。os.path.join(path1…

cmake入门教程 跨平台项目构建工具cmake介绍

一.初识cmake 在介绍cmake之前&#xff0c;我们先来从工具一个个衍生出来&#xff0c;做过linux c/c编程的时候一般用过gcc指令或者makefile。我们先来介绍下 gcc&#xff08;GNU Compiler Collection&#xff09;将源文件编译&#xff08;Compile&#xff09;成可执行文件或…

若依框架解读(前后端分离版)—— 1.Spring Security相关配置(@Anonymous注解)

有关Spring Security与JWT相关知识可以看我之前写的文章&#xff1a;SpringBoot整合SpringSecurityJWT(三更草堂) 这边需要对RBAC模型有一点了解&#xff0c;比较简单可自行百度。 首先查看Security配置类SecurityConfig&#xff0c;如果我们想要放行自己写的接口是可以在此配置…

数学建模国赛/美赛常见赛题类型及建模方案(纯干货)

目录 一&#xff0c;评价类问题 1&#xff0c;建模步骤如下图所示&#xff1a; 2&#xff0c;主客观评价问题的区别 3&#xff0c;如何选择合适的评价方法 二&#xff0c;预测类赛题 1&#xff0c;预测类赛题的基本解题步骤 2&#xff0c;预测类问题的区别 3&#xff0c;…

什么是Tomcat?如何使用Tomcat?如何部署一个静态页面?

目录 1、Tomcat是什么&#xff1f; 2、下载安装 3、目录结构 4、启动服务器 5、部署静态页面&#xff08;简单举例&#xff09; 1、Tomcat是什么&#xff1f; Tomcat是一个HTTP服务器&#xff0c;Tomcat就是基于Java实现的一个开源免费的HTTP服务器 2、下载安装 下载网…

virtualbox安装openEuler-方案二

下载的讲解在另一篇&#xff1a;VirtualBox安装openEuler 方案一 安装&#xff1a; 1&#xff0c;配置网卡 2&#xff0c;加载光驱设置 选择对应的iso文件即可。 3&#xff0c;启动openEuler 选择第一个即可&#xff0c;第二个选项一般是在生成环境中使用。 经过一段…

std::weak_ptr(分析、仿写)

目录 一、为什么会有weak_ptr? 1、看一个例子 2.weak_ptr 是什么? 3.weak_ ptr 如何使用? 1、如何创建weak_ ptr 实例 2、如何判断weak_ptr指向的对象是否存在 3、weak_ptr的使用 二、仿写std::weak_ptr 1、c参考手册 2、代码 一、为什么会有weak_ptr? 1、看一个例子…

18uec++多人游戏【服务器为两个角色发枪,并能在线开枪】

打开主角类&#xff0c;生成枪的代码逻辑在游戏开始函数里 所以在生成之前&#xff0c;我们需要判断该对象是否在服务器端&#xff08;服务器端视角&#xff09; void ASCharacter::BeginPlay() {Super::BeginPlay();DefaultsFOV CameraComp->FieldOfView;//判断是否在服务…

支付系统 — 支付路由

本文主要介绍下支付中路由系统的主要流程。 支付路由的作用 降低成本&#xff1a;越便宜越好&#xff1b; 提高用户体验&#xff1a;用户支付的越爽越好&#xff1b;越快越好&#xff1b;成功率越高越好。 确保有可用通道&#xff1a;多个选择&#xff0c;确保能完成支付。 …

【JVM】PC程序计数器和PC寄存器

一、JVM体系结构 本文所讲内容主要是 JVM 体系结构图中 运行时数据区 中的 PC寄存器&#xff0c;如下图所示&#xff1a; 二、PC寄存器是什么&#xff1f; 这里引用别人的一句话&#xff1a; 首先这里的PC寄存器并非广义上所指的物理寄存器&#xff0c;或许将其翻译为PC计数…

antd Carousel 重写dot样式

antd的Carousel走马灯组件的dot也就是下面那个滑动的按钮非常的不起眼。 白色背景的时候完全看不到。 但是我们大部分时候又都是白色背景&#xff0c;于是来自己重写一下样式。 在控制台看了一下&#xff0c;应该是这个属性在控制dot的颜色&#xff0c;重写这个属性就可以了。…

Nginx源码解析 --红黑树

预读知识 红黑树是一种自平衡二叉树&#xff0c;不仅可以使用二分法快速查找&#xff0c;而且插入和删除操作的效率也很高&#xff0c;常用于构造关联数组&#xff08;例如C标准库里的set和 map)。 在Nginx里红黑树主要用在事件机制里的定时器&#xff0c;检查连接超时&#…

Debian11之基于kubeadm安装K8S集群

官方安装教程 硬件要求 每台机器的内存要 2GB、CPU2 核心及以上 集群中的所有机器的网络彼此均能相互连接&#xff08;公网和内网都可以&#xff09; 节点之中不可以有重复的主机名、MAC 地址或 product_uuid 开启机器上的某些端口 为了保证 kubelet 正常工作&#xff0c;必须…