内核是怎样实现信号的捕捉呢?处理流程如下图所示:




signalr grpc 区别_sleep的模拟实现


系统往往在从内核态切回用户态时会进行信号的处理。



信号捕捉函数:



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;
}



运行结果:



signalr grpc 区别_sleep的模拟实现_02




我们可以通过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;
}


运行结果,每隔一秒钟输出



signalr grpc 区别_信号的捕捉_03