天天看点

[linux c/c++] linux编程中的信号

前言:

用户通过 signal 和 sigaction 向内核注册自己的信号处理函数,当内核达到某个条件或者用户自行触发某个信号时,进程会遍历自己所有线程的信号屏蔽字(每个线程有自己的信号屏蔽字),从主线程找起,找到第一个不屏蔽此信号的线程,然后打断这个线程的执行,执行完毕后恢复线程的执行。

多线程场景下需要注意的点:

1)信号是发送给进程的,进程收到信号后,会编译自己所有线程的信号屏蔽字,从自己开始,找到第一个不屏蔽此信号的线程,然后打断;

2)创建线程时,子线程会继承父线程的信号屏蔽字,因此可以设计一个应用场景,让某一个线程接收所有信号,而其他线程屏蔽所有信号,这样这个线程就可以以最小的轮训周期来响应信号,认为这个线程时专门的信号处理线程;

3)如果子线程中重新修改信号处理函数,那么会影响所有其他线程,即信号处理函数是进程层面的,每个线程无法设置自己的信号处理函数。

其他需要注意的点:

1)进程在执行exec后,会把原先注册了的信号处理函数都复位,还原成默认或者忽略(程序上下文都变了,信号处理函数跟着变也是情理之中);

2)signal函数应当禁止使用,因为其不是标准实现,每个系统都有自己的实现,而且使用风险极大;

3)fork动作会继承父进程的信号处理函数;

4)早期的信号处理机制,在收到信号并进入信号处理函数之前,内核会先把信号的处理函数设置为默认,而某些信号的默认处理方式是终止进程,那么在“信号处理函数中把信号重新注册”和“内核自动置默认”这之间,就有可能因为在此触发信号而导致进行了默认动作。(内核之前由于存在自动设置的机制在,所以在编写信号处理函数的时候,会在刚进函数的时候就把信号处理函数再注册一遍)

5)慢速系统调用 与 中断,如果系统停在一个慢速系统调用中,此时有一个信号发生了,那么此系统调用会返回一个错误码EINTR,表示自己在执行期间被一个信号中断了,那么程序应当在此调用此系统调用。

6)对于#5,因为存在上述问题,所以又如下编程范式:

again:
        if((n=read(fd,n,buff))<0)
          {
            if(errno=EINTR)
              goto again;
            {other wrong dealing}
          }      

针对如上会被中断打断的系统调用情况,内核提供了自动恢复机制,这样就不用处理EINTR错误码,内核在检测到系统调用错误后,首先会检查错误码是不是EINTR,如果是就继续系统调用,这样用户就不用处理EINTR了。但是有些时候,就是想处理这些错误码,那么可以再sigaction中设置,把这个机制打开或者关闭。这里的系统调用指慢速系统调用,比如read,write,ioctl,wait,waitpid,readv,writev。(以后尽量使用sigaction而不是signal)

7)注意:在进入信号处理函数之前,千万要先保存errno的值,因为在信号处理函数中,这个值可能会被改变。

   设置的方法:进入终端处理函数就把这个值赋值给一个局部变量,在离开终端处理函数之前再把这个值赋值给errno

8)什么是“可重入函数”?

进入中断处理函数后,使用这些函数,不会导致系统的数据区发生改变的函数。比如malloc就不行,如果在进中断之前做了malloc,然后这个函数刚执行一半,被一个中断插进来,而且又在中断中使用了malloc,那么从中断中出来后,外面的malloc将会失败。   有一个可重入函数的列表,大致总结下就是,凡是使用了malloc和free的,全是不可重入函数,非系统调用的I/O函数基本上全是不可重入(因为他们肯定会使用malloc和free)

9)信号的产生:硬件异常、软件条件(比如alarm)、终端产生、主动递送(比如kill)

     信号的递送:信号产生以后,内核会搜索进程表,在其中找到信号的归属,然后在进程控制表(pct)中相应的标志位置位,置位成功后,就是递送完成之时。

在信号的 “产生” 到 “递送(完成)” 之间,这段时间信号是  "未决的"。进程可选择(可能需要调用内核的某个设置函数)“阻塞信号递送”,即在“未决”时间段内,先不注册信号处理方式(默认的/信号处理函数,这里不包含忽略,可见忽略的情况提前会被阻挡掉),等到信号不阻塞了(可以被处理了),在决定处理方式。可使用sigpending来查看哪些信号处于“阻塞未决状态”。

10)信号的排队

对于非实时信号,相同信号不能在信号队列中排队,也就是说多个相同信号会被合并成一个,因此会被认为只发生了一次;

对于实时信号,相同信号可以在信号队列中排队,多个信号不会被合并成一个,会被认为发生了多次。

如果信号队列中有多个实时以及非实时信号排队,实时信号并不会先于非实时信号被取出,信号数字小的会先被取出:如 SIGUSR1(10)会先于 SIGUSR2 (12),SIGRTMIN(34)会先于 SIGRTMAX (64), 非实时信号因为其信号数字小而先于实时信号被取出。

11)kill命令发送信号

     kill  pid>0 发给pid进程

           pid==0 发送给当前进程所在进程组的所有进程

           pid<0  发送给当前进程所在进程组中进程号为 -pid 的进程

           pid==-1  看不懂

继续阅读