Linux信号

一、信号:在计算机中存在了一组由整数构成的一组数,这组数大多用于软件中断作用。

用kill -l可以查看系统中所定义的信号。


wKioL1clo-KTTL5cAAEg8Wk91UA719.png

其中1到31号信号为普通信号,34到64号为实时信号。这里主要讨论普通信号。

这些信号的默认动作基本上是中断/忽略,或者为中断/core。

core:它是程序运行时中断后存在计算机中的一个复制该进程的一个储存印象,文件名为core,在调试的时候用它可以查找中断原因。

二、信号的产生

1>由键盘产生:例如Ctrl+Z,Ctrl+C等,这两个信号可以结束前端正在运行的程序。

2>由软件产生:例如一些信号产生函数,aralm(),abort(),kill(),raise()等。

3>由硬件产生:除数为零,无效内存的引用等。

三、信号的处理:

信号的处理有三种方式。

1>:忽略该信号。

2>:执行默认方式。

3>:执行用户自定义动作,称为捕捉该信号。

四、阻塞、未决和递达

信号的阻塞:一旦信号阻塞,它就不会被递达,注意它表示的是一种状态,和是否未决没有关系。

信号的未决:信号在产生和递达之前这段过程中对信号的一个标识,有或者无。

信号的递达:信号的处理方式,执行默认动作or捕捉该信号。

wKiom1clqCqSm_NVAAEaaJWOsBc983.png

每个进程或者线程都保留三个表,block,pending,handler(指针数组),block表示信号是否被阻塞,pending表示信号是否未决,handler表示信号递达后将会执行什么样的动作。

例如SIGHUP(1)它没有被阻塞并且也没未决,SIGINT(2)它被阻塞了,已经未决了,一旦解除阻塞他将执行SIG_IGN动作。

五、函数使用

1>产生信号的函数

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

       int kill(pid_t pid, int sig);
       int raise(int sig);
       unsigned int alarm(unsigned int seconds);
       void abort(void);

kill可以在前端使用kill+进程号+信号名来杀死某个进程,也可以用这个函数在进程内终止某个进程。

raise()给当前进程发送指定的信号。

alarm()它想当于一个闹钟,在程序运行多少秒之后发送一个闹钟信号SIGALRM也就是14号信号。

abort()用于结束该进程,因为该函数总是会成功所以没有返回值。

2>信号集的处理函数

sigset_t,它表示信号集,是该pcb中做保存的sig集合。

       #include <signal.h>

       int sigemptyset(sigset_t *set);

       int sigfillset(sigset_t *set);

       int sigaddset(sigset_t *set, int signum);

       int sigdelset(sigset_t *set, int signum);

       int sigismember(const sigset_t *set, int signum);

sigemptyset()和sigfillset()用于初始化信号集,empty表示把所用都清为0,fill表示初始化为该计算机系统所支持的所用信号对应位设置为1或者0;在用使用信号集是必须先用这两个函数对信号集进行初始化。

sigaddset表示在这个信号集中添加一个信号del为删除

sigsimember()用于表示该信号集的信号为1或0。

       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

sigpromask()是设置信号屏蔽字的函数,第二个参数表示设置为哪一个信号集,第二个参数为保存老的信号集便于恢复,how有以下参数

wKiom1clsrvxMz5lAAGY09lPgiU601.png

       int sigpending(sigset_t *set);

sigpending()用于获取该进程的未决信号集。

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

signal()是一个信号捕捉的函数,他将某一个要在该线程中递达的信号执行用户自定义动作。


