[转帖]Linux 进程间通信(IPC)简介_VMware, Unix及操作系统讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  VMware, Unix及操作系统讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3944 | 回复: 0   主题: [转帖]Linux 进程间通信(IPC)简介        下一篇 
zhou
注册用户
等级:中校
经验:2210
发帖:125
精华:6
注册:2012-11-19
状态:离线
发送短消息息给zhou 加好友    发送短消息息给zhou 发消息
发表于: IP:您无权察看 2012-11-22 9:44:15 | [全部帖] [楼主帖] 楼主

Linux 进程间通信(IPC)简介


Linux系统中,以进程为单位分配和管理资源。由于保护的缘故,一个进程不能直接访问另一个进程的资源,也就是说,进程之间互相封闭。但在一个复杂的应用系统中,通常会使用多个相关的进程来共同完成一项任务,因此要求进程之间必须能够互相通信,从而来共享资源和信息。所以,一个操作系统内核必须提供进程间的通信机制(IPC)。
进程间通信(IPC: Inter-process communication)有如下一些目的:
数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
通知事件���一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

Linux下进程间通信的几种主要手段简介:
1. 管道(Pipe)及有名管道(named pipe)
   管道可用于具有亲缘关系进程间的通信,有名管道(FIFO)克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
2. 信号(Signal)
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可���信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
3. 报文(Message)队列(消息队列)
   消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4. 共享内存(Shared Memory)
   使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
6. 套接口(Socket):
   更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
PS: 一般来说,linux下的进程包含以下几个关键要素:
  • 有一段可执行程序;
  • 有专用的系统堆栈空间;
  • 内核中有它的控制块(进程控制块),描述进程所占用的资源,这样,进程才能接受内核的调度;
  • 具有独立的存储空间

进程和线程有时候并不完全区分,而往往根据上下文理解其含义。

5.1 共享内存(Shared Memory)
    * 共享内存是最快捷的进程间通信方式。访问共享内存的效率和访问进程自己的非共享内存的效率是相同的,而且这种通信方式不需要任何额外的系统调用。
    * 系统不会自动为共享内存处理同步问题,这个问题必须由用户自己解决。
    * 共享内存的步骤通常是:
          o 一个进程申请一块共享内存,即在它的页表中加入新的一项
          o 所有进程Attach该共享内存,即从申请内存的进程中拷贝对应的页表
          o 使用该内存进行通讯
          o 结束后所有进程detach该共享内存
          o 申请共享内存的进程在确定所有进程都detach后,释放该内存
    * 由于共享内存是通过页表来实现的,我们可以得出一个结论:共享内存的大小是页面大小的整数倍,页面的大小可以通过getpagesize()来得到,通常在Linux下该值是4KB
    * 相关的API函数:
          o 申请共享内存:shmget,返回共享内存segment的id
          o Attach,Detach函数:shmat,shmdt。需要共享内存segment的id
          o 释放申请的内存:shmctl。一定要记得释放!调用exit和exec会自动detach,但不会自动释放。
    * 使用 ipcs -m来观看当前系统存在的共享内存

5.2 进程信号量
    * 信号量(Semaphore)的概念前面已经介绍过了。Linux对用来同步进程的信号量采取了一种特别的实现方式。这些信号量也就被称为进程信号量(Process Semaphore)。(这一节下面所提到的所有信号量默认都是指进程信号量)
    * 相关的API函数:
          o 申请:semget
          o 释放:semctl。需要注意的是信号量不会被自动释放,我们必须显式释放它。
          o Wait和Post:semop
    * 使用ipcs -s来观看当前系统存在的信号量

5.3 内存映射(Mapped Memory)   --本文开篇并没提到,可将其归入"共享内存"
    * 内存映射使得不同的进程可以通过一个共享文件来互相通信。

和共享内存几乎相同,除了特工们把地点从内存改成了文件系统。

    * 相关的API函数:
          o 映射:mmap
          o 同步:msync。用来指定对文件的修改是否被buffer。
          o 释放:munmap。在程序结束的时候会自动unmap
    * mmap的其他用法:
          o 可以替代read和write,有时使用内存映射后的效率比单纯使用I/O操作来的更快
          o 在内存映射文件中构建structure,修改structure再次将文件映射到内存中可以快速的将structure恢复到原来的状态
          o 把/dev/zero文件映射到内存中。该文件可以提供无限的0,并且写到该文件的所有内容将被直接丢弃

