欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux信号捕捉

程序员文章站 2022-07-12 11:19:18
...
内核是怎样实现信号的捕捉呢?处理流程如下图所示:
Linux信号捕捉
系统往往在从内核态切回用户态时会进行信号的处理。

信号捕捉函数:

sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signo, struct sigacton *act, struct sigaction* oact);
这两个函数都可以对信号进行捕捉,sigaction相对于signal来说相对复杂一点,区别在于:
1、signal在调用handler之前先把信号的handler指针恢复;sigaction调用之后不会恢复handler指针,直到再次调用sigaction修改handler指针。
2、signal在调用过程不支持信号block;sigaction调用后在handler调用之前会把屏蔽信号(屏蔽信号中自动默认包含传送的该信号)加入信号中,handler调用后会自动恢复信号到原先的值。

具体看一下sigaction函数:
#include<signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction可以修改相关与指定信号相关联的处理动作,成功返回0,出错返回-1。

关于结构体 struct sigaction
struct sigaction
{
     void (*sa_handler) (int); //信号处理方式
     sigset_t sa_mask; //屏蔽的信号
     int sa_flag;
     void (*sa_sigaction)(int , siginfo_t *, void *);
}
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用⾃自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则 用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

看一个关于sinaction的实例:
#include<stdio.h>
#include<signal.h>

void handler(int sig)
{
    printf("get a sig : %d\n", sig);   
}

int main()
{
    struct sigaction act, oact;
    act.sa_handler = &handler;//设置捕捉函数
    sigemptyset(&act.sa_mask);//使用act之前先对其进行清空
    sigaction(2, &act, &oact);//捕捉2号信号
    while(1);
    return 0;
}

运行结果:
Linux信号捕捉


我们可以通过sigaction实现自己的sleep()函数。
实现原理:
比如我们要进行sleep(3),可以这样实现,在运行在这条语句时将进程挂起,等到3秒钟后产生一个信号将进程从挂起**即可。

我们要实现进程的挂起可以使用pause()函数实现。
pause函数:
#include<unistd.h>
int pause(void );
函数介绍:
会使进程挂起,直到有信号递达该进程才会取消挂起。
该函数只有出错返回值,如果处理信号的默认动作是终止该进程,那么进程将被终止pause没有返回的机会;如果处理信号的默认动作是忽略,那么进程将继续保持挂起状态不能被执行,pause也不能返回;只有信号被捕捉处理时,捕捉函数调用完成之后pause才会返回-1,此时挂起状态被破坏。

对于时间的控制我们可以使用alarm()函数实现。
alarm函数:
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,在seconds秒后会给当前进程发送SIGALRM信号,该信号默认动作是终止当前进程。这个函数的返回值是0或者是以前是定闹钟时间还剩下的秒数。

实现代码:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

//只需要睡眠,不做操作即可
void handler(int sig)
{

}

void mysleep(int time)
{
    struct sigaction act, oact;
    //设置捕捉函数
    act.sa_handler = handler;
    //清空信号集
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    //设置捕捉动作
    sigaction(SIGALRM, &act, &oact);
   
    //定下闹钟
    alarm(time);
    //挂起进程
    pause();
    int ret = alarm(0);
    //将进程的信号集,恢复原来的状态
    sigaction(SIGALRM,&oact, NULL);   
}

int main()
{
    while(1)
    {
        mysleep(1);
        printf("use mysleep\n");
    }
    return 0;
}
运行结果,每隔一秒钟输出
Linux信号捕捉