信号:通俗来说,就是使操作系统执行某些指定动作。

操作系统中的信号一共有62中,通过kill-l命令可以查看这些信号,其中1-31号为普通信号,33-64号为实时信号。

信号的产生:

1)键盘指令:如发送Ctrl+c 终止该进程,只能用于前台进程,后台进程需要通过 ps aux|grep 进程名   找到该进程,再用 kill -信号索引 进程号 终止进程。

2)系统函数调用:kill()abort();raise();

3)软件行为:alarm 

信号处理:

1)忽略

2)执行默认动作

3)执行自定义动作(catch 捕捉)

信号集存在于进程的PCB中,在PCB中有block(阻塞)、pending(未决)、handler(动作)三种信号集



 1 #include<stdio.h>
  2 #include<signal.h>
  3 void handler(int sig)
  4 {
  5     printf("this is %d sig...\n",sig);
  6 }
  7 void show(sigset_t *sig)
  8 {
  9     int i = 1;
 10     for(;i<32;++i)
 11     {
 12         if(sigismember(sig,i)==1)
 13         {
 14             printf("1");
 15         }
 16         else
 17             printf("0");
 18     }
 19     printf("\n");
 20 }
 21 int main()
 22 {
 23     sigset_t sig; //定义信号集
 24     sigset_t old_sig;  //为了恢复
 25     sigemptyset(&sig);  //初始化信号集
 26     sigemptyset(&old_sig);
 27     sigaddset(&sig,SIGINT);  //将SIGINT信号添加到信号集中
 28     sigprocmask(SIG_SETMASK,&sig,&old_sig);  //更改阻塞信号集(信号屏蔽字)
 29        //以上代码作用将信号SIGINT阻塞
 30     //signal(2,handler);  //自定义信号处理动作
 31     int count = 30;
 32     while(1)
 33     {
 34         sigset_t pending; 
 35         sigpending(&pending);  //获取进程pending信号集
 36         show(&pending);
 37         if(count--)
 38         {
 39             sigprocmask(SIG_SETMASK,&old_sig,NULL);  //恢复
 40         }
 41         sleep(1);
 42     }
 43     return 0;
 44 }
 // 信号为默认动作,因为2号信号被阻塞,所以发送2号信号,信号不会被递达。
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000
0100000000000000000000000000000

//信号为自定义动作  信号被递达
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
this is 2 sig...
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000


当向进程发送一个信号时,信号并不是立即被操作系统处理,而是等到合适的时间再处理,出了9号信号(SIGKILL)。

下面浅谈一下信号被操作系统处理的过程以及信号何时处理的。

1)首先操作系统由于中断、异常或函数调用从用户模式切换到内核模式。

2)然后内核处理完异常后就会检查进程的PCB中有没有处于未决的并且没有被阻塞的信号。

3)如果信号的动作是忽略,则从内核模式直接切换到用户模式执行异常之后的代码。如果是默认动作,  则执行完该动作再回到用户模式执行异常之后的代码,如果是自定义动作,则切回到用户模式执行信  号处理函数。

4)在用户模式执行完信号处理函数后,返回到内核模式。

5)如果没有新的信号需要处理,由内核模式切回到用户模式,执行异常之后的代码。

注意:信号处理函数和main函数使用的是不同的堆栈。所以必须由内核模式切换到用户模式执行信号处理    函数。

所以信号的处理是在操作系统由于异常等情况切换到内核模式处理完异常后去完成的。

pause:使进程挂起直到有信号递达。如果信号的处理方式是忽略,则进程继续被挂起。如果是默认动作,(一般都是终止进程),则进程终止;如果是自定义动作,则调用信号处理函数后pause出错返回(-1)。所以pause只有出错的返回值

   
  2 #include<signal.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 void handler(int sig)
  6 {
  7     printf("the %d signal is coming...\n",sig);
  8 }
  9 int main()
 10 {
 11     struct sigaction act; 
 12     struct sigaction old;
 13     act.sa_handler = handler;
 14     act.sa_flags = 0;
 15     sigemptyset(&act.sa_mask);
 16     memset(&old,'\0',sizeof(old));
 17     while(1)
 18     {
 19         sigaction(SIGINT,&act,&old);  //修改信号处理动作
 20         printf("i am sleeping...\n");
 21         pause(); //使进程挂起直到有信号产生
 22         sigaction(SIGINT,&old,NULL);
 23         sleep(1);
 24     }
 25     return 0;
 26 }
 
 结果
 [fbl@localhost sig_catch]$ ./my_sig 
i am sleeping...
^Cthe 2 signal is coming...  //当信号来了处理自定义动作
i am sleeping...
^Cthe 2 signal is coming...
i am sleeping...


用alarm()函数模拟实现sleep()函数。

alarm是订制闹钟,当alarm时间结束后,给进程发送SIGARM信号。

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 void handler(int sig)
  6 {
  7 }
  8 int my_sleep( int sec)
  9 {
 10     sigset_t sig,old_sig;
 11     struct sigaction act;
 12     struct sigaction old;
 13     act.sa_handler = handler;
 14     act.sa_flags = 0;
 15     sigemptyset(&act.sa_mask);
 16     memset(&old,'\0',sizeof(old));
 17     sigaction(SIGALRM,&act,&old);
 18     //屏蔽SIGALRM信号
 19     sigemptyset(&sig);
 20     sigaddset(&sig,SIGALRM);
 21     sigprocmask(SIG_BLOCK,&sig,&old_sig);
 22     
 23     alarm(sec);
 24     sigdelset(&sig,SIGALRM); 
 25     sigsuspend(&sig);  //pause();
 26     int ret = alarm(0);
 27     sigaction(SIGINT,&old,NULL);
 28     return ret;
 29 }
 30 int main()
 31 {
 32     while(1)
 33     {
 34         printf("i am sleeping...\n");
 35         my_sleep(3);
 36     }
 37     return 0;
 38 }
 
 [fbl@localhost sig_catch]$ ./my_sig 
i am sleeping...
i am sleeping...
i am sleeping...
^Ci am sleeping...
i am sleeping...
^\Quit (core dumped)


上面的程序可以用pause()来引发信号的处理,也可以用函数sigsuspend()来代替pause()。但是使用pause()会引发一些问题。

因为我们预期的指令执行的顺序是在alarm(sec)秒之内pause收到信号,这样程序能正常运行。

但程序有时候并不向我们希望的方向发展,假如有多个进程执行,而这个进程的优先级很低,由于系统调度方法的原因,有可能执行到alarm()函数时来了个优先级高的进程,这时这个进程就会被切换出去,让优先级高的进程先运行,当此进程再次被切换回来时,已经超过了alarm设置的时间,pause就不会接收到SIGALRM信号,进程就会一直被挂起。

竟态条件(竟态危害):事件由于不正当的顺序而导致的不可预知的错误,最终的结果取决于事件间的排列顺序。(上述例子就出现了竟态条件)

处理的方法就是在调用pause之前屏蔽SIGALRM信号,可以用函数sigsuspend来处理,sigsuspend函数的功能是:解除信号屏蔽并且挂起进程等待信号递达。