操作系统(linux)中信号工作的原理分析

信号

首先我们先理解一下信号是什么?在linux下我们先看看都有那些信号,
我们输入kill -l 就会出现
信号表
这就是信号的全部种类,总共有62种信号,其中1到31是普通信号,也是这篇主要理解的,后面34到64的信号为实时信号。

信号是干什么的呢?
我们举个例子:
最简单的理解,在linux下我们在运行某个进程的时候,通常在shell下启动一个前台进程,但是我们进程运行过程中我们,按下Ctrl+c,这个时候我们的进程就会停止或者说结束。其实,这就是信号起了作用,在我们按下Ctrl+c的时候,将会产生硬件中断,从而cpu转到内核去处理中处理,同时终端会将Ctrl+c被解析成一个SIGINT信号,发送给进程PBC,这个时候PCB中的会被记录下一个SIGINT信号,当某个时候cup从内核切回到用户空间时候,所以会处理这个进行中记录的信号,而这个信号的默认处理动作是结束进程。

信号的产生

关于信号的产生有三种情况:
1)键盘按键产生信号
键盘按键产生信号很好理解,比如我按的 Ctrl+c 产生信号SIGINT,按下Ctrl+z 产生SIGSTP 信号,Ctrl+\ 产生SIGOUT信号。这里要说一一下Ctrl+\ 这个产生信号,将程序终止掉后会产生core dump文件。

core dump文件干什么用的?core dump 是在程序运行过程中收到信号,或者在程序异常终止后,系统会把程序在内核中程序终止时候的信息保存到core dump文件中。我们可以用ulimit -c 来进行对core dump文件大小进行调整。在不修改的前提下默认的core dump文件的大小为0。
core dump文件
2)调用系统函数
调用系统函数向进程发送信号,我们看看系统调用函数。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

上面的两个函数,我们来解释一下,
kill函数两个参数,pid为给哪个进程要发信号,signo为哪中信号。
raise函数其实是kill函数的封装,raise函数是自己给自己发送任意信号。
上面两个函数都是成功返回1,失败返回0。

还有个函数,是c语言中的函数

#include <stdlib.h>
void abort(void);

abort函数是给自己发送abort信号。就像exit函数一样,但是abort总会成功。

3)软件产生信号
如果了解进程间的通信,我们就会知道有个匿名管道,如果我们在打开管道,如果打开写端关闭读端,系统会自动的发送一个SIGPIPE信号,关闭管道。

信号的阻塞

信号阻塞我们就需要了解信号在PCB中是怎么样的一种方式来表示它的信号。
在进程中当一个进程收到一个信号的时候,信号其实是先到该进程中的PCB中找到一个表,我们就叫未决信号表,其实是二进制表示的位图,先将 sigset_t 动作叫做信号递达。

我们用张图来理解:
进程中信号阻塞表
我们要注意

进程可以选择阻塞某个信号
如果在信号未决后,我们设置了阻塞,那么该信号会在未决表中等待解除阻塞
在信号中我们要明白,信号的阻塞和忽略不同,忽略是信号的处理方式,而阻塞只是信号在传递过程中,对信号的一种延后处理的行为。

信号操作函数

在这里我们了解几个信号集操作的函数

#include <signal.h>
int sigemptyset(sigset_t* set); // 将全部的比特为0
int sigfillset(sigset_t* set); // 全部比特为1
int sigaddset(sigset_t* set, int signo); // 在某一为上设置1
int sigdelset(sigset_t* set, int signo); // 某一位设置为0
int sigismember(const sigset_t* set, int signo);//查看是否被设置

这五个函数中,前面四个函数,都是成功返回1,失败返回0.
最后一个函数是一个bool的如果被设置返回1,没有返回0;

信号屏蔽

在看过对信号集操作函数后,我们就来看看信号阻塞函数。
函数不仅仅是可以阻塞信号,还可以读取和修改。

#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);

返回值是,成功返回0,失败则返回1;
参数说明,how是采用那种方式,set是输入型参数,oset是输出型参数。
这里的how有这么几个要参数:

  • SIG_BLOCK 相当于在原有的信号屏蔽字中添加新的屏蔽字。
  • SIG_UNBLOCK 解除set的当前信号屏蔽字,只解除set对应的。
  • SIG_SETMASK 设置当前屏蔽字,就不用管原来的字是什么,就设置成set

这里我们用一个例子来验证一下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void Handle(int sig) // 当信号捕捉后,就会跳到这个函数中
{
    printf("sig = %d\n",sig);
}

void PrintSigSet(sigset_t* set)
{
    int i = 1;
    for (; i <= 31; ++i)
    {   
        if (sigismember(set,i)) 
        {   
            printf("1");
        }   
        else
        {   
            printf("0");
        }   
    }   
    printf("\n");
}

int main()
{
    // 捕捉SIGINT信号
    signal(SIGINT, Handle); // 捕捉函数
    // 再屏蔽SIGINT信号
    sigset_t set;
    sigemptyset(&set); // 把要设置的set全部bit位设置为0
    sigaddset(&set, SIGINT); // 添加要设置的位,SIGINT是2号信号
    sigprocmask(SIG_BLOCK, &set, NULL); // 把set设置到信号屏蔽字中
    // 循环读取未决信号集
    while (1)
    {
        sigset_t pending_set;

        //这个函数是读取未决信号集,且只能读,不能修改,
        //因为我们屏蔽了2号信号,所以在未决信号集中会在2号位置出现1
        sigpending(&pending_set);

        PrintSigSet(&pending_set);
        sleep(1);
    }                           
    return 0;
}

