Interprocess Communication Mechanisms (进程间通讯机制)
进程之间互相通讯并和核心通讯,协调它们的行为。 Linux 支持一些进程间通讯( IPC )的机制。信号和管道是其中的两种, Linux 还支持系统 V IPC (用首次出现的 Unix 的版本命名)的机制。
5.1 Signals (信号)
信号是 Unix 系统中使用的最古老的进程间通讯的方法之一。用于向一个或多个进程发送异步事件的信号。信号可以用键盘终端产生,或者通过一个错误条件产生,比如进程试图访问它的虚拟内存中不存在的位置。 Shell 也使用信号向它的子进程发送作业控制信号。
有一些信号有核心产生,另一些可以由系统中其他有权限的进程产生。你可以使用 kill 命令( kill –l )列出你的系统的信号集,在我的 Linux Intel 系统输出:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR
在 Alpha AXP Linux 系统上编号不同。进程可以选择忽略产生的大多数信号,有两个例外: SIGSTOP (让进程停止执行)和 SIGKILL (让进程退出)不可以忽略,虽然进程可以选择它如何处理信号。进程可以阻塞信号,如果它不阻塞信号,它可以选择自己处理或者让核心处理。如果核心处理,将会执行该信号的缺省行为。例如,进程接收到 SIGFPE (浮点意外)的缺省动作是产生 core 并退出。信号没有固有的优先级,如果一个进程同时产生了两个信号,它们会以任意顺序出现在进程中并按任意顺序处理。另外,也没有机制可以处理统一种类的多个信号。进程无法知道它接收了 1 还是 42 个 SIGCONT 信号。
Linux 用进程的 task_struct 中存放的信息来实现信号机制。支持的信号受限于处理器的字长。 32 位字长的处理器可以有 32 中信号,而 64 位的处理器,比如 Alpha AXP 可以有多达 64 种信号。当前待处理的信号放在 signal 域, blocked 域放着要阻塞的信号掩码。除了 SIGSTOP 和 SIGKILL ,所有的信号都可以被阻塞。如果产生了一个被阻塞的信号,它一直保留待处理,直到被解除阻塞。 Linux 也保存每一个进程如何处理每一种可能的信号的信息,这些信息放在一个 sigaction 的数据结构数组中,每一个进程的 task_struct 都有指针指向对应的数组。这个数组中包括处理这个信号的例程的地址,或者包括一个标志,告诉 Linux 该进程是希望忽略这个信号还是让核心处理。进程通过执行系统调用改变缺省的信号处理,这些调用改变适当的信号的 sigaction 和阻塞的掩码。
并非系统中所有的进程都可以向其他每一个进程发送信号,只有核心和超级用户可以。普通进程只可以向拥有相同 uid 和 gid 或者在相同进程组的进程发送信号。通过设置 task —— struct 的 signal 中适当的位产生信号。如果进程不阻塞信号,而且正在等待但是可以中断(状态是 Interruptible ),那么它的状态被改为 Running 并确认它在运行队列,通过这种方式把它唤醒。这样调度程序在系统下次调度的时候会把它当作一个运行的候选。如果需要缺省的处理, Linux 可以优化信号的处理。例如如果信号 SIGWINCH ( X window 改变焦点)发生而使用缺省的处理程序,则不需要做什么事情。
信号产生的时候不会立刻出现在进程中,它们必须等到进程下次运行。每一次进程从系统调用中退出的时候都要检查它的 signal 和 blocked 域,如果有任何没有阻塞的信号,就可以发送。这看起来好像非常不可靠,但是系统中的每一个进程都在调用系统调用,比如向终端写一个字符的过程中。如果愿意,进程可以选择等待信号,它们挂起在 Interruptible 状态,直到有了一个信号。 Linux 信号处理代码检查 sigaction 结构中每一个当前未阻塞的信号。
如果信号处理程序设置为缺省动作,则核心会处理它。 SIGSTOP 信号的缺省处理是把当前进程的状态改为 Stopped ,然后运行调度程序,选择一个新的进程来运行。 SIGFPE 信号的缺省动作是让当前进程产生 core ( core dump ),让它退出。变通地,进程可以指定自己的信号处理程序。这是一个例程,当信号产生的时候调用而且 sigaction 结构包括这个例程的地址。 Linux 必须调用进程的信号处理例程,至于具体如何发生是和处理器相关。但是,所有的 CPU 必须处理的是当前进程正运行在核心态,并正准备返回到调用核心或系统例程的用户态的进程。解决这个问题的方法是处理该进程的堆栈和寄存器。进程程序计数器设为它的信号处理程序的地址,例程的参数加到调用结构或者通过寄存器传递。当进程恢复运行的时候显得信号处理程序是正常的调用。
Linux 是 POSIX 兼容的,所以进程可以指定调用特定的信号处理程序的时候要阻塞的信号。这意味着在调用进程的信号处理程序的时候改变 blocked 掩码。信号处理程序结束的时候, blocked 掩码必须恢复到它的初始值。因此, Linux 在收到信号的进程的堆栈中增加了对于一个整理例程的调用,把 blocked 掩码恢复到初始值。 Linux 也优化了这种情况:如果同时几个信号处理例程需要调用的时候,就在它们堆积在一起,每次退出一个处理例程的时候就调用下一个,直到最后才调用整理例程。
5.2 Pipes (管道)
普通的 Linux shell 都允许重定向。例如:
$ ls | pr | lpr
把列出目录文件的命令 ls 的输出通过管道接到 pr 命令的标准输入上进行分页。最后, pr 命令的标准输出通过管道连接到 lpr 命令的标准输入上,在缺省打印机上打印出结果。管道是单向的字节流,把一个进程的标准输出和另一个进程的标准输入连接在一起。没有一个进程意识到这种重定向,和它平常一样工作。是 shell 建立了进程之间的临时管道。在 Linux 中,使用指向同一个临时 VFS I 节点(本身指向内存中的一个物理页)的两个 file 数据结构来实现管道。图 5.1 显示了每一个 file 数据结构包含了不同的文件操作例程的向量表的指针:一个用于写,另一个从管道中读。这掩盖了和通用的读写普通文件的系统调用的不同。当写进程向管道中写的时候,字节拷贝到了共享的数据页,当从管道中读的时候,字节从共享页中拷贝出来。 Linux 必须同步对于管道的访问。必须保证管道的写和读步调一致,它使用锁、等待队列和信号( locks , wait queues and signals )。
参见 include//inode_fs.h
当写进程向管道写的时候,它使用标准的 write 库函数。这些库函数传递的文件描述符是进程的 file 数据结构组中的索引,每一个都表示一个打开的文件,在这种情况下,是打开的管道。 Linux 系统调用使用描述这个管道的 file 数据结构指向的 write 例程。这个 write 例程使用表示管道的 VFS I 节点存放的信息,来管理写的请求。如果有足够的空间把所有的字节都写导管到中,只要管道没有被读进程锁定, Linux 为写进程上锁,并把字节从进程的地址空间拷贝到共享的数据页。如果管道被读进程锁定或者空间不够,当前进程睡眠,并放在管道 I 节点的等待队列中,并调用调度程序,运行另外一个进程。它是可以中断的,所以它可以接收信号。当管道中有了足够的空间写数据或者锁定解除,写进程就会被读进程唤醒。当数据写完之后,管道的 VFS I 节点锁定解除,管道 I 节点的等待队列中的所有读进程都会被唤醒。
参见 fs/pipe.c pipe_write()
从管道中读取数据和写数据非常相似。进程允许进行非阻塞的读(依赖于它们打开文件或者管道的模式),这时,如果没有数据可读或者管道被锁定,会返回一个错误。这意味着进程会继续运行。另一种方式是在管道的 I 节点的等待队列中等待,直到写进程完成。如果管道的进程都完成了操作,管道的 I 节点和相应的共享数据页被废弃。
参见 fs/pipe.c pipe_read()
Linux 也可以支持命名管道,也叫 FIFO ,因为管道工作在先入先出的原则下。首先写入管道的数据是首先被读出的数据。不想管道, FIFO 不是临时的对象,它们是文件系统中的实体,可以用 mkfifo 命令创建。只要有合适的访问权限,进程就可以使用 FIFO 。 FIFO 的大开方式和管道稍微不同。一个管道(它的两个 file 数据结构, VFS I 节点和共享的数据页)是一次性创建的,而 FIFO 是已经存在,可以由它的用户打开和关闭的。 Linux 必须处理在写进程打开 FIFO 之前打开 FIFO 读的进程,以及在写进程写数据之前读的进程。除了这些, FIFO 几乎和管道的处理完全一样,而且它们使用一样的数据结构和操作。
Sockets
注意:写网络篇的时候加上去
System V IPC mechanisms (系统 V IPC 机制)
Linux 支持三种首次出现在 Unix 系统 V ( 1983 )的进程间通讯的机制:消息队列、信号灯和共享内存( message queues , semaphores and shared memory )。系统 V IPC 机制共享通用的认证方式。进程只能通过系统调用,传递一个唯一的引用标识符到核心来访问这些资源。对于系统 V IPC 对象的访问的检查使用访问许可权,很象对于文件访问的检查。对于系统 V IPC 对象的访问权限由对象的创建者通过系统调用创建。每一种机制都使用对象的引用标识符作为资源表的索引。这不是
【责编:admin】
--------------------next---------------------