《Linux原理与结构 作者 郭玉东 全书 第10章》由会员分享,可在线阅读,更多相关《Linux原理与结构 作者 郭玉东 全书 第10章(155页珍藏版)》请在金锄头文库上搜索。
1、1第十章 进 程 间 通 信 10.1 信号 10.2 管道 10.3 消息队列 10.4 共享内存2在线教务辅导网:http:/教材其余课件及动画素材请查阅在线教务辅导网QQ:349134187 或者直接输入下面地址:http:/3利用锁、信号量、RCU等可以协调进程的动作,实现进程间的互斥与同步,但所传递的信息量极少,难以用于进程间通信。为了使相互协作的进程能够更好地工作,除了互斥与同步之外,还需要提供一些进程间的通信机制,如通知机制、信息传递机制、信息共享机制等。早期的Unix仅提供了两种进程间通信机制,分别是用于通知的信号和用于传递数据的管道。1983年,AT/ 信号处理程序unsig
2、ned longsa_flags;/ 信号的特殊处理需求20_sigrestore_tsa_restorer;/ 善后处理程序sigset_tsa_mask;/ 处理信号时的新增阻塞位;struct k_sigaction struct sigactionsa;struct sighand_struct 21atomic_tcount;/ 引用计数struct k_sigactionaction_NSIG;/ 信号处理程序列表,_NSIG等于64spinlock_tsiglock;/ 保护用自旋锁wait_queue_head_tsignalfd_wqh;/ 等待接收该信号的进程队列;22第0
3、号进程的sighand_struct结构是静态建立的,它的所有信号处理程序都是缺省(SIG_DFL)的。其它进程的sighand_struct结构都是动态建立的。在创建之初,进程要么与创建者共用同一个sighand_struct结构,要么从创建者复制一个sighand_struct结构,因而在创建之初,进程处理信号的方式与创建者进程完全相同。23在为进程加载新的执行程序之前,加载程序会为进程建立 独立的sighand_struct结构,并对其进行清理。在清理后的sighand_struct结构中,用户自定义的所有处理程序都被换成了缺省的SIG_DFL,所有的sa_mask和sa_flags也都
4、被清空。运行中的进程可以通过系统调用(如signal()、sigaction()、rt_sigaction()等)更改自己对各信号的处理方式,包括注册自己定义的信号处理程序。函数signal()是较老的一个系统调用(即将被淘汰),仅能设置信号处理程序。函数sigaction()可以设置一个信号的处理程序及阻塞掩码、特殊标志等,但由于结构的 变化,也将被淘汰。函数rt_sigaction()是目前建议的系统调用,可用于设置一个信号处理的所有部分。24进程设置的信号处理程序可以是缺省(SIG_DFL)、忽略(SIG_IGN)或自定义的函数。如果进程设置的信号处理程序是一个自定义的用户空间函数,那么
5、sigaction结构中的sa_handler将指向该函数。如果进程将某信号的处理程序换成了忽略,那 么它此前收到的该种信号会被清除。由于信号SIGCONT、SIGCHLD、SIGWINCH和SIGURG的缺省处理是忽略,因而若进程将这四个信号的处理程序换成了缺省,那么它此前收到的这些信号也会被清除。SIGKILL和SIGSTOP是内核专用的信号,它们的处理方式不能被更改。系统调用sigprocmask()专门用于设置或获取进程的阻塞掩码blocked。2510.1.4 信号发送与其它形式的通信机制不同,信号是由发送者直接送给接收者的,接收者不需要采取任何接收动作。也就是说,信号的发送和接收都
6、是由发送者进程负责的,操作系统只需要提供信号的发送操作即可。发送信号的进程必须在运行状态,但接收信号的进程却可以在任意状态。处于可中断等待状态或停止状态的进程可能会被收到的信号唤醒。在早期的Unix系统中,发送信号实际就是在接收者进程的signal位图中设置一个标志。信号的接收者只知道收到了某类信号,却不知道信号的来源和数量。26在Linux的早期实现中,实时信号可以带一个附加信息,这些附加信息被挂在接收者进程的信号队列中,因而实时信号有了 数量的概念。新版本的Linux给普通信号也准备了附加信息,可用于通报信号的来源,但为了与老版本兼容,普通信号仍没有数量的概念。信号的附加信息被包装在结构s
7、igqueue中,其中内嵌的结构siginfo用于描述附加信息本身,主要内容如下:(1) 域si_signo中记录着信号的编码,从1开始。(2) 域si_code中记录着信号的来源,如内核、用户、定时器等。27(3) 联合_sifields中记录着各信号特定的附加信息,如发送者的pid、uid等。信号的接收者可以是一个特定的进程,也可以是一个线程组。作为线程组所收到的信号可以被组中的所有进程看到,也可以被组中的任意一个进程处理,但只能被处理一次。不管被哪个进程处理,作为线程组收到的信号都会影响到组中所有的进程。发送信号的进程至少需要提供四个参数,分别是信号编号 、附加信息、接收者进程、目标类别
8、(线程组或单个进程)等。发送信号时完成的工作主要有如下几件:(1) 解决停止与继续信号的矛盾问题,防止它们在接收者进程中共存。28 如果要发送的是停止信号(SIGSTOP、SIGTSTP、SIGTTOU、SIGTTIN),则应将接收者及其线程组此前已收到的继续信号全部清除。 如果要发送的是继续信号(SIGCONT),则应将接收者及其线程组此前已收到的停止信号全部清除,并唤醒接收者线程组中所有停止态的进程。(2) 丢弃接收者进程要忽略的信号。在接收者进程中,处理程序为SIG_IGN的信号是被忽略的,处理程序为SIG_DFL的SIGCHLD、SIGCONT、SIGURG或SIGWINCH信号也是被
9、忽略的。接收者进程当前阻塞的信号不能被忽略。29(3) 确定新信号将要进驻的接收队列。一个信号只能被加入到一个队列中,信号所在队列由参数中的目标类别决定,可 能是task_struct结构中的pending或signal_struct结构中的shared_pending队列。(4) 丢弃重复收到的普通信号。根据传统的信号语义,不需要记录普通信号的接收次数。因而,如果要发送的普通信号已在选定的接收队列中,可将其直接丢弃。(5) 发送信号。 向对象内存管理器申请一个空闲的sigqueue结构,用发送者提供的参数填写该结构,并将其挂在接收队列的队尾。为 了防止拒绝服务类攻击,Linux限制了一个进程
10、可用的sigqueue结构数。30 如果有等待该信号的进程(signalfd_wqh队列不空),则将其中的一个唤醒。 将信号在队列位图signal中的标志位置1,表示收到了该信号。(6) 善后处理。 如果接收者是单个进程,在下列情况下,不需要特别的善后处理: 信号正被接收者阻塞。 接收者进程正处于停止状态。 接收者进程当前未在处理器上运行且其上已有待处理的信号。31 如果接收者是一个线程组,则在其中选一个满足下列条件之一的进程用来处理该信号: 未阻塞该信号且正在运行的进程。 未阻塞该信号、不在停止状态且无其它待处理信号的进程。如果组中的所有进程都不能满足上述条件,则不需特别处理。32 如果信号
11、(包括SIGKILL)对接收者是致命的(处理动作是终止进程),则向线程组中的所有进程发送SIGKILL信号,并将处于可中断等待状态或醒后终止状态的进程全部唤醒,以便让它们尽快终止。 如果信号对接收者不是致命的但为其选定的处理者进程处于可中断等待状态,则将其唤醒。大部分信号都是由内核发送给进程或线程组的,只有一小部分信号是在进程之间互相发送的。系统会对进程之间互相发 送的信号进行严格的权限检查。Linux提供了多个系统调用以便于在进程之间互发信号,主要有以下几个:33(1) 函数kill()。在未引入线程组时,该函数用于向一个进程或进程组发送信号。在引入线程组之后,该函数的意义取决 于接收者进程
12、的pid: pid0,用于向一个线程组发送信号,pid是接收者线程组的ID号。 pid=0,用于向发送者进程所在的线程组发送信号。 pid=-1,用于向当前线程组之外的所有其它线程组发送信号。 pid-1,用于向进程组中的所有进程发送信号,-pid是进程组的ID号。34(2) 函数tgkill()用于向一个特定的进程(由TGID和PID标识)发送信号。(3) 函数tkill()是tgkill()的特例,仅指定了PID而未指定TGID,接收者进程可位于任意一个线程组中。(4) 函数rt_sigqueueinfo()用于向一个线程组发送一个带附加信息的信号。(5) 函数rt_tgsigqueuei
13、nfo()用于向一个特定的进程发送一个带附加信息的信号。3510.1.5 信号处理进程可以在任何状态下接收信号,但只能在从核心态返回 用户态之前处理信号,见图4.3。也就是说,信号是由接收者进程在特定时刻自己处理的,有可能不够及时。由于内核守护线程不会返回用户态,因而它们不会处理信号。进程先处理发给自己的信号(在task_struct结构的pending队列中),再处理发给线程组的信号(在signal_struct结构的shared_pending队列中)。但进程并不按到达的顺序处理信号。一般情况下,进程会按编号从小到大的顺序处理队列中未被阻塞的信号。36然而有些信号是由进程在执行过程中的异常
14、操作引起的,属于 同步信号,如SIGSEGV、SIGBUS、SIGILL、SIGTRAP、SIGFPE等,应该优先处理。对同一种信号来说,先收到的先被处理。处理过的信号会被从队列中摘除。当队列中的一种信 号被彻底处理完之后,它在位图signal中的标志也会被清除。进程处理信号的方法由它的sighand_struct结构决定。如果信号的处理程序是忽略(SIG_IGN),那么简单地将其丢弃即可。37如果信号的处理程序是缺省(SIG_DFL),那么由内核处理即可。内核对不同信号的缺省处理方法也不同,大致可分为如下几类:(1) 忽略。信号SIGCONT、SIGCHLD、SIGWINCH和SIGURG的
15、缺省处理动作是忽略,即将信号直接丢弃。(2) 停止。信号SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU的缺省处理动作是停止,即将进程所在线程组中的所有进程(包括自己)都设为停止状态。(3) 终止。其它信号的缺省处理动作都是终止,即终止进程所在线程组中的所有进程(包括自己)。终止线程组中其它进程的方法是向它们发送SIGKILL信号,终止自己的方法是执行函数exit()。38如果信号的处理程序是用户自定义的,则需要进入用户空间执行一次这一用户态处理程序。由于进程还未返回到用户空 间,按照Intel处理器的约定,不能从高特权级向低特权级转移控制,因而不能直接调用处理程序。但由于进程正
16、处在返回用户空间的过程中,其返回状态,包括用户堆栈的栈顶、用户空 间下一条指令的地址等,都已记录在进程的系统堆栈中(由pt_regs结构描述,见图4.2),因而只要将pt_regs结构中的ip换成信号处理程序的入口地址(sighand_struct结构中的sa_handler),而后让进程正常返回,它就会立刻转去执行自己定义的信号处理程序。39然而,对ip域的简单修改破坏了进程的原有返回状态,使得进程在执行完信号处理程序之后无法再返回它在用户空间的 应有位置。因而,Linux将自定义信号处理程序的执行过程分成了如下六步:(1) 将系统堆栈栈顶的pt_regs结构暂存起来。(2) 修改进程的用户堆栈,在其中压入必须的参数,如信号编号、附加信息等。(3) 将pt_regs结构中的ip改为自定义信号处理程序的入口地址。(4) 返回用户态,让进程执行自定义的信号处理程序。40(5) 在完成处理工作