上面就是我们在用函数 sigprocmask 把信号的屏蔽字中的2号信号SIGINT设置为屏,所以我们在程序运行时候,观察未决信号集表中,当我们按下Ctrl+c时候,未决信号集表中2号位置,会变成1,当时就不会变回去,是因为我们没有解除对二号信号的屏蔽,当我们解除屏蔽,信号才能递达。

我们来看看结果:
未决信号集

信号屏蔽字中对信号的解除

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void Handle(int sig) // 当信号捕捉后,就会跳到这个函数中
{
    printf("sig = %d\n",sig);
}

void PrintSigSet(sigset_t* set)
{
    int i = 1;
    for (; i <= 31; ++i)
    {   
        if (sigismember(set,i)) 
        {   
            printf("1");
        }   
        else
        {   
            printf("0");
        }   
    }   
    printf("\n");
}

int main()
{
    // 捕捉SIGINT信号
    signal(SIGINT, Handle); // 捕捉函数
    // 再屏蔽SIGINT信号
    sigset_t set; // 定义一个set用来设置信号屏蔽字
    sigemptyset(&set); // 把要设置的set全部bit位设置为0
    sigaddset(&set, SIGINT); // 添加要设置的位,SIGINT是2号信号
    sigprocmask(SIG_BLOCK, &set, NULL); // 把set设置到信号屏蔽字中
    // 循环读取未决信号集
    int i = 10;
    while (1)
    {
        sigset_t pending_set;

        //这个函数是读取未决信号集,且只能读,不能修改,
        //因为我们屏蔽了2号信号,所以在未决信号集中会在2号位置出现1
        sigpending(&pending_set);
        if (i == 0)
        {
            // 3秒后就会解除信号屏蔽字中的SIGINT,未决表中也会变成0
            // 解除信号屏蔽字
            sigprocmask(SIG_UNBLOCK, &set, NULL); 
            --i;
        }
        PrintSigSet(&pending_set);
        sleep(1);
    }                           
    return 0;
}

这里我们为了更清楚的演示信号被解除,就是信号未决表中的2号位置的1被置为0,所以我们不然程序立马收到SIGINT信号退出,所以我们用了信号捕捉。
我们来看看上面代码的运行结果:
信号的解除
图中的1,是我们按下Ctrl+c产生SIGINT信号
途中的2,是我们再次按下Ctrl+c,为了验证我们用了信号的捕捉。
图中的3,是我们再10秒后信号解除函数执行,未决表中全为0,因为信号已经被处理。

信号的捕捉

在看完阻塞,我们还需要知道信号的的处理方式,信号的处理方式有三种:
1)忽略
2)默认
3)捕捉
信号的忽略我们可以用

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);

其中hangdler是一个函数指针。
只要将handler参数填写成常数SIGIGN,就会忽略相对应的signum信号,但是在信号中会有几个函数是不能被忽略或者不能被捕捉。

我们主要介绍的是函数的捕捉。
函数捕捉,我们用图来了解一下在一个程序执行的过程中收到信号,然后程序自己进行对信号的捕捉的过程。
信号自定义捕获
上图中我们就可以看出,在捕获过程中进行了四次的用户与内核之间的交互。
要注意的是:
子啊主控制流的收到信号、异常、或者中断时候,会把当前进程的阻塞,一直等处理完信号。

SIGCHLD信号

这个信号我们来单独认识一下。
它在linux中用kill -l我们就可以看出来,是一个17号信号,为什么说它呢?
因为在对与父进程与子进程间的进程等待问题。
如果父进程没有读取子进程的返回码,就会产生僵尸进程,造成内存泄漏,所以父进程就需要进程等待。

这个信号会在子进程终止时候,父进程发送一个SIGCHLD信号,但是父进程对该信号默认处理的是忽略的,所以我们改变信号的处理方式为捕捉,在捕捉函数中我们就可以wait子进程的退出码。所以父进程就不必关心子进程会变成僵尸进程。

下面我们就用一段代码来具体实现一下:

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

void handler(int sig)
{
    (void)sig;
    while (1) 
    {   
        // 这里采用轮巡的等待方式是因为,有可能,
        // 在触发信号的子进程有很多
        // 但是未决信号表只能表现为一次,
        // 所以我们采用waitpid轮巡的去等待
        pid_t id = waitpid(-1, NULL, WNOHANG);
        if (id > 0)
        {
            printf("wait success\n");
        }   
        if (id < 0)
        {   
            break;
        }   
    }   
}
int main()
{
    signal(SIGCHLD, handler); // 用来接受信号并对其进行捕捉
    pid_t id = fork();
    if (id == 0)
    {
        printf("i am child\n");
        sleep(3);
        exit(1);
    }
    while (1)
    {
        printf("i am father\n");
        sleep(1);
    }
    return 0;
}                       

我们来看代码的结果:
信号方式处理进程等待
我们开始创建father和child进程,当子进程3秒后,退出,触发sigchld信号,程序到捕捉函数中,执行完成后,又回到主线程中。

如有错误,多多指导,谢谢!

猜你喜欢

转载自blog.csdn.net/gangstudyit/article/details/80551912