进程间通信IPCInterProcessCommunication.ppt

上传人:本田雅阁 文档编号:3174723 上传时间:2019-07-20 格式:PPT 页数:59 大小:288.53KB
返回 下载 相关 举报
进程间通信IPCInterProcessCommunication.ppt_第1页
第1页 / 共59页
进程间通信IPCInterProcessCommunication.ppt_第2页
第2页 / 共59页
进程间通信IPCInterProcessCommunication.ppt_第3页
第3页 / 共59页
进程间通信IPCInterProcessCommunication.ppt_第4页
第4页 / 共59页
进程间通信IPCInterProcessCommunication.ppt_第5页
第5页 / 共59页
点击查看更多>>
资源描述

《进程间通信IPCInterProcessCommunication.ppt》由会员分享,可在线阅读,更多相关《进程间通信IPCInterProcessCommunication.ppt(59页珍藏版)》请在三一文库上搜索。

1、进程间通信 IPC(InterProcess Communication),Zhxg, JN56 soft, 2004.3,IPC类型,管道 FIFO(命名管道) 消息队列 信号量 共享空间 套接口 (可用于多台主机间的进程间通信 ),管道,特点: 半双工,即数据只能在一个方向上流动 只能在具有公共祖先的进程之间使用。通常,一个管道由一进程调用fork,此后父、子进程之间就可应用该管道,系统调用pipe函数创建管道 #include int pipe(int filedes2); 返回:若成功则为0,若出错则为-1 经由参数filedes返回两个文件描述符,filedes0为读而打开,file

2、des1为写而打开。filedes1的输出是filedes0的输入。,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程或反之的IPC通道。,fork之后做什么取决于我们想要有的数据流的方向。对于从父进程到子进程的管道,父进程关闭管道的读端(fd0),子进程则关闭写端(fd1)。对于从子进程到父进程的管道,父进程关闭fd1,子进程关闭fd0。,下面是一个父进程通过管道向子进程传送数据的程序例子。 #include #include #include #include #include main() int n, fd2; pid_t pid; char linePIPE_BUF;

3、 if (pipe(fd) 0) / parent close(fd0); write(fd1, “hello worldn“, 12); else / child close(fd1); n = read(fd0, line, PIPE_BUF); write(STDOUT_FILENO, line, n); exit(0); ,在上面的例子中,直接对管道描述符调用read和write。 在写管道时,常数PIPE_BUF规定了内核中管道缓存器的大小。如果对管道进行write调用,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道(或FIFO)的write操作穿插进行

4、。但是,若有多个进程同时写一个管道(或FIFO),而且某个或某些进程要求写的字节数超过PIPE_BUF字节数,则数据可能会与其他写操作的数据相穿插。,当管道的一端被关闭后,下列规则起作用: 1) 当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示达到了文件结束处。 2)如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write出错返回,errno设置为EPIPE。,FIFO,FIFO又称为命名管道。管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。 FIFO是一种

5、文件类型。stat结构成员st_mode的编码指明文件是否是FIFO类型。可以用S_ISFIFO宏对此进行测试。创建FIFO类似于创建文件。FIFO的路径名确实存在于文件系统中。,FIFO可由库函数mkfifo创建: #include #include int mkfifo(const char *pathname, mode_t mode); 返回:若成功则为0,出错则为-1 mode参数的规格说明与open函数中的mode相同。,FIFO也可由系统调用mknod创建 #include #include #include #include int mknod(const char *path

6、name, mode_t mode, dev_t dev); 返回:若成功则为0,出错则为-1 mode参数的规格说明与open函数中的mode相同。但必须带上S_IFIFO标志,以说明将创建的是一个FIFO。,一旦已经创建了一个FIFO,就可用open打开它。一般的文件I/O函数(close、read、write、unlink等)都可用于FIFO。 # mknod fifo p # ls l fifo prw-r-r- 1 root root 0 Mar 25 00:16 fifo,类似于管道,若写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若某个FIFO的最后一个写进程关闭

