一、管道
单向,一段输入,另一端输出,先进先出FIFO。管道也是文件。管道大小4096字节。
特点:管道满时,写阻塞;空时,读阻塞。
分类:普通管道(仅父子进程间通信)位于内存;命名管道位于文件系统,没有亲缘关系管道只要知道管道名也可以通讯。
管道是由内核管理的一个缓冲区(buffer),相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
信号又称软中断,通知程序发生异步事件,程序执行中随时被各种信号中断,进程可以忽略该信号,也可以中断当前程序转而去处理信号,引起信号原因:
1).程序中执行错误码;
2).其他进程发送来的;
3).用户通过控制终端发送来;
4).子进程结束时向父进程发送SIGCLD;
5).定时器生产的SIGALRM;
(一)、信号在内核中的表示当用户进程通过系统调用刚进入内核的时候,CPU会自动在该进程的内核栈上压入下图所示的内容:
1、之所以把EIP的值设置成信号处理函数的地址,是因为一旦进程返回用户态,就要去执行信号处理程序,所以EIP要指向信号处理程序而不是原来应该执行的地址。2、之所以要把frame从内核栈拷贝到用户栈,是因为进程从内核态返回用户态会清理这次调用所用到的内核栈(类似函数调用),内核栈又太小,不能单纯的在栈上保存另一个frame(想象一下嵌套信号处理),而我们需要EAX(系统调用返回值)、EIP这些信息以便执行完信号处理函数后能继续执行程序,所以把它们拷贝到用户态栈以保存起来。这时进程返回用户空间,就会根据内核栈中的EIP值执行信号处理函数。那么,信号处理程序执行完后,怎么返回程序继续执行呢?信号处理程序执行完毕之后,进程会主动调用sigreturn()系统调用再次回到内核,查看有没有其他信号需要处理,如果没有,这时内核就会做一些善后工作,将之前保存的frame恢复到内核栈,恢复eip的值为old_eip,然后返回用户空间,程序就能够继续执行。至此,内核遍完成了一次(或几次)信号处理工作。 C++ Code
12 (By default, the signal handler is invoked on the normal PRocess stack. It is possible to arrange that the signal handler uses an alternate stack; see sigaltstack(2) for a discussion of how to do this and when it might be useful.) 三、System V 的IPC 机制为了提供与其他系统的兼容性,Linux 也支持3 种system Ⅴ的进程间通信机制:消息、信号量(semaphores)和共享内存,Linux 对这些机制的实施大同小异。我们把信号量、消息和共享内存统称System V IPC 的对象,每一个对象都具有同样类型的接口,即系统调用。就像每个文件都有一个打开文件号一样,每个对象也都有唯一的识别号,进程可以通过系统调用传递的识别号来存取这些对象,与文件的存取一样,对这些对象的存取也要验证存取权限,System V IPC 可以通过系统调用对对象的创建者设置这些对象的存取权限。在Linux 内核中,System V IPC 的所有对象有一个公共的数据结构pc_perm 结构,它是IPC 对象的权限描述,在linux/ipc.h 中定义如下: C++ Code
在这个结构中,要进一步说明的是键(key)。键和识别号指的是不同的东西。系统支持两种键:公有和私有。如果键是公有的,则系统中所有的进程通过权限检查后,均可以找到System V IPC 对象的识别号。如果键是私有的,则键值为0,说明每个进程都可以用键值0 建立一个专供其私用的对象。注意,对System V IPC 对象的引用是通过识别号而不是通过键。(一)、信号量Linux 中信号量是通过内核提供的一系列数据结构实现的,这些数据结构存在于内核空间,对它们的分析是充分理解信号量及利用信号量实现进程间通信的基础,下面先给出信号量的数据结构(存在于include/linux/sem.h 中) C++ Code
12345678910 struct ipc_perm{ key_t key; /* 键 */ ushort uid; /* 对象拥有者对应进程的有效用户识别号和有效组识别号 */ ushort gid; ushort cuid; /* 对象创建者对应进程的有效用户识别号和有效组识别号 */ ushort cgid; ushort mode; /* 存取模式 */ ushort seq; /* 序列号 */}; C++ Code
1234567891011121314151617181920212223242526272829303132333435 (1)系统中每个信号量的数据结构(sem)struct sem{ int semval; /* 信号量的当前值 */ unsigned short semzcnt; /* # waiting for zero */ unsigned short semncnt; /* # waiting for increase */ int sempid; /*在信号量上最后一次操作的进程识别号*/};(2)系统中表示信号量集合(set)的数据结构(semid_ds)struct semid_ds{ struct ipc_perm sem_perm; /* IPC 权限 */ long sem_otime; /* 最后一次对信号量操作(semop)的时间 */ long sem_ctime; /* 对这个结构最后一次修改的时间 */ struct sem *sem_base; /* 在信号量数组中指向第一个信号量的指针 */ struct sem_queue *sem_pending; /* 待处理的挂起操作*/ struct sem_queue **sem_pending_last; /* 最后一个挂起操作 */ struct sem_undo *undo; /* 在这个数组上的undo 请求 */ ushort sem_nsems; /* 在信号量数组上的信号量号 */};(3)系统中每一信号量集合的队列结构(sem_queue)struct sem_queue{ struct sem_queue *next; /* 队列中下一个节点 */ struct sem_queue **prev; /* 队列中前一个节点, *(q->prev) == q */ struct wait_queue *sleeper; /* 正在睡眠的进程 */ struct sem_undo *undo; /* undo 结构*/ int pid; /* 请求进程的进程识别号 */ int status; /* 操作的完成状态 */ struct semid_ds *sma; /*有操作的信号量集合数组 */ struct sembuf *sops; /* 挂起操作的数组 */ int nsops; /* 操作的个数 */};
123456 struct sembuf{ ushort sem_num; /* 在数组中信号量的索引值 */ short sem_op; /* 信号量操作值(正数、负数或0) */ short sem_flg; /* 操作标志,为IPC_NOWAIT 或SEM_UNDO*/}; 如果进程被挂起,Linux 必须保存信号量的操作状态并将当前进程放入等待队列。为此,Linux 内核在堆栈中建立一个 sem_queue 结构并填充该结构。新的 sem_queue 结构添加到集合的等待队列中(利用 sem_pending 和 sem_pending_last 指针)。当前进程放入sem_queue 结构的等待队列中(sleeper)后调用调度程序选择其他的进程运行。当某个进程修改了信号量而进入临界区之后,却因为崩溃或被“杀死(kill)”而没有退出临界区,这时,其他被挂起在信号量上的进程永远得不到运行机会,这就是所谓的死锁。Linux 通过维护一个信号量数组的调整列表(semadj)来避免这一问题。其基本思想是,当应用这些“调整”时,让信号量的状态退回到操作实施前的状态。(二)、消息队列
消息队列是先进先出FIFO原则
消息结构模板
strut msgbuf{long int mtype;//消息类型char mtext[1];//消息内容}Linux 中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC 的标识号唯一地标识。Linux 为系统中所有的消息队列维护一个 msgque 链表,该链表中的每个指针指向一个 msgid_ds 结构,该结构完整描述一个消息队列。C++ Code
(三)、共享内存
123456789101112131415161718192021222324252627282930313233 (1)消息缓冲区(msgbuf)/* msgsnd 和msgrcv 系统调用使用的消息缓冲区*/struct msgbuf{ long mtype; /* 消息的类型,必须为正数 */ char mtext[1]; /* 消息正文 */};(2)消息结构(msg)struct msg{ struct msg *msg_next; /* 队列上的下一条消息 */ long msg_type; /*消息类型*/ char *msg_spot; /* 消息正文的地址 */ short msg_ts; /* 消息正文的大小 */};(3)消息队列结构(msgid_ds)/* 在系统中的每一个消息队列对应一个msgid_ds 结构 */struct msgid_ds{ struct ipc_perm msg_perm; struct msg *msg_first; /* 队列上第一条消息,即链表头*/ struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */ time_t msg_stime; /* 发送给队列的最后一条消息的时间 */ time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */ time_t msg_ctime; /* 最后修改队列的时间*/ ushort msg_cbytes; /*队列上所有消息总的字节数 */ ushort msg_qnum; /*在当前队列上消息的个数 */ ushort msg_qbytes; /* 队列最大的字节数 */ ushort msg_lspid; /* 发送最后一条消息的进程的pid */ ushort msg_lrpid; /* 接收最后一条消息的进程的pid */}; 共享内存是分配一块能被其他进程访问的内存,实现是通过将内存去映射到共享它的进程的地址空间,使这些进程间的数据传送不再涉及内核,即,进程间通信不需要通过进入内核的系统调用来实现;共享内存与其他的进程间通信最大的优点是:数据的复制只有两次,一次是从输入文件到共享内存区,一次从共享内存区到输出文件而其他的则是需要复制4次:服务器将输入文件读入自己的进程空间,再从自己的进程空间写入管道/消息队列等;客户进程从管道/消息队列中读出数据到自己的进程空间,最后输出到客户指定的文件中;
要使用共享内存,应该有如下步骤:1.开辟一块共享内存 shmget()2.允许本进程使用共某块共享内存 shmat()3.写入/读出4.禁止本进程使用这块共享内存 shmdt()5.删除这块共享内存 shmctl()或者命令行下ipcrm与消息队列和信号量集合类似,内核为每一个共享内存段(存在于它的地址空间)维护着一个特殊的数据结构shmid_ds,这个结构在include/linux/shm.h 中定义如下: C++ Code我们用图 7.4 来表示共享内存的数据结构shmid_ds 与其他相关数据结构的关系。
12345678910111213141516 /* 在系统中 每一个共享内存段都有一个shmid_ds 数据结构. */struct shmid_ds{ struct ipc_perm shm_perm; /* 操作权限 */ int shm_segsz; /* 段的大小(以字节为单位) */ time_t shm_atime; /* 最后一个进程附加到该段的时间 */ time_t shm_dtime; /* 最后一个进程离开该段的时间 */ time_t shm_ctime; /* 最后一次修改这个结构的时间 */ unsigned short shm_cpid; /*创建该段进程的 pid */ unsigned short shm_lpid; /* 在该段上操作的最后一个进程的pid */ short shm_nattch; /*当前附加到该段的进程的个数 */ /* 下面是私有的 */ unsigned short shm_npages; /*段的大小(以页为单位) */ unsigned long *shm_pages; /* 指向frames -> SHMMAX 的指针数组 */ struct vm_area_struct *attaches; /* 对共享段的描述 */}; 某个进程第1 次访问共享虚拟内存时将产生缺页异常。这时,Linux 找出描述该内存的vm_area_struct 结构,该结构中包含用来处理这种共享虚拟内存段的处理函数地址。共享内存缺页异常处理代码对shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项。如果没有,系统将分配一个物理页并建立页表项,该页表项加入 shmid_ds 结构的同时也添加到进程的页表中。这就意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程。因此说,第1 个进程对共享内存的存取引起创建新的物理页面,而其他进程对共享内存的存取引起把那个页添加到它们的地址空间。当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表。当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页。当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中。四、Posix 的IPC 机制信号量:分为命名和匿名信号量。命名信号量通常用于不共享内存的进程之间(内核实现);匿名信号量可以用于线程通信(存放于线程共享的内存,如全局变量),或者用于进程间通信(存放于进程共享的内存,如System V/ Posix 共享内存)。消息队列、共享内存:与System V 类似。互斥锁mutex + 匿名信号量:线程通信互斥锁mutex + 条件变量condition :线程通信转:http://blog.csdn.net/jnu_simba/article/details/11746217http://blog.csdn.net/yangcs2009/article/details/39697303
新闻热点
疑难解答