04-UNIX信号模型如何映射到线程中
33.2.1 UNIX信号模型如何映射到线程中
要了解UNIX信号如何映射到Pthreads模型,就需要了解,信号模型的哪些方面属于进程层面(由进程中的所有线程所共享),哪些方面是属于进程中的单个线程层面。如下是对其关键点的汇总。
- 信号动作属于进程层面。如果某进程的任一线程收到任何未经(特殊)处理的信号,且其缺省动作为stop或terminate,那么将停止或者终止该进程的所有线程。
- 对信号的处置属于进程层面,进程中的所有线程共享对每个信号的处置设置。如果某一线程使用函数sigaction()为某类信号(比如,SIGINT)创建了处理函数,那么当收到SIGINT时,任何线程都会去调用该处理函数。与之类似,如果将对信号的处置设置为忽略(ignore),那么所有线程都会忽略该信号。
- 信号的发送既可针对整个进程,也可针对某个特定线程。满足如下三者之一的信号当属面向线程的。
- 信号的产生源于线程上下文中对特定硬件指令的执行(即22.4节所描述的硬件异常:SIGBUS、SIGFPE、SIGILL和SIGSEGV)。
- 当线程试图对已断开的(broken pipe)管道进行写操作时所产生的SIGPIPE信号。
- 由函数pthread_kill()或pthread_sigqueue()所发出的信号,这些函数(由33.2.3节描述)允许线程向同一进程下的其他线程发送信号。
- 由其他机制产生的所有信号都是面向进程的。例如,其他进程通过调用kill()或者sigqueue()所发送的信号;用户键入特殊的终端字符所产生的信号,诸如SIGINT和SIGTSTP;还有一些信号由软件事件产生,例如终端窗口大小的调整(SIGWINCH)或者定时器到期(例如,SIGALRM)。
- 当多线程程序收到一个信号,且该进程已然为此信号创建了信号处理程序时,内核会任选一条线程来接收这一信号,并在该线程中调用信号处理程序对其进行处理。这种行为与信号的原始语义保持了一致。让进程针对单个信号重复处理多次是没有意义的。
- 信号掩码(mask)是针对每个线程而言。(对于多线程程序来说,并不存在一个作用于整个进程范围的信号掩码,可以管理所有线程。)使用Pthreads API所定义的函数pthread_sigmask(),各线程可独立阻止或放行各种信号。通过操作每个线程的信号掩码,应用程序可以控制哪些线程可以处理进程收到的信号。
- 针对为整个进程所挂起(pending)的信号,以及为每条线程所挂起的信号,内核都分别维护有记录。调用函数sigpending()会返回为整个进程和当前线程所挂起信号的并集。在新创建的线程中,每线程的挂起信号集合初始时为空。可将一个针对线程的信号仅向目标线程投送。如果该信号遭线程阻塞,那么它会一直保持挂起,直至线程将其放行(或者线程终止)。
- 如果信号处理程序中断了对pthread_mutex_lock()的调用,那么该调用总是会自动重新开始。如果一个信号处理函数中断了对pthread_cond_wait()的调用,则该调用要么自动重新开始(Linux就是如此),要么返回 0,表示遭遇了假唤醒(如30.2.3节所述,此时,设计良好的应用程序会重新检查相应的判断条件并重新发起调用)。SUSv3对这两个函数的行为要求与此处的描述一致。
- 备选信号栈是每线程特有的(参考21.3节对函数sigaltstack()的描述)。新创建的线程并不从创建者处继承备选信号栈。
更确切地说,SUSv3规定每个内核调度实体(KSE)都有一个单独的备选信号栈。在按1:1比例实现线程的系统中,例如Linux,每一个线程对应一个KSE(见33.4节)。