论文部分内容阅读
【摘 要】在Linux下,由于处于用户态的不同进程之间是彼此隔离的,它们必须通过某种机制来进行通信。Linux平台下提供了多种进程通信方式,如管道、信号量、消息队列等,本文主要研究了Linux环境中的管道通信的实现机制,探讨无名管道和有名管道的工作方式,及相应的创建和使用的方法。
【关键词】Linux、进程通信、管道
一、管道实现机制
我们把从一个进程连接到另一个进程的数据流称为 “管道”,这是最早的Linux进程间通信机制之一。在Linux中管道常作为一种特殊文件处理。实际上,管道是内核中一個固定大小的缓冲区,它按先进先出的方式进行数据传输,一个进程向管道中写的内容会被管道另一端的进程读出。每次写入的内容都添加在管道缓冲区的末尾,且从缓冲区的头部读出数据,读写的位置自动增加,并且从管道读数据是一次性操作,数据一旦被读,便从管道中被抛弃。在缓冲区写满时,则由相应的规则控制读写进程进入等待队列,当空的缓冲区有写入数据或满的缓冲区有数据读出时,就唤醒等待队列中的读写进程继续读写。
管道分无名管道和有名管道。无名管道没有文件名,也没有磁盘节点,仅作为一个内存对象存在,用完后便销毁。无名管道没有显式的打开过程,实际上它在创建时就自动打开了,故只能由有亲缘关系的两个进程间通信使用。而有名管道克服了无名管道没有名字的限制,可由任意两个或多个进程间通信使用,它的使用方法和普通文件类似,都遵循打开、读、写、关闭的过程,只是读写的内部实现和普通文件有所不同。
二、无名管道
(一)无名管道创建
int pipe(int fd[2])在Linux中可以通过系统调用建立管道。当一个管道建立时,会创建两个文件描述符fd [0]和fd [1],其中fd [0]固定用于读管道,fd[1]固定用于写管道,如图1所示,这样就构成了一个半双工通道。
由于管道用于不同进程间通信,而调用pipe()创建的管道两端处于一个进程中,这在实际应用中没有太大意义。因此,通常先是创建一个管道,再通过调用fork()函数创建子进程,该子进程会继承父进程所创建的管道,这时,父子进程就可以共享该管道。管道的文件描述符对应关系如图2所示。此时关系看似非常复杂,实际上却给不同进程之间的读写创造了很好的条件。父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端文件描述符关闭即可。如在图3中将父进程的写端fd[1]和子进程的读端fd[0]关闭,此时,父子进程之间就建立起了一条“子进程写入父进程读取”的通道。反之,亦如此。
(二) 无名管道读写操作
创建完管道后,便可以调用I/O函数,如close、read、write等函数,对管道进行读写了,但注意与普通文件读写还有一定区别。
1.从无名管道中读取数据。如果管道写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
当管道写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数或返回请求的字节数。
2.向无名管道中写入数据。向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,则写操作将一直阻塞。
三、有名管道
有名管道建立在实际的磁盘介质上,有自己的名字,它以FIFO的文件形式存在于文件系统中。故即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。
(一) 有名管道创建
int mkfifo(const char * pathname, mode_t mode)
该函数的第一个参数是路径名,即创建后FIFO的名字。若该参数是一个已经存在的路径名时,会返回EEXIST错误,故在调用mkfifo函数前最好先检查FIFO是否已经创建。
第二个参数与open()函数中的mode参数相同。如果程序在打开FIFO时指定了只读/只写方式,则该进程对于打开的FIFO就是一个读端/写端。如果指定的是读写方式,进程既是读端又是写端。
(二) 有名管道的打开
无名管道在创建时自动打开,但有名管道在使用时必须调用open()打开。因为无名管道有两个文件数据结构:对应的VFS索引节点及共享数据页,在进程每次运行时都会创建一次,而FIFO一旦创建便一直存在。
对管道使用open()时可能会引起阻塞。若同时用读写方式(O_RDWR)打开,则不会引起阻塞;若用只读方式(O_RDONLY)打开,则open()会阻塞一直到有写方打开管道, 除非指定了非阻塞方式(O_NONBLOCK)打开;若以只写方式(O_WRONLY)打开也会阻塞到有读方打开管道。
(三) 有名管道的读写
对有名管道的读写可以直接调用read()、write()函数实现,但在管道的读写中可能有阻塞情况,我们可以在open()函数中设定非阻塞标志(O_NONBLOCK)。
1.从FIFO中读取数据。如果有进程写打开FIFO,且当前FIFO内没有数据,则对设置了阻塞标志的读操作将一直阻塞。对没有设置阻塞标志读操作来说则返回-1。对设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据或是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入。
2.向FIFO中写入数据。对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,若此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作;当要写入的数据量大于PIPE_BUF时,Linux将不保证写入的原子性,FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据。
对于没有设置阻塞标志的写操作,当要写入的数据量大于PIPE_BUF时,Linux不保证写入的原子性,在写满所有FIFO空闲缓冲区后,写操作返回;当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,若当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。
四、 结束语
管道是Linux平台下最有特色的IPC方式之一,本文介绍了Linux中管道的实现机制,并详细讨论了无名管道和有名管道的创建及使用方法,指出无名管道用于亲缘关系的进程,而有名管道则可用于任何进程之间。
参考文献:
[1] 乔静等,Linux两种管道通信方式分析及对比,电脑知识与技术,V01.6,No.24,August 2010
[2] 周超,董军军,Linux进程管道通信的研究,电脑开发与应用,2008(1)
[3] 杨水清等,ARM嵌入式Linux系统开发技术详解,北京:电子工业出版社,2008
[4] DanielP. Bovet等, 深入理解Linux内核, 中国电力出版社, 2008
作者简介:高霞,女,1980年11月,湖北十堰,武昌理工学院信息工程学院讲师,嵌入式方向
【关键词】Linux、进程通信、管道
一、管道实现机制
我们把从一个进程连接到另一个进程的数据流称为 “管道”,这是最早的Linux进程间通信机制之一。在Linux中管道常作为一种特殊文件处理。实际上,管道是内核中一個固定大小的缓冲区,它按先进先出的方式进行数据传输,一个进程向管道中写的内容会被管道另一端的进程读出。每次写入的内容都添加在管道缓冲区的末尾,且从缓冲区的头部读出数据,读写的位置自动增加,并且从管道读数据是一次性操作,数据一旦被读,便从管道中被抛弃。在缓冲区写满时,则由相应的规则控制读写进程进入等待队列,当空的缓冲区有写入数据或满的缓冲区有数据读出时,就唤醒等待队列中的读写进程继续读写。
管道分无名管道和有名管道。无名管道没有文件名,也没有磁盘节点,仅作为一个内存对象存在,用完后便销毁。无名管道没有显式的打开过程,实际上它在创建时就自动打开了,故只能由有亲缘关系的两个进程间通信使用。而有名管道克服了无名管道没有名字的限制,可由任意两个或多个进程间通信使用,它的使用方法和普通文件类似,都遵循打开、读、写、关闭的过程,只是读写的内部实现和普通文件有所不同。
二、无名管道
(一)无名管道创建
int pipe(int fd[2])在Linux中可以通过系统调用建立管道。当一个管道建立时,会创建两个文件描述符fd [0]和fd [1],其中fd [0]固定用于读管道,fd[1]固定用于写管道,如图1所示,这样就构成了一个半双工通道。
由于管道用于不同进程间通信,而调用pipe()创建的管道两端处于一个进程中,这在实际应用中没有太大意义。因此,通常先是创建一个管道,再通过调用fork()函数创建子进程,该子进程会继承父进程所创建的管道,这时,父子进程就可以共享该管道。管道的文件描述符对应关系如图2所示。此时关系看似非常复杂,实际上却给不同进程之间的读写创造了很好的条件。父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端文件描述符关闭即可。如在图3中将父进程的写端fd[1]和子进程的读端fd[0]关闭,此时,父子进程之间就建立起了一条“子进程写入父进程读取”的通道。反之,亦如此。
(二) 无名管道读写操作
创建完管道后,便可以调用I/O函数,如close、read、write等函数,对管道进行读写了,但注意与普通文件读写还有一定区别。
1.从无名管道中读取数据。如果管道写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;
当管道写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数或返回请求的字节数。
2.向无名管道中写入数据。向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,则写操作将一直阻塞。
三、有名管道
有名管道建立在实际的磁盘介质上,有自己的名字,它以FIFO的文件形式存在于文件系统中。故即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。
(一) 有名管道创建
int mkfifo(const char * pathname, mode_t mode)
该函数的第一个参数是路径名,即创建后FIFO的名字。若该参数是一个已经存在的路径名时,会返回EEXIST错误,故在调用mkfifo函数前最好先检查FIFO是否已经创建。
第二个参数与open()函数中的mode参数相同。如果程序在打开FIFO时指定了只读/只写方式,则该进程对于打开的FIFO就是一个读端/写端。如果指定的是读写方式,进程既是读端又是写端。
(二) 有名管道的打开
无名管道在创建时自动打开,但有名管道在使用时必须调用open()打开。因为无名管道有两个文件数据结构:对应的VFS索引节点及共享数据页,在进程每次运行时都会创建一次,而FIFO一旦创建便一直存在。
对管道使用open()时可能会引起阻塞。若同时用读写方式(O_RDWR)打开,则不会引起阻塞;若用只读方式(O_RDONLY)打开,则open()会阻塞一直到有写方打开管道, 除非指定了非阻塞方式(O_NONBLOCK)打开;若以只写方式(O_WRONLY)打开也会阻塞到有读方打开管道。
(三) 有名管道的读写
对有名管道的读写可以直接调用read()、write()函数实现,但在管道的读写中可能有阻塞情况,我们可以在open()函数中设定非阻塞标志(O_NONBLOCK)。
1.从FIFO中读取数据。如果有进程写打开FIFO,且当前FIFO内没有数据,则对设置了阻塞标志的读操作将一直阻塞。对没有设置阻塞标志读操作来说则返回-1。对设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其它进程在读这些数据或是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入。
2.向FIFO中写入数据。对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,若此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作;当要写入的数据量大于PIPE_BUF时,Linux将不保证写入的原子性,FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据。
对于没有设置阻塞标志的写操作,当要写入的数据量大于PIPE_BUF时,Linux不保证写入的原子性,在写满所有FIFO空闲缓冲区后,写操作返回;当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,若当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。
四、 结束语
管道是Linux平台下最有特色的IPC方式之一,本文介绍了Linux中管道的实现机制,并详细讨论了无名管道和有名管道的创建及使用方法,指出无名管道用于亲缘关系的进程,而有名管道则可用于任何进程之间。
参考文献:
[1] 乔静等,Linux两种管道通信方式分析及对比,电脑知识与技术,V01.6,No.24,August 2010
[2] 周超,董军军,Linux进程管道通信的研究,电脑开发与应用,2008(1)
[3] 杨水清等,ARM嵌入式Linux系统开发技术详解,北京:电子工业出版社,2008
[4] DanielP. Bovet等, 深入理解Linux内核, 中国电力出版社, 2008
作者简介:高霞,女,1980年11月,湖北十堰,武昌理工学院信息工程学院讲师,嵌入式方向