信号是在Unix System V中首先引入的,它实现了15种信号,但很不可靠。BSD4.2解决了其中的许多问题,而在BSD4.3中进一步加强和改善了信号机制。但两者的接口不完全兼容。在Posix 1003.1标準中做了一些强行规定,它定义了一个标準的信号接口,但没有规定接口的实现。目前几乎所有的Unix变种都提供了和Posix标準兼容的信号实现机制。
传统上有很多种实现管道的方法,如利用档案系统、利用套接字(sockets)、利用流等。在Linux中,使用两个file数据结构来实现管道。这两个file数据结构中的f_inode(f_dentry)指针指向同一个临时创建的VFS I节点,而该VFS I节点本身又指向记忆体中的一个物理页,如图5.1所示。两个file数据结构中的f_op指针指向不同的档案操作例程向量表:一个用于向管道中写,另一个用于从管道中读。这种实现方法掩盖了底层实现的差异,从进程的角度来看,读写管道的系统调用和读写普通档案的普通系统调用没什幺不同。当写进程向管道中写时,位元组被拷贝到了共享数据页,当读进程从管道中读时,位元组被从共享页中拷贝出来。Linux必须同步对于管道的存取,必须保证管道的写和读步调一致。Linux使用锁、等待伫列和信号(locks,wait queues and signals)来实现同步。管道示意图
右图 --管道示意图所示
参见include/linux/inode_fs.h
当写进程向管道写的时候,它使用标準的write库函式。这些库函式(read、write等)要求传递一个档案描述符作为参数。档案描述符是该档案对应的file数据结构在进程的file数据结构数组中的索引,每一个都表示一个打开的档案,在这种情况下,是打开的管道。Linux系统调用使用描述这个管道的file数据结构中f_op所指的write例程,该write例程使用表示管道的VFS I 节点中存放的信息,来管理写请求。如果共享数据页中有足够的空间能把所有的位元组都写到管道中,而且管道没有被读进程锁定,则Linux就在管道上为写进程加锁,并把位元组从进程的地址空间拷贝到共享数据页。如果管道被读进程锁定或者共享数据页中没有足够的空间,则当前进程被迫睡眠,它被挂在管道I节点的等待伫列中等待,而后调用调度程式,让另外一个进程运行。睡眠的写进程是可以中断的(interruptible),所以它可以接收信号。当管道中有了足够的空间可以写数据,或者当锁定解除时,写进程就会被读进程唤醒。当数据写完之后,管道的VFS I 节点上的锁定解除,在管道I节点的等待伫列中等待的所有读进程都会被唤醒。
前面讨论的信号和管道虽然可以在进程之间通信,但还有许多应用程式的IPC需求它们不能满足。因此在System V UNIX(1983)中首次引入了另外三种进程间通信机制(IPC)机制:讯息伫列、信号灯和共享记忆体(message queues,semaphores and shared memory)。它们最初的设计目的是满足事务式处理的套用需求,但目前大多数的UNIX供应商(包括基于BSD的供应商)都实现了这些机制。 Linux完全支持Unix System V中的这三种IPC机制。
System V IPC机制共享通用的认证方式。进程在使用某种类型的IPC资源以前,必须首先通过系统调用创建或获得一个对该资源的引用标识符。进程只能通过系统调用,传递一个唯一的引用标识符到核心来访问这些资源。在每一种机制中,对象的引用标识符都作为它在资源表中的索引。但它不是直接的索引,需要一个简单的操作来从引用标识符产生索引。对于System V IPC对象的访问,使用访问许可权检查,这很象对档案访问时所做的检查。System V IPC对象的访问许可权由对象的创建者通过系统调用设定。
系统中表示System V IPC对象的所有Linux数据结构中都包括一个ipc_perm数据结构,用它记录IPC资源的认证信息。其定义如下:
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_tmode;
unsigned short seq;
};
在ipc_perm数据结构中包括了创建者进程的用户和组标识、所有者进程的用户和组标识、对于这个对象的访问模式(属主、组和其它)以及IPC对象的键值(key)。Linux通过key 来定位System V IPC对象的引用标识符,每个IPC对象都有一个唯一的key。Linux支持两种key:公开的和私有的。如果key是公开的,那幺系统中的任何进程,只要通过了许可权检查,就可以找到它所对应的System V IPC对象的引用标识符。System V IPC对象不能直接使用key来引用,必须使用它们的引用标识符来引用。(参见include/linux/ipc.h)每种IPC机制都提供一种系统调用,用来将键值(key)转化为对象的引用标识符。
对所有的System V IPC,Linux提供了一个统一的系统调用:sys_ipc,通过该函式可以实现对System V IPC的所有操作。函式sys_ipc的定义如下:
struct msg *msg_first; /* first message on queue */
struct msg *msg_last; /* last message in queue */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
struct wait_queue *wwait;
struct wait_queue *rwait;
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
其中包括:
l 一个ipc_perm的数据结构(msg_perm域),描述该讯息伫列的通用认证方式。
l 一对讯息指针(msg_first、msg_last),分别指向该讯息伫列的队头(第一个讯息)和队尾(最后一个讯息)(msg)。传送者将新讯息加到队尾,接收者从队头读取讯息。
l 三个时间域(msg_stime、msg_rtime、msg_ctime)用于记录伫列最后一次传送时间、接收时间和改动时间。
l 两个进程等待伫列(wwait、rwait)分别表示等待向讯息伫列中写的进程(wwait)和等待从讯息伫列中读的进程(rwait)。如果某进程向一个讯息伫列传送讯息而发现该伫列已满,则进程挂在wwait伫列中等待。从该讯息伫列中读取讯息的进程将从伫列中删除讯息,从而腾出空间,再唤醒wwait伫列中等待的进程。如果某进程从一个讯息伫列中读讯息而发现该伫列已空,则进程挂在rwait伫列中等待。向该讯息伫列中传送讯息的进程将讯息加入伫列,再唤醒rwait伫列中等待的进程。
l 三个记数域(msg_cbytes、msg_qnum、msg_qbytes)分别表示伫列中的当前位元组数、伫列中的讯息数和伫列中最大位元组数;
l 两个PID域(msg_lspid、msg_lrpid)分别表示最后一次向该讯息伫列中传送讯息的进程和最后一次从该讯息伫列中接收讯息的进程。
见右图(参见include/linux/msg.h)图 System V IPC 机制——讯息伫列System V IPC 机制——讯息伫列