【发布时间】:2011-05-11 14:47:08
【问题描述】:
信号在 unix 中是如何工作的?我通过 W.R. Stevens 但无法理解。请帮帮我。
【问题讨论】:
信号在 unix 中是如何工作的?我通过 W.R. Stevens 但无法理解。请帮帮我。
【问题讨论】:
信号只不过是进程执行中的一个中断。一个进程可以给自己发信号,也可以将一个信号传递给另一个进程。也许父母可以向其孩子发送信号以终止它,等等。
查看以下链接了解。
https://unix.stackexchange.com/questions/80044/how-signals-work-internally
http://www.linuxjournal.com/article/3985
http://www.linuxprogrammingblog.com/all-about-linux-signals?page=show
【讨论】:
将信号设施视为由操作系统(而不是硬件)实现的中断。
当您的程序愉快地遍历其根植于 main() 的执行轨迹时,可能会发生这些中断,导致程序被分派到一个向量(处理程序),在那里运行代码,然后返回到它得到的位置被打断了。
这些中断(信号)可以来自多种来源,例如硬件错误,例如访问错误或未对齐的地址、子进程死亡、用户使用kill 命令生成的信号,或来自其他进程使用kill 系统调用的信号。使用信号的方式是为它们指定处理程序,这些处理程序在信号发生时由操作系统调度。请注意,其中一些信号无法处理,并导致进程直接死亡。
但那些可以处理的,可能非常有用。您可以将它们用于进程间通信,即一个进程向另一个进程发送信号,另一个进程处理它,并在处理程序中做一些有用的事情。如果您向它们发送正确的信号,许多守护进程会做一些有用的事情,例如重新读取配置文件。
【讨论】:
上述所有陈述中未解决的一些问题是多核、在接收信号时在内核空间中运行、在接收信号时在内核空间中休眠、系统调用重启和信号处理程序延迟。
这里有几个问题需要考虑:
答案:
siginterrupt(2) 系统调用来控制要中断系统调用的信号以及不希望中断系统调用的信号。当您使用带有SA_RESTART 标志的sigaction(2) 注册信号时,您可以决定是否要为某个信号重新启动系统调用。如果发出系统调用并被信号切断并且没有自动重新启动,您将获得EINTR(中断)返回值,您必须处理该值。您还可以查看restart_syscall(2) 系统调用了解更多详情。关于这一切为何如此复杂的几点说明:
kill(2) 系统调用的信号等等)。那么信号处理的延迟是多少?
【讨论】:
下面的解释并不准确,它的工作方式在不同系统之间的几个方面有所不同(甚至可能在某些部分的不同硬件上使用相同的操作系统),但我认为它通常很好 足够满足你的好奇心来使用它们。大多数人开始在编程中使用信号时甚至没有这种程度的理解,但在我习惯使用它们之前,我想了解它们。
操作系统内核有一个称为进程控制块的数据结构,用于每个运行的进程,其中包含有关该进程的数据。这可以通过进程 ID (PID) 进行查找,并包含一个信号操作和待处理信号表。
当一个信号被发送到一个进程时,操作系统内核将查找该进程的进程控制块并检查信号动作表来定位正在发送的特定信号的动作。如果信号动作值为SIG_IGN,则内核会忘记新信号。如果信号动作值为SIG_DFL,那么内核会在另一个表中查找该信号的默认信号处理动作并执行该动作。如果值是其他任何值,则假定它是进程中的函数地址,信号被发送到应该被调用的进程。 SIG_IGN 和 SIG_DFL 的值是转换为函数指针的数字,其值不是进程地址空间内的有效地址(例如 0 和 1,它们都在第 0 页中,从不映射到进程中)。
如果进程注册了一个信号处理函数(信号动作值既不是 SIG_IGN 也不是 SIG_DFL),则在挂起的信号表中为该信号创建一个条目,并且该进程被标记为准备好运行(它可能有一直在等待某些事情,例如数据可用于调用 read、等待信号或其他一些事情)。
现在,下一次运行该进程时,操作系统内核将首先将一些数据添加到堆栈中并更改该进程的指令指针,使其看起来几乎就像该进程本身刚刚调用了信号处理程序一样。这并不完全正确,实际上与实际发生的情况有很大的偏差,我稍后再谈。
信号处理函数可以做它所做的任何事情(它是代表它被调用的进程的一部分,所以它是在了解程序应该如何处理该信号的情况下编写的)。当信号处理程序返回时,该进程的常规代码再次开始执行。 (再次,不准确,但更多关于下一个)
好的,以上内容应该已经让您很好地了解了信号是如何传递给进程的。我认为需要这个pretty good idea 版本才能掌握完整的想法,其中包括一些更复杂的东西。
操作系统内核通常需要知道信号处理程序何时返回。这是因为信号处理程序需要一个参数(这可能需要堆栈空间),您可以在信号处理程序执行期间阻止同一信号被传递两次,和/或在传递信号后重新启动系统调用。要完成这一点,除了堆栈和指令指针的变化之外。
必须发生的是内核需要让进程告诉它它已经完成了信号处理函数的执行。这可以通过将一段 RAM 映射到进程的地址空间来完成,该地址空间包含进行此系统调用的代码,并使信号处理函数的返回地址(该函数开始运行时堆栈上的顶部值)为这段代码。我认为这就是它在 Linux 中的完成方式(至少是较新的版本)。实现这一点的另一种方法(我不知道这是否完成,但它可能会这样做)将信号处理函数的返回地址设为无效地址(例如 NULL),这将在大多数系统上导致中断,这将再次给予操作系统内核控制权。这如何发生并不重要,但内核必须再次获得控制权以修复堆栈并知道信号处理程序已完成。
Linux 内核确实为此将一个页面映射到进程中,但是用于注册信号处理程序的实际系统调用(什么 sigaction 调用 )需要一个参数 sa_restore 参数,该地址是应该用作信号处理程序的返回地址,内核只是确保它放在那里。该地址的代码发出I'm done 系统调用(sigreturn),内核知道信号处理程序已经完成。
我主要假设您首先知道信号是如何产生的。由于某些事情发生,操作系统可以代表进程生成它们,例如计时器到期、子进程死亡、访问不应访问的内存或发出不应访问的指令(不存在的指令)或享有特权的人),或许多其他事情。计时器情况在功能上与其他情况略有不同,因为它可能在进程未运行时发生,因此更像是通过kill 系统调用发送的信号。对于代表当前进程发送的与定时器无关的信号,这些信号是在发生中断时生成的,因为当前进程做错了。这个中断给了内核控制权(就像一个系统调用一样),内核生成要传递给当前进程的信号。
【讨论】:
strace、阅读代码并最终编写一个从从未发送过的信号返回的程序了解到这一点。