5.4 管道(Pipes)
    * 管道是单向的,即一个线程写,另一个线程读,无法互换
    * 如果写的速度太快,造成管道满了,那么写的线程就会被block;如果读的速度太快,造成管道空了,那么读的进程就会被block。因此事实上我们可以说管道自动实现了同步机制
    * 我们可以通过调用pipe函数来生成一对pipe file description。(为什么是一对?因为一个读一个写)。可是,生成的pipe file description无法传送给不相关的进程(因为做为file descriptor即使它拿到了也没法用)。但是我们注意到fork之后父进程所有的file descriptor在子进程中依然有效,因此管道最大的作用是在父子进程之间通信。或者更确切的说,是在有共同祖先的进程之间通信。
    * 典型的创建管道的流程如下:
          o 用pipe生成2个pipe file description(简称fds)。然后调用fork
          o 在父进程关闭fds[0](或fds[1]),并以只读(或只写)方式打开fds[1](或fds[0])。在子进程中关闭fds[1](或fds[0]),并以只写(或只读)���式打开fds[0](或fds[1])。打开的函数是fdopen。
          o 开始通信。结束后用close函数关闭剩下的fds。
    * 这里有一个技巧:可以利用管道来达成重定向stdin, stdout和stderr。注意到dup2这个API可以把一个file descriptor复制到另一个上。
    * 事实上,我们有一对更为简洁的函数popen/pclose来完成上面的一系列复杂的操作。popen有两个参数:
          o 第一个参数接受一个exec,子进程将执行这个exec
          o 第二个参数为”w”或者”r”,”w”表示父进程写子进程读,”r”则反之
          o 返回值为管道的一端,也就是一个file descriptor
          o pclose用来关闭popen返回的file descriptor
    * FIFO(First In First Out)文件事实上是一个有名字的管道,换句话说,他可以用来让“不相干”的程序互相通信。
          o 我们使用mkfifo函数来创建一个FIFO文件
          o 我们可以使用任何的低级I/O函数(open, write, read, close等)以及C库I/O函数(fopen, fprintf, fscanf, fclose等)来操作FIFO文件。
    * Linux的管道和Windows下的命名管道(Named Pipes)的区别
          o Windows的命名管道更像一个套接字(sockets),它可以通过网络让不同主机上的程序进行通信
          o Linux的管道允许有多个reader和writer,每个reader和writer进行读/写的最大容量为 PIPE_BUF(4KB),如果有多个writer同时写,他们写的东西会被分为一个一个的chunk(每个4KB)并允许交错写。(例如进程A有两个 chunk,A1,A2。进程B也有两个chunk,B1,B2。A和B同时写,则顺序可能为A1,B1,A2,B2) Windows的管道允许在同一个管道上有多个reader/writer对,他们之间读写的数据没有交叉。

5.5 套接字(Sockets)
    * 套接字的特点:
          o 它是双向通信的
          o 它是进程间通信的,包括其他机器上的进程
    * 套接字有三个参数:
          o 通讯类型(communication style)
                + 连接(connection)类型:保证所有的包按发送的顺序到达接受方。(类似于电话)如果包丢失或者抵达顺序错误,会自动重发。
                + datagram类型:所有包单独发送,可能会出现丢失或者晚发早到的现象。(类似于邮寄)
          o 命名空间(namespace):描述套接字的地址是如何表示的,例如本地就是文件名,internet上就是ip地址。
          o 协议(protocol):通讯协议,常用的有TCI/IP,AppleTalk等。
    * 相关的API(套接字也是通过file descriptor来表示的):
          o socket:创建一个socket
          o closes:销毁一个socket
          o connect:在两个socket之间创建一个连接。这个API通常由客户端调用。
          o bind:给服务器的一个套��字绑定一个地址,服务器端调用。
          o listen:让一个套接字开始侦听,准备接受请求,服务器端调用。
          o accept:接受一个连接请求,并且为该连接创建一个新的套接字,服务器端调用。
    * 服务器端的生命流程:
          o 创建一个connection类型的socket
          o 给该socket绑定一个地址
          o 调用listen来enable该socket(listen可以指定最多有多少个请求在等待队列中,如果等待队列满了,又有新的请求到达的时候,则该请求被拒绝)
          o 对于收到的连接请求调用accept来接受
          o 关闭socket
    * 本地socket(local socket)
          o 如果是同一台电脑上的两个进程需要通信的话,可以使用本地socket。这种情况下socket的地址是文件路径。注意进程必须对该路径拥有可写权限,否则无法建立连接
          o 完成之后使用unlink来关闭一个socket
5.6 信号
信号本质:信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号来源:信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
进程对信号的响应:进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。

5.7 消息队列:
消息队列(也叫做报文队列)能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限,后来虽然 POSIX 1003.1b在信号的实时性方面作了拓广,使得信号在传递信息量方面有了相当程度的改进,但是信号这种通信方式更像"即时"的通信方式,它要求接受信号的进程在某个时间范围内对信号做出反应,因此该信号最多在接受信号进程的生命周期内才有意义,信号所传递的信息是接近于随进程持续的概念(process-persistent);管道及有名管道及有名管道则是典型的随进程持续IPC,并且,只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。
消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。���两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。

本文仅是一些简单的基础概念,如需了解更多细节的知识,可以到http://www.ibm.com/developerworks/cn
搜索“Linux环境进程间通信”来阅读相关的一些列

该贴被zhou编辑于2012-11-22 9:45:42



赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论