当写者想对管道写入时,它使用标准的写库函数。表示打开文件和打开管道的描叙符用来对进程的file数据 结构集合进行索引。Linux系统调用使用由管道file数据结构指向的write过程。这个write过程用保存在表示管道的VFS inode中的信息来管理写请求。
如果没有足够的空间容纳对所有写入管道的数据,只要管道没有被读者加锁。则Linux为写者加锁,并把从写入进程地址空间中写入的字节拷贝到共享数据页面中去。如果管道被读者加锁或者没有足够空间存储数据,当前进程将在管道inode的等待队列中睡眠,同时调度管理器开始执行以选择其它进程来执行。如果写入进程是可中断的,则当有足够的空间或者管道被解锁时,它将被读者唤醒。当数据被写入时,管道的VFS inode被解锁,同时任何在此inode的等待队列上睡眠的读者进程都将被唤醒。
从管道中读出数据的过程和写入类似。
进程允许进行非阻塞读(这依赖于它们打开文件或者管道的方式),此时如果没有数据可读或者管道被加锁, 则返回错误信息表明进程可以继续执行。阻塞方式则使读者进程在管道inode的等待队列上睡眠直到写者 进程结束。当两个进程对管道的使用结束时,管道inode和共享数据页面将同时被遗弃。
Linux还支持命名管道(named pipe),也就是FIFO管道,因为它总是按照先进先出的原则工作。第一个被写入 的数据将首先从管道中读出来。和其它管道不一样,FIFO管道不是临时对象,它们是文件系统中的实体并且 可以通过mkfifo命令来创建。进程只要拥有适当的权限就可以自由使用FIFO管道。打开FIFO管道的方式稍有不同。其它管道需要先创建(它的两个file数据结构,VFS inode和共享数据页面)而FIFO管道已经存在,只需要由使用者打开与关闭。在写者进程打开它之前,Linux必须让读者进程先打开此FIFO管道;任何读者进程从中读取之前必须有写者进程向其写入数据。FIFO管道的使用方法与普通管道基本相同,同时它们使用相同数据结构和操作。
5.3 套接口
5.3.1 系统V IPC机制
Linux支持Unix系统V(1983)版本中的三种进程间通讯机制。它们是消息队列、信号灯以及共享内存。这些系统V IPC机制使用共同的授权方法。只有通过系统调用将标志符传递给核心之后,进程才能存取这些资源。这些系统V IPC对象使用与文件系统非常类似的访问控制方式。对象的引用标志符被用来作为资源表中的索引。这个索引值需要一些处理后才能得到。
系统中所有系统V IPC对象的Linux数据结构包含一个ipc_perm结构,它含有进程拥有者和创建者及组标志符。另外还有对此对象(拥有者,组及其它)的存取模式以及IPC对象键。此键值被用来定位系统V IPC对象的引用标志符。这样的键值一共有两组:公有与私有。如果此键为公有,则系统中任何接受权限检查的进程都可以找到系统V IPC对象的引用标志符。系统V IPC对象绝不能用一个键值来引用,而只能使用引用标志符。
5.3.2 消息队列
消息队列允许一个或者多个进程向它写入与读取消息。Linux维护着一个msgque消息队列链表,其中每个元素 指向一个描叙消息队列的msqid_ds结构。当创建新的消息队列时,系统将从系统内存中分配一个msqid_ds结构,同时将其插入到数组中。
图5.2 系统V IPC消息队列
每个msqid_ds结构包含一个ipc_perm结构和指向已经进入此队列消息的指针。另外,Linux保留有关队列修改时间信息,如上次系统向队列中写入的时间等。msqid_ds包含两个等待队列:一个为队列写入进程使用而另一个由队列读取进程使用。
每次进程试图向写入队列写入消息时,系统将把其有效用户和组标志符与此队列的ipc_perm结构中的模式进行比较。如果允许写入操作,则把此消息从此进程的地址空间拷贝到msg数据结构中,并放置到此消息队列尾部。由于 Linux严格限制可写入消息的个数和长度,队列中可能容纳不下这个消息。此时,此写入进程将被添加到这个消息队列的等待队列中,同时调用调度管理器选择新进程运行。当由消息从此队列中释放时,该进程将被唤醒。
从队列中读的过程与之类似。进程对这个写入队列的访问权限将被再次检验。读取进程将选择队列中第一个消息(不管是什么类型)或者第一个某特定类型的消息。如果没有消息可以满足此要求,读取进程将被添加 到消息队列的读取等待队列中,然后系统运行调度管理器。当有新消息写入队列时,进程将被唤醒继续执行。
5.3.3 信号灯
信号灯最简单的形式是某个可以被多个进程检验和设置(test&set)的内存单元。这个检验与设置操作对每个进程而言是不可中断或者说是一个原子性操作;一旦启动谁也终止不了。检验与设置操作的结果是信号灯当前值加1, 这个值可以是正数也可以是负数。根据这个操作的结果,进程可能可以一直睡眠到此信号灯的值被另一个进程更改为止。信号灯可用来实现临界区(critical region):某一时刻在此区域内的代码只能被一个进程执行。
如果你有多个协作进程从一个数据文件中读取与写入记录。有时你可能需要这些文件访问遵循严格的访问次序。 那么可在文件操作代码上使用一个初始值为1的信号灯,它带有两个信号灯操作,一个检验并对信号灯 值减1,而另一个检验并加1。第一个访问文件的进程将试图将信号灯值减1,如果获得成功则信号灯值变成了 0。此进程于是开始使用这个数据文件,但是此时如果另一进程也想将信号灯值减1,则信号灯值将为-1,这次操作将会失败。它将挂起执行直到第一个进程完成对此数据文件的使用。此时这个等待进程将被唤醒,这次它对信号灯的操作将成功。
图5.3 系统V IPC信号灯
每个系统V IPC信号灯对象对应一个信号灯数组,Linux使用semid_ds结构来表示。系统中所有semid_ds结构由一组semary指针来指示。在每个信号灯数组中有一个sem_nsems,它表示一个由sem_base指向的sem结构。授权的进程可以使用系统调用来操纵这些包含系统V IPC信号灯对象的信号灯数组。这个系统调用可以定义许多种操作,每个操作用三个输入来描叙:信号灯索引、操作值和一组标志。信号灯索引是一个信号灯数组的索引,而操作值是将被加到信号灯上的数值。首先Linux将检查是否所有操作已经成功。如果操作值与信号灯当前数值相加大于0,或者操作值与信号灯当前值都是0,操作将会成功。如果所有信号灯操作失败,Linux仅仅会把那些操作标志没有要求系统调用为非阻塞类型的进程挂起。进程挂起后,Linux必须保存信号灯操作的执行状态并将当前进程放入等待队列。系统还在堆栈上建立sem_queue结构并填充各个域。这个sem_queue结构将被放到此信号灯对象等待队列的尾部(使用 sem_pending和sem_pending_last指针)。系统把当前进程置入sem_queue结构中的等待队列(sleeper)中,然后启动调度管理器选择其它进程运行。
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




