《UNIX进程间通信.ppt》由会员分享,可在线阅读,更多相关《UNIX进程间通信.ppt(44页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。
1、Linux进程间通信华清远见 杨劲松2版权n华清远见嵌入式培训中心版权所有;n未经华清远见明确许可,不能为任何目的以任何形式复制或传播此文档的任何部分;n本文档包含的信息如有更改,恕不另行通知;n保留所有权利。培训对象n有Windows平台的编程经验n有C/C+基础和经验培训目标n了解Linux操作系统n熟悉Linux编程环境n熟悉Linux下的软件开发、调试及测试工具n熟悉Linux进程和进程间通信编程知识n熟悉Linux下网络编程的知识n提供Linux环境下开发的参考建议参考资料nW.Richard StevensUNIX网络编程(第二卷 进程间通信)nIBM Developerworks
2、Linux环境进程间通信(一)Linux环境进程间通信(二)(上、下)主要内容nIPC概述1.IPC概述1.1 IPC概述n进程间通信(IPC Interprocess communication)n传统上(大多数情况下),IPC指的是运行在某个操作系统之上的不同进程间消息传递(Message passing)的方式,如管道、消息队列、共享内存等。n在有些时候,IPC也包括在不同主机进程之间的消息传递,如socket、Sun RPC等。1.2 IPC的类型n消息传递管道FIFO消息队列n同步互斥锁条件变量读写锁文件与记录锁信号灯n共享内存区匿名共享内存区有名共享内存区n远程过程调用Solari
3、s门SUN RPC1.3 IPC对象的持续性n随进程持续的IPC一直存在到打开着该对象的最后一个进程关闭该对象为止管道FIFOn随内核持续的IPC一直存在到内核重新自举或显式删除该对象为止System V消息队列信号灯共享内存区n随文件系统持续的IPC一直存在到显式删除该对象为止,即使内核重新自举了,该对象还保持其值Posix消息队列Posix信号灯Posix共享内存区1.4 Posix IPCnPosix消息队列nPosix信号灯nPosix共享内存区1.5 System V IPCnSystem V消息队列nSystem V信号灯nSystem V共享内存区2.管道2.1.1 管道n管道是
4、Linux支持的最初UNIX IPC形式之一n所有的UNIX都支持管道n管道是半双工的,数据只能向一个方向传输,需要全双工通信时,需要建立起两个管道,或者使用socketpair()创建全双工管道。n管道由单个进程创建,但是通常很少在一个进程内部使用,管道通常用于具有亲缘关系的进程(父子进程或者兄弟进程之间)间通讯。n宏S_ISFIFO()可以用来确定一个文件描述符或文件是否为管道或者FIFOn数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。n管道和FIFO都不支持诸如lseek()等文件定位操作。2
5、.1.2 管道单个进程中的管道单个进程中的管道,fork()后两个进程间的管道2.2 创建管道n管道两端可分别用描述字fd0以及fd1来描述,分别作为管道的读端和写端管道读端,由描述字fd0表示管道写端,由描述字fd1表示n如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。n一般文件的I/O函数都可以用于管道,如close()、read()、write()等等。n管道创建方式:2.3 管道的读写规则n从管道中读取数据:如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;当管道的写端存在时如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字
6、节数如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量);或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。n向管道中写入数据:向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)2.4 管道应用n管道常用于两个方面:在shell中时常会用到管道
7、(作为输入输出的重定向),在这种应用方式下,管道的创建对于用户来说是透明的;用于具有亲缘关系的进程间通信,用户自己创建管道,并完成读写操作。2.5 管道的局限性n管道的主要局限性正体现在它的特点上:只支持单向数据流只能用于具有亲缘关系的进程之间没有名字管道的缓冲区是有限的(管道只存在于内存中,在管道创建时,为缓冲区分配一个页面大小)管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式(即制定通讯协议),比如多少字节算作一个消息(或命令、或记录)等等3.有名管道(FIFO)3.有名管道(FIFO)n管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间
8、通信nFIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中n即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。nFIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。n管道和FIFO都不支持诸如lseek()等文件定位操作。3.2 FIFO创建nFIFO创建方式:n参数说明:第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二
9、个参数与打开普通文件的open()函数中的mode 参数相同。n可能错误:如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。n一般文件I/O函数都可以用于FIFO,如close()、read()、write()等等。n使用mkfifo()创建FIFO后,还需要进行open()。3.3 FIFO的打开规则n如果当前打开操作是为读而打开FIFO时若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打
10、开操作未设置非阻塞标志);或者,成功返回(当前打开操作设置非阻塞标志)。n如果当前打开操作是为写而打开FIFO时如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作未设置非阻塞标志);或者,返回ENXIO错误(当前打开操作设置了非阻塞标志)。3.4.1 FIFO的读写规则n从FIFO中读取数据规则约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。如果有进程写打开FIFO,且当前FIFO内没有数据,则对于未设置非阻塞标志的读操作来说,将一直阻塞。对于设置了非阻塞
11、标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。对于未设置非阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据;另外就是FIFO内没有数据。写阻塞的原因则是FIFO中有新的数据写入,不论新写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。n如果FIFO中有数据,则设
12、置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。3.4.2 FIFO的读写规则n向FIFO中写入数据:约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。对于设置了阻塞标志的写操作:当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区
13、域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。对于没有设置阻塞标志的写操作:当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;4.Posix消息队列4.1.1 Posix消息队列n消息队列可以认为是一个消息链表,有足够写权限的进程/线程可以向队列中放置(send)消息,有足够
14、读权限的进程/线程可以从队列中取走(receive)消息。n每个消息是一个记录,它由发送者赋予一个优先级n在某个进程/线程向一个消息队列写入消息之前,并不需要另外某个进程/线程在该消息队列上等待消息的到达n消息队列至少具有随内核的持续性,即一个进程可以往某个消息队列写入一些消息后终止,另外一个进程在以后某个时刻可以将消息读出nPosix消息队列与System V消息队列区别对Posix消息队列的读总是返回最高优先级的最早消息,对System V消息队列的读则可以返回任意指定优先级的消息当往一个空队列放置一个消息时,Posix消息队列允许产生一个信号或启动一个线程,System V消息队列则不提
15、供类似机制n消息队列中的每个消息具有如下的属性一个无符号整数优先级(Posix)或一个长整数类型(System V)消息的数据部分长度(可以为0)数据本身(如果长度大于0)4.1.2 消息队列的可能布局28含有三个消息的某个Posix消息队列的可能布局4.2 mq_open()nmq_open()函数创建一个新的消息队列或者打开一个已经存在的消息队列noflag参数取值O_RDONLYO_WRONLYO_RDWRn在创建消息队列时,需要设定attrnname是以”/”开头的字符串,并且后边不能再含有”/”(Posix的规定)nmq_open()的返回值为消息队列描述字(mqd_t类型)4.3
16、mq_close()n已打开的消息队列由mq_close()关闭n调用mq_close()后,调用进程不能再次使用该消息队列,但是消息队列并不从系统中删除n一个进程终止时,其所有打开着的消息队列都被自动关闭4.4 mq_unlink()n已经创建的消息队列使用mq_unlink()删除n每个消息队列都有一个保存其当前打开着的文件描述符的引用计数,只有当引用计数为0时,才真正的删除消息队列nmq_unlink()使用的参数为消息队列的名字,而不是消息队列的描述符4.5 mq_getattr()/mq_setattr()n每个消息队列有四个属性,mq_getattr()取得这些属性,mq_seta
17、ttr()设置其中某个属性nstruct mq_attr定义4.6 mq_send()/mq_receive()nmq_send()用于向消息队列中放入一个消息mq_send()可以为消息设定一个优先级允许发送0长度的消息nmq_receive()用于从消息队列中取出一个消息mq_receive()总是返回指定队列中最高优先级的最早消息,而且该优先级能随消息的内容及其长度一同返回nPosix消息队列和System V消息队列都不标识消息的发送者4.7 消息队列限制n在创建消息队列时,对创建的消息队列有两个限制mq_maxmsgmq_msgsizen系统定义了两个限制(在unistd.h中定义,
18、可以通过sysconf获取)MQ_OPEN_MAXPosix要求至少为8在Linux下测试其值为10MQ_PRIO_MAXPosix要求该值至少为4.8.1 mq_notify()nPosix消息队列允许异步事件通知(Asynchronous event notification),以通知何时有一个消息放置到了某个空消息队列中,并可以进行相应的处理产生一个信号创建一个线程执行一个指定的函数4.8.2 mq_notify()参数5.Posix信号灯5.1 Posix信号灯n信号灯(semaphore)是一种用于提供不同进程间或者一个给定进程的不同线程间同步手段的原语Posix有名信号灯,使用Po
19、six IPC名字标识Posix基于内存的信号灯,存放在共享内存中System V信号灯,在内核中维护5.2 信号灯的三种操作n创建(create)需要设定初始值,对于二值信号灯,可以为0或者为1n等待(wait)测试信号灯的值如果信号灯值小于1,则阻塞如果信号灯值大于0,则将其减1必须为原子操作n挂出(post)将信号灯值加1必须为原子操作5.3 Posix信号灯的函数调用5.4 sem_open()nsem_open()函数创建一个新的有名信号灯或者打开一个已经存在的有名信号灯noflag参数取值O_CREATO_CREAT|O_EXCLnsem_open()的返回值为sem_t类型)5.5 sem_close()n已打开的有名信号灯由sem_close()关闭n一个进程终止时,无论自愿终止还是非自愿终止,内核还对其上仍然打开着的所有有名信号灯自动执行sem_close()操作n关闭一个信号灯没有将它从系统中删除,即Posix信号灯至少是随内核持续的4.6 sem_unlink()n已经创建的信号灯使用sem_unlink()删除n每个信号灯都有一个保存期当前打开着的文件描述符的引用计数,只有当引用计数为0时,才真正的删除该信号灯nsem_unlink()使用的参数为消息队列的名字,而不是消息队列的描述符4.8 sem_post()/sem_getvalue()
限制150内