您的位置:首页 > 运维架构 > Linux

Linux-信号机制详解(二)

2016-07-18 00:05 225 查看
       前面的详解(一)已经详细讨论了信号的种类,信号产生的条件和信号的阻塞信号,基本上已经对信号的处理方式有了一定的了解。今天我们继续了解信号的其他内容。

四。捕捉信号

之前的信号的处理中有提到过捕捉信号,就是通过调用自定义的函数处理信号

信号捕捉举例:

1. ⽤户程序注册了SIGQUIT信号的处理函数sighandler。

2. 当前正在执⾏main函数,这时发⽣中断或异常切换到内核态。

3. 在中断处理完毕后要返回⽤户态的main函数之前检查到有信号SIGQUIT递达。

4. 内核决定返回⽤户态后不是恢复main函数的上下⽂继续执⾏,⽽是执⾏sighandler函

数,sighandler和main函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,

是 两个独⽴的控制流程。

5. sighandler函数返回后⾃动执⾏特殊的系统调⽤sigreturn再次进⼊内核态。

6. 如果没有新的信号要递达,这次再返回⽤户态就是恢复main函数的上下⽂继续执⾏了。

下面是一些信号处理的函数使用方法:

(一)sigaction函数

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct
sigaction *oact);</span>

      sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤成功则返回0,出错则返回- 1。 signo是指定信号的编号。若act指针⾮空,则根据act修改该信号的处理动作。

若oact指针⾮ 空,则通过oact传出该信号原来的处理动作。 
       当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么 它会被阻塞到当前处理结束为⽌。如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。

(二)pause函数

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <unistd.h>
int pause(void);</span>

       pause函数使调⽤进程挂起直到有信号递达。如果信号的处理动作是终⽌进程,则进程终⽌,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调⽤了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值(想想以前还学过什么函数只有出错返回值?)。错误码EINTR表 ⽰“被信号中断”。

(三)可重入函数

       当捕捉到信号时,不论进程的主控制流程当前执⾏到哪⼉,都会先跳到信号处理函数中执⾏,从信号处理函数返回后再继续执⾏主控制流程。信号处理函数是⼀个单独的控制流程,因为它和主控制流程是异步的,⼆者不存在调⽤和被调⽤的关系,并且使⽤不同的堆栈空间。引⼊了信号处理函数使得⼀个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突。

如图:



       main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的 时候,因为硬件中断使进程切换到内核,再次回⽤户态之前检查到有信号待处理,于

是切换 到sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的 两步都做完之后从sighandler返回内核态,再次回到⽤户态就从main函数调⽤的insert函数中继续 往下执⾏,先前做第⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后 向链表中插⼊两个节点,⽽最后只有⼀个节点真正插⼊链表中了。

     像这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函 数,这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为 不可重⼊函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数,则称为可重⼊(Reentrant) 函数。

(四)sig_atomic_t类型与volatile限定符

       在上⾯的例⼦中,main和sighandler都调⽤insert函数则有可能出现链表的错乱,其根本原因在 于,对全局链表的插⼊操作要分两步完成,不是⼀个原⼦操作,假如这两步操作必定会⼀起做完, 中间不可能被打断,就不会出现错乱了。

     对于程序中存在多个执⾏流程访问同⼀全局变量的情况,volatile限定符是必要的,此外,虽然程 序只有单⼀的执⾏流程,但是变量属于以下情况之⼀的,也需要volatile限定:

1. 变量的内存单元中的数据不需要写操作就可以⾃⼰发⽣变化,每次读上来的值都可能不⼀样

2. 即使多次向变量的内存单元中写数据,只写不读,也并不是在做⽆⽤功,⽽是有特殊意义的什么样的内存单元会具有这样的特性呢?肯定不是普通的内存,⽽是映射到内存地址空间的硬件寄存器,例如串⼜的接收寄存器属于上述第⼀种情况,⽽发送寄存器属于上述第⼆种情况。sig_atomic_t类型的变量应该总是加上volatile限定符,因为要使⽤sig_atomic_t类型的理由也正 是要加volatile限定符的理由。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  信号 linux