7、了该FIFO,则将为该FIFO的读进程产生一个文件结束标志。 一个给定的FIFO有多个写进程是常见的。这就意味着如果不希望多个进程所写的数据互相穿插,则需考虑原子写操作。正如对于管道一样,常数PIPE_BUF说明了可被原子写到FIFO的最大数据量。(Linux里定义PIPE_BUF值为4096),在一般情况中,当打开一个FIFO时,如果没有说明非阻塞标志O_NONBLOCK,只读打开要阻塞到某个其他进程为写打开此FIFO。类似,为写而打开一个FIFO要阻塞到某个其他进程为读而打开它。 如果指定了O_NONBLOCK,则只读打开立即返回。但是,如果没有进程已经为读而打开一个FIFO,那么只写打开

8、将出错返回,其errno是ENXIO。,系统V IPC的一般特征,消息队列、信号量以及共享内存又统称为系统V IPC (一般的,所谓IPC即指系统V IPC ,不特别说明的话,下面的IPC都是这个含义),标识符和关键字,每个内核中的IPC结构(消息队列、信号量或共享内存)都用一个非负整数的标识符(identifier)加以引用。例如,为了对一个消息队列发送或取消息,只需知道其队列标识符。,创建IPC结构(调用msgget、semget或shmget)时,都必须指定一个关键字(key),关键字的数据类型由系统规定为key_t,通常在头文件中被规定为长整型。关键字由内核变换成标识符。 三个get函

9、数(msgget、semget和shmget)都有两个类似的参数key和一个整型的flag。如若满足下列条件,则创建一个新的IPC结构: 1)key是IPC_PRIVATE,或 2)key当前未与特定类型的IPC结构相结合,flag中指定了IPC_CREAT位。,如果希望创建一个新的IPC结构,保证不是引用具有同一标识符的一个现行IPC结构,那么必须在flag中同时指定IPC_CREAT和IPC_EXCL位。这样做了以后,如果IPC结构已经存在就会造成出错,返回EEXIST(这与指定了O_CREAT和O_EXCL标志的open相类似)。,三个get函数也可以用来访问现存的IPC结构,这时key

10、必须等于创建该IPC结构时所指定的关键字,并且不应在flag中指定IPC_CREAT。 为了访问一个用IPC_PRIVATE关键字创建的现存的IPC结构,一定要知道与该IPC结构相结合的标识符,然后在其他IPC调用中(例如msgsnd、msgrcv)使用该标识符。,许可权结构,系统V IPC为每一个IPC结构设置了一个ipc_perm结构。该结构规定了许可权和所有者。 事实上,大多数情况不需要考虑许可权结构的设置和修改。,缺陷,IPC结构是在系统范围内起作用的,没有访问计数。 这些IPC结构并不按名字为文件系统所知。我们不能用文件类的函数来存取它们或修改它们的特性。,例如,如果创建了一个消息队

11、列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不被删除。它们余留在系统中直至:由某个进程调用msgrcv或msgctl读消息或删除消息队列,或某个进程执行ipcrm命令删除消息队列;或由正在再起动的系统删除消息队列。 将此与管道pipe相比,那么当最后一个访问管道的进程终止时,管道就被完全地删除了。 对于FIFO而言虽然当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在FIFO中的数据却在此时全部删除。,为了支持它们不得不增加了十多个全新的系统调用(msgget、semop、shmat等)。我们不能用ls命令见到它们,不能用rm命令删除它们,不

12、能用chmod命令更改它们的存取权。于是,也不得不增加了全新的命令ipcs和ipcrm。 因为这些IPC不使用文件描述符,所以不能对它们使用多路转接I/O函数:select和poll。这就使得一次使用多个IPC结构,以及用文件或设备I/O来使用IPC结构很难做到。例如,没有某种形式的忙-等待循环,就不能使一个服务器等待一个消息放在两个消息队列的任意一个中。,消息队列,消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。消息队列简称为“队列”,其标识符为“队列ID”。msgget用于创建一个新队列或打开一个现存的队列。msgsnd用于将新消息添加到队列尾端。每个消息包含一个正长整型类型字