下面用上面的函数实现一个阻塞键盘输入的Ctrl+C也就是2号信号时,该信号阻塞,不会终止程序,五秒钟后解除该信号的阻塞,再输入Ctrl+C时该信号执行用户自定义动作,所以效果为前五秒钟2号信号未决但被阻塞不递达,五秒钟后,信号递达。

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<unistd.h>
  4 typedef void (*sighandler_t)(int);
  5 
  6 void hander(int arg)
  7 {
  8     printf("sig is kill\n");
  9 }
 10 
 11 void printfsig(sigset_t* sig_pend)
 12 {
 13     int i=0;
 14     for(i=1;i<32;i++)
 15     {
 16         if(sigismember(sig_pend,i))
 17         {
 18             printf("1");
 19         }
 20         else
 21         {
 22             printf("0");
 23         }
 24     }
 25     printf("\n");
 26 }
 27 
 28 int main()
 29 {
 30     sigset_t sig_set;
 31     sigset_t sig_old;
 32     sigset_t sig_pend;
 33     sighandler_t p_handler=hander;
 34     sigemptyset(&sig_set);
 35     sigaddset(&sig_set,SIGINT);
 36     signal(SIGINT,p_handler);
 37     sigprocmask(SIG_SETMASK,&sig_set,&sig_old);
 38     int i=0;
 39     while(i++>=0)
 40     {
 41         sigpending(&sig_pend);
 42         printfsig(&sig_pend);
 43         sleep(1);
 44         if(i==5)
 45         {
 46             sigprocmask(SIG_SETMASK,&sig_old,NULL);
 47         }
 48     }
 49     return 0;
 50 
 51 }

wKiom1clteWC9u0DAAAu7G2VLI4294.png

可以看出在信号解除阻塞后未决信号集中的信号会首先被递达。

六:信号的捕捉

       #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
       struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

如果在调用信号处理函数对当前信号进行处理的时候,又产生了该信号,则会将它添加到信号屏蔽字当中,直到信号处理函数返回时,自动恢复为原来的信号屏蔽字。

将sa_handler赋值为SIG_IGN表示忽略该信号,赋值为SIG_DFL表示执行系统默认动作,赋值为用户自定义函数指针表示用户自定义函数捕捉该信号,或者说是向内核注册了一个信号处理函数。

sa_mask设置在调用信号处理函数时除了当前信号自动屏蔽以为还希望屏蔽的其它哪些信号。

sa_flags设置为0;

restorer事实时信号。

        int pause(void);

pause();用于将进程挂起直到有信号递达,信号动作为终止则直接终止,信号动作为忽略则继续挂起,信号为执行自定义则返回-1.


用sigaction模拟了函数sleep()

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<stdlib.h>
  4 #include<pthread.h>
  5 void sig_sleep()
  6 {
  7         ;
  8 }
  9 
 10 void sleep_sig(unsigned num)
 11     {
 12         
 13         struct sigaction new_action;
 14         struct sigaction old_action;
 15         new_action.sa_handler=sig_sleep;
 16         new_action.sa_flags=0;
 17         sigemptyset(&new_action.sa_mask);
 18         sigaction(SIGALRM,&new_action,&old_action);
 19         alarm(num);
 20         pause();
 21         alarm(0);
 22         sigaction(SIGALRM,&old_action,NULL);
 23     }
 24 int main()
 25 {
 26     while(1)
 27     {
 28         sleep_sig(4);
 29         printf("weeking...\n");
 30     }
 31 
 32     return 0;
 33 }

wKioL1clu9ijaum3AAAVNZNbPJE334.png每隔4秒会weeking..


下面分析一下上面代码中系统的运作:

1.首先main函数中调用了sleep_sig()函数,sleep_sig()函数使用sigaction函数向内核注册了一个SIGALRM信号的处理函数new_action。

2.执行alarm(num),pause()将进程挂起,num秒之后内核发送SIGALRM给这个进程。

3.从内核返回这个进程的时候发现有未决的信号,其处理状态是new_action.

4.从内核模式返回用户模式调用new_action,调用new_action时SIGALRM加入信号屏蔽字,从new_action返回时取消屏蔽

5.然后执行系统调用sigreturn返回内核。

6.返回用户模式,pause()返回-1,调用alarm()将闹钟初始化,在调用sigaction将SIGALRM返回为之前的默认动作。