Linux的进程间通信(信号)
信号的本质
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。异步意味着我们不需要创建单独的线程来接收信号, 信号在软件层次上是对中断机制的一种模拟。
进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息(基于POSIX标准)。
进程对信号的处理
收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理, 进程通过系统调用signal来指定进程对某个信号的处理行为。
第二种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。
第三种方法是,执行操作系统的缺省动作。
当进程执行一个信号处理程序的函数时,通常屏蔽相应的信号,即自动阻塞这个信号,直到处理程序结束。因此,所处理的信号的另一次出现,并不能中断信号处理程序,所以信号处理函数不必是可以重入的。但是默认情况下当前信号执行的时候, 并不会屏蔽其它的信号, 如果需要屏蔽其它信号, 可以调用sigaction()函数来设置需要block的信号.
内核中关于信号处理的关键流程
内核发送信号的流程
Sys_kill()->do_tkill()->do_send_specific()->do_send_sig_info()->send_signal()->complete_signal()->signal_wake_up()
signal_wake_up()首先为进程设置TIF_SIGPENDING标志,说明该进程有延迟的信号要等待处理。然后再调用wake_up_state()唤醒目标进程,如果目标进程在其他的CPU上运行,wake_up_state()将返回0,此时调用kick_process()向该CPU发送一个处理器间中断, 加速触发目标进程对信号的处理.
进程处理信号的流程
当进程被调度时,会调用do_notify_resume()来处理信号队列中的信号。_TIF_SIGPENDING标志是在signal_wake_up()函数中设置的,检查该标志后,接下来就调用do_signal()函数来处理信号. Do_signal()中会通过user_mode来判断当前进程是否将要回到用户态, 如果是由调用handle_signal()来处理信号. 信号处理程序执行完毕之后,进程会主动调用sys_sigreturn ()系统调用再次回到内核, 然后恢复进程的用户态堆栈, 进程便可以返回用户态继续执行了.
总结
信号是异步的.
每个线程都可能接收到信号, 取决于它的掩码, 每个线程都有自己的掩码.
信号在进程上下文中被处理, 内核会在系统调用返回的时候, 处理pending的信号. 如果用户定义了信号的关联函数,内核则会强暴进程的用户态堆栈(由于信号处理函数在用户空间), 让它自觉的处理信号处理函数.
信号在软件层次上是对中断机制的一种模拟. 但是由于有用户态回调函数的原因, 信号的处理流程比中断更加复杂, 因为有更多的内核-用户态切换, 还要准备和恢复栈帧.