13、段,一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列时,传送给msgsnd。msgrcv用于从队列中取消息。我们并不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。,#include #include #include 创建队列 int msgget(key_t key, int flag); 删除队列 int msgctl(int msqid, IPC_RMID , NULL);,发送消息 int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); 每个消息都由三部分组成,它们是

14、:正长整型类型字段、非负长度(msgsz)以及实际数据字节(对应于长度)。消息总是放在队列尾端。,msgp指向一个长整型数,它包含了正整型消息类型,在其后立即跟随了消息数据。(若msgsz是0,则无消息数据。)若发送的最长消息是512字节,则可定义下列结构: struct mymesg long mtype; /* positive message type */ char mtext512; /* message data,of length msgsz*/ ; 于是,msgp就是一个指向mymesg结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。,msgflg的值可以指定为IP

15、C_NOWAIT。这类似于文件I/O的非阻塞I/O标志。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。 如果没有指定IPC_NOWAIT,则进程阻塞直到(a)有空间可以容纳要发送的消息,或(b)从系统中删除了此队列,或(c)捕捉到一个信号,并从信号处理程序返回。在第二种情况下,返回EIDRM(“标志符被删除”)。最后一种情况则返回EINTR。,接收消息 ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long

16、msgtyp, int msgflg); 如同msgsnd中一样,msgp参数指向一个长整型数(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓存。msgsz说明数据缓存的长度。若返回的消息大于msgsz,而且在msgflg中设置了MSG_NOERROR,则该消息被截短(在这种情况下,不通知我们消息截短了)。如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中)。,参数type使我们可以指定想要哪一种消息: type = 0 返回队列中的第一个消息。 type 0 返回队列中消息类型为type的第一个消息。 type 0 返回队列中消息类型值小于或等于type

17、绝对值,而且在这种消息中,其类型值又最小的消息。 非0type用于以非先进先出次序读消息。例如,若应用程序对消息赋优先权,那么type就可以是优先权值。如果一个消息队列由多个客户机和一个服务器使用,那么type字段可以用来包含客户机进程ID。,可以指定msgflg值为IPC_NOWAIT,使操作不阻塞。这使得如果没有所指定类型的消息,则msgrcv出错返回ENOMSG。如果没有指定IPC_NOWAIT,则进程阻塞直至(a)有了指定类型的消息,或(b)从系统中删除了此队列(出错返回EIDRM),或(c)捕捉到一个信号并从信号处理程序返回(出错返回EINTR)。,消息对列的系统限制,MSGMAX

18、可发送的最长消息的字节长度 8192 MSGMNB 一个队列的最大字节长度(亦即队列中所有消息之和) 16384 MSGMNI 系统中最大消息队列数 16 如果进程间要交换的数据量比较大,可以用文件或套接口机制。,信号量,信号量实际上是同步原语而不是IPC,常用于共享资源的同步存取,例如共享内存。,为了获得共享资源,进程需要执行下列操作: 1)测试控制该资源的信号量。 2)若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示它使用了一个资源单位。 3)若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至第1步。 4)当进程不再使用由一个信息量控制的

19、共享资源时,该信号量值增1。如果有进程正在睡眠等待此信号量,则唤醒它们。,信号量其实是含有一个或多个信号量值的集合。 调用的函数semget创建信号量,获得一个信号量ID。 #include #include #include int semget(key_t key, int nsems, int flag); 返回:若成功则返回信号量ID,若出错则为-1 nsems是该集合中的信号量数。如果是创建新集合,则必须指定nsems。如果引用一个现存的集合,则将nsems指定为0。,双态信号量(binary semaphore),信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用

