Skip to content

Latest commit

 

History

History
71 lines (43 loc) · 7.1 KB

Signal.md

File metadata and controls

71 lines (43 loc) · 7.1 KB

信号驱动的异步I/O是指一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态。异步 I/O 和硬件上常提的中断的概念类似,信号是在软件层次上对中断机制的一种模拟。

信号

软中断信号(signal,又简称为信号)是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

进程之间可以互相通过系统调用kill发送软中断信号,内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

收到信号的进程对各种信号有不同的处理方法,主要分为以下三类:

  1. 类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。进程通过系统调用signal来指定进程对某个信号的处理行为。
  2. 忽略某个信号,对该信号不做任何处理,就象未发生过一样。
  3. 对该信号的处理保留系统的默认值,对大部分的信号的缺省操作是使得进程终止。

从可靠性方面信号分为可靠信号与不可靠信号;信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。信号值位于 SIGRTMIN 和 SIGRTMAX 之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。

linux 下信号的生命周期如下:

  • 在目的进程中安装该信号。即设置捕获该信号时进程执行的操作,采用signal 或者 sigaction 系统调用来实现。
  • 信号被某个进程产生,同时设置该信号的目的进程(使用pid),之后交给操作系统进行管理。采用kill()、arise()、alarm()等系统调用来实现。
  • 信号在目的进程被注册。就是把信号值加入到进程的PCB(task_struct)中相关的数据结构里——未决信号的数据成员,信号携带的其他信息被保留到未决信的队列的某个sigqueue结构中。
  • 信号在进程中注销。在执行信号处理函数前,要把信号在进程中注销。
  • 信号生命的终结。进程终止当前的工作,保护上下文,执行信号处理函数,之后恢复。

信号实现机制

发送信号

内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。

进程的 PCB 中有关于本进程中未决信号的数据成员 struct sigpending pending:

struct sigpending{
	struct sigqueue *head, *tail;
	sigset_t signal;
};

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

struct sigqueue{
	struct sigqueue *next;
	siginfo_t info;
}

信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该进程被信号阻塞。

当一个可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失。这意味着同一个可靠信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个可靠信号,都会为它分配一个结构来注册该信号信息,并把该结构添加在未决信号链尾)。

当一个非可靠信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。

总之信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。

处理信号

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。

内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当进程接收到一个它忽略的信号时,进程丢弃该信号,就像没有收到该信号似的继续运行。

如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。**而且执行用户定义的函数的方法很巧妙,内核在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。**这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。

对于非可靠信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。

当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。

更多阅读

linux内核中异步通知机制--信号处理机制
Linux信号机制与信号处理
Linux环境进程间通信(二): 信号(上)
Linux环境进程间通信(二): 信号(下)