内核内实现信号的捕捉
1.main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
2. 调用alarm(seconds)设定闹钟。
3. 调用pause等待,内核切换到别的进程运行。
4. seconds秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是handler。
6. 切换到用户态执行handler函数,进入handler函数时SIGALRM信号被自动屏蔽,从handler函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。
需要注意的是虽然handler函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到seconds而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。
普通版本的mysleep存在一个问题:pause函数使调用进程挂起知道捕捉到一个信号,在调用alam和pause之间有个竞态条件,在一个繁忙的系统,alrm调用pause之前可能超时,并调用了信号处理程序。如果发生这种情况,则在调用pause之后,如果没有捕捉到其它信号,该调用者会被永远挂起。
1. 编写普通版本mysleep
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发 SIGALRM信号, 该信号的默认处理动作是终⽌止当前进程。这个函数的返回值是0或者是以前 设定的闹钟时间还余下 的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分 钟后被人吵醒了,还想多睡 一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间 还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍 然是以前设定的闹钟时间还余下的秒 数
pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终 止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause 不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为 EINTR, 所以pause只有出错的返回值。错误码 EINTR表示“被信号中断”。
sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤用成功则返回0,出错则 返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结 构体:
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL 表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册 了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信 号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main 函数调⽤用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函 数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号 屏蔽字。
sa_flags字段包含一些选项,代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,
运行结果
2. 规避竞态条件的mysleep
系统运行的时序(Timing)并不像我们写程序时所设想的那样。 虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用 alarm(nsecs)之后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。
解决该问题的方法需要将“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作,而sigsuspend函数刚好有这个功能。 sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause
和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后 sigsuspend才返回,返回值为-1,errno设置为EINTR。
调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时 解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
要点
如果在调用mysleep函数时SIGALRM信号没有屏蔽:
1.调用sigprocmask(SIG_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。
2.调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。
3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
4.调用sigprocmask(SIG_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的 屏蔽。
BIT
BIT