20、。 常用的信号量形式被称之为双态信号量(binary semaphore)。它控制单个资源,其初始值为1。 双态信号量被用来实现进程间互斥。,#include #include #include int main(void) int semid; union semun arg; struct sembuf acquire_m = 0, -1 , SEM_UNDO, release_m = 0, 1 , SEM_UNDO; if ( (semid = semget(IPC_PRIVATE, 1, IPC_CREAT) 0) perror(“semget error”); exit(1); ar

21、g.val = 1; if (semctl(semid, 0, SETVAL, arg) 0) perror(“semctl setval error”); semctl(semid, 0, IPC_RMID, NULL); exit(1); semop(semid, ,1、创建信号量 semid = semget(IPC_PRIVATE, 1, IPC_CREAT); 创建一个新的信号量集合,这个集合里只有一个成员,返回信号量标识符semid,2、设置信号量的值 union semun arg; arg.val = 1; semctl(semid, 0, SETVAL, arg); 设置信号

22、量集合中的第一个成员的值为1,成员的计数从0开始,3、获取信号量 struct sembuf acquire_m = 0, -1 , SEM_UNDO, semop(semid, 对信号量集合中的第一个成员做减一操作,即试图获得信号量,进程将一直等待,直到获得信号量。,int semop(int semid, struct sembuf semoparray , size_t nops); 返回:若成功则为0,若出错则为-1 semoparray是一个指针,它指向一个信号量操作数组。 struct sembuf unsigned short sem_num; /* semaphore inde

23、x in array */ short sem_op; /* operation */ short sem_flg; /* operation flags */ ; nops规定该数组中操作的数量(元素数)。,4、释放信号量 struct sembuf rlease_m = 0, 1 , SEM_UNDO, semop(semid, 对信号量集合中的第一个成员做加一操作,即释放信号量,五、删除信号量 semctl(semid, 0, IPC_RMID, NULL); 进程终止时,不会自动删除信号量。,如果在进程终止时,它占用了经由信号量分配的资源,那么就会成为一个问题。无论何时只要为信号量操作

24、指定了SEM_UNDO标志,然后分配资源(sem_op值小于0),那么内核就会记住对于该特定信号量,分配给我们多少资源(sem_op的绝对值)。当该进程终止时,内核将检验该进程是否还有尚未处理的信号量调整值,如果有,则按调整值对相应量值进行调整,设置为0 。,信号量与记录锁的比较,如果多个进程共享一个资源,则可使用信号量或记录锁。 若使用信号量,则先创建一个包含一个成员的信号量集合,然后对该信号量值赋初值1。为了分配资源,以sem_op为1调用semop,为了释放资源,则以sem_op为+1调用semop。对每个操作都指定SEM_UNDO,以处理在未释放资源情况下进程终止的情况。,若使用记录锁

25、,则先创建一个空文件。为了分配资源,先对该文件获得一个写锁,释放该资源时,则对该文件解锁。记录锁的性质确保了,当有一个锁的进程终止时,内核会自动释放该锁。 记录锁稍慢于信号量锁,但如果只需锁一个资源(例如共享内存)并且不需要使用系统V信号量的所有花哨的功能,则宁可使用记录锁。理由是:( a )使用简易,(b)进程终止时,会处理任一遗留下的锁。,记录锁示例,int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) struct flock lock; lock.l_type = type; lock.

26、l_start = offset; lock.l_whence = whence; lock.l_len = len; return(fnctl(fd, cmd, ,#define read_lock(fd,offset,whence,len) lock_reg (fd, F_SETLK, F_RDLCK, offset, whence, len) 加读锁,失败立即返回 #define readw_lock(fd,offset,whence,len) lock_reg (fd, F_SETLKW, F_RDLCK, offset, whence, len) 加读锁,直到成功才返回 #defin

27、e write_lock(fd,offset,whence,len) lock_reg (fd, F_SETLK, F_WRLCK, offset, whence, len) 加写锁,失败立即返回 #define writew_lock(fd,offset,whence,len) lock_reg (fd, F_SETLKW, F_WRLCK, offset, whence, len) 加写锁,直到成功才返回 #define un_lock(fd,offset,whence,len) lock_reg (fd, F_SETLK, F_UNLCK, offset, whence, len) 解锁

28、,共享内存,共享内存允许两个或多个进程共享一给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种IPC。使用共享内存的唯一窍门是多个进程之间对一给定存储区的同步存取。若进程A将数据放入共享内存区,则在进程A做完这一操作之前,进程B不应当去取这些数据。通常,信号量/记录锁被用来实现对共享内存存取的同步。,#include #include #include #define SHM_SIZE 100000 #define SHM_MODE (SHM_R|SHM_W) int main(void) int shmid; char *shmptr; if (shmid=shmget(IPC_

29、PRIVATE, SHM_SIZE, SHM_MODE) 0) perror(“shmget error”); exit(1); if ( (shmptr = shmat(shmid, 0, 0) = (void *)-1 ) perror(“shmat error”); exit(1); if (shmctl(shmid, IPC_RMID, 0) 0) perror(“shmctl error”); exit(1); exit(0); ,一、创建共享内存 #define SHM_SIZE 100000 #define SHM_MODE (SHM_R|SHM_W) shmid=shmget(

30、IPC_PRIVATE,SHM_SIZE,SHM_MODE); 创建大小为SHM_SIZE的可读可写的共享内存段,返回标识符shmid。,二、连接共享内存段到进程地址空间 shmptr = shmat(shmid, 0, 0); 把共享内存段连接到由内核选择的进程第一个可用地址上。接下来,进程通过指针shmptr就可以象访问普通内存段一样使用共享内存段了。,三、释放共享内存 shmctl(shmid, IPC_RMID, 0);,前面结合具体的例子介绍了信号量和共享内存,这些例子都是最常用的方法,进程间通信.doc有更详细更深入的说明。 例子中都是以IPC_PRIVATE作为关键字创建IPC的

31、,对于有父子关系的进程,可以令父进程创建IPC,并把IPC标识符说明成全局变量(对于共享内存也可以把共享内存地址说明成全局变量),子进程通过继承取得这些值,并使用它们进行IPC。,对于独立进程间的通信,一般要指定一个约定的key,一个进程以它为关键字创建IPC,其他进程以它为关键字引用IPC(创建和引用IPC的接口是一样的)。如果创建IPC的进程确需以IPC_PRIVATE作为关键字,那么必须以某种方式将IPC标识符传递给其他进程,比如记文件。,小结,本章详细说明了进程间通信的多种形式;管道、命名管道(FIFO)以及另外三种IPC形式,通常称之为系统V IPC消息队列、信号量和共享内存。信号量

32、实际上是同步原语而不是IPC,常用于共享资源的同步存取,例如共享内存。 提出下列建议:学会使用管道和FIFO,因为在大量应用程序中仍可有效地使用这两种基本技术。在新的应用程序中,要尽可能避免使用消息队列以及信号量,而应当考虑套接口和记录锁,因为它们与Linux内核的其他部分集成得要好得多,并且通过知识的复用(例如既把套接口用于网络通信,也用来在进程间传递消息),可以减轻记忆的负担,也有利于更熟练更准确更深入的使用。共享内存有其应用场合。,关于服务器调试,用加打印语句的方法 对后台运行的服务器,把信息打印到约定的日志文件里。服务器初始化的时候,以添加写的方式打开日志文件,进程结束自动关闭打开的文件。 信息分级:debug、verbose、error,可以定义成共享内存变量。 信息组成:时间、进程号或任务名、正文 char *ctime(const time_t *timep); “Fri Mar 26 15:09:54 2004n” 实时查看信息:tail f 日志文件,谢谢!,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1