Linux两种管道通信方式的分析

2023-09-06 05:43邓飞蔡波
现代信息科技 2023年14期

邓飞 蔡波

摘  要:Linux系统中管道通信是从Unix系统继承的一种通信方式,管道是操作系统内核管理的一个内存缓冲区,采用半双工的通信方式。由于管道是临界资源,所以进程要互斥地访问管道,管道分为无名管道和命名管道。文章分析了Linux进程之间采用无名管道和命名管道通信的特点,并对无名管道的父子进程、兄弟进程以及命名管道通信进行了研究对比。

关键词:进程通信;临界资源;无名管道;命名管道

中图分类号:TP311     文献标识码:A   文章编号:2096-4706(2023)14-0054-03

Analysis of Two Pipe Communication Ways of Linux

DENG Fei, CAI Bo

(Chengdu Colledge of University of Electronic Science and Technology of China, Chengdu  611731, China)

Abstract: Pipe communication in Linux system is a communication mode inherited from Unix system. The pipe is a memory buffer managed by the operating system kernel, and it adopts half-duplex communication mode. Because pipe is a critical resource, process should access the pipe file mutually exclusive. Pipes are divided into anonymous pipe and named pipe. This paper analyzes the characteristics of pipe communication mainly adopts the anonymous pipe and named pipe between Linux Processes. The research and comparison of the parent-child process, sibling process and the anonymous pipe and named pipe communication is carried out.

Keywords: process communication; critical resource; anonymous pipe; named pipe

0  引  言

Linux管道是由操作系统内核管理的一个内存缓冲区,该缓冲区以循环队列结构采用先进先出方式的传输数据,即管道采用某一方向的方式传输数据,一个进程连接管道输入端,该进程会向管道末端写入数据;另一个进程连接管道的输出端,该进程会读取被放入管道的数据;而且管道中的数据只能被读取一次,即不能重复读取[1],数据所占用空间被读走数据以便下次留给写进程写入数据。由于管道在进程通信过程中数据被存放在内存缓冲区,缓冲区是临界资源,所以为了保证读写进程对缓冲区里数据正确访问,对管道需要互斥访问[2]。虽然管道并不能像普通磁盘文件存放数据,但可以被看成特殊的文件,也可以使用读、寫、关闭等系统函数访问管道[3]。Linux管道分为无名管道和命名管道两种。本文主要围绕Linux无名管道的父子进程、兄弟进程通信以及命名管道通信特点进行研究对比。

1  无名管道通信机制

无名管道并不是真正的外存磁盘文件,实际为系统内核缓冲区。具有血缘关系的两个进程只能使用无名管道通信,指具有一个共同祖先的两个进程之间才能利用无名管道通信,所以无名管道可以应用在父子、兄弟进程之间的通信[4]。

由于无名管道没有文件名,所以无名管道是通过文件描述符方式控制读写端来实现通信,当进程新建管道时,系统会给调用pipe函数的进程分配文件描述符fd [0]和fd [1],一般情况下从管道读取数据使用fd [0]端,而往管道写入数据使用fd[1]端[5],这样就形成了一条半双工的拥有固定的读端和写端数据传输通道。

在实际使用无名管道通信时,系统给新建管道的进程返回文件描述符fd[0]和fd[1],接着新建子孙进程,子孙进程会继承文件描述符fd[0]和fd[1],这些有血缘关系的进程都有自己的读写端,这样便可以实现它们共享该管道[6],为了实现它们中任意两个进程通信就需要保留相应的读、写端,将多余的读、写端对应的文件描述符关闭就可以了。

下面分别对父子、兄弟进程的无名管道通信进行分析。

1.1  父子进程之间的管道通信分析

一般情况在利用无名管道通信的父子进程之间建立起一条“子进程写入父进程读取”的通道,如图1所示。父进程调用pipe()函数新建管道,系统给父进程指定文件描述符fd[0]和fd[1],接着创建子进程,子进程会继承父进程的文件描述符fd[0]和fd[1],父子进程共享该管道;子进程保留文件描述符fd[1]向管道写入数据而关闭自己的读取端fd[0],而父进程保留文件描述符fd[0]从管道读取数据而关闭自己的写入端fd[1],这样就建立了一条通信通道。

图1  父子进程之间管道通信示意图

父进程调用fork()创建子进程,父进程保留读端fd[0],子进程保留写端fd[1],其余读写端口关闭。子进程调用函数write(fd[1], s, strlen((const char*)s))将s指向的内存数据写入管道,fd[1]为子进程的写入端;父进程调用函数read(fd[0],buf,size)从管道读走数据,fd[0]为父进程的读端;size为管道对应的内核缓冲区大小。下面为通信父、子进程的部分代码:

(1)/*子进程的部分代码*/

{ /* 子进程关闭读描述符*/

close(fd[0]);

/*子进程向管道写入real_write字节数据 */

real_write = write(fd[1], s, strlen((const char*)s)))

/* 完成所有写任务后关闭写端 */

close(fd[1]); }

(2)/*父进程的部分代码*/

{ /*父进程关闭写端*/

close(fd[1]);

/*父进程从管道中读走real_read字节的数据*/

real_read = read(fd[0], buf, size);

/*完成所有读任务后关闭读取端*/

close(fd[0]); }

1.2  兄弟进程之间的管道通信分析

由于无名管道应用在有血缘关系的进程之间通信,所以无名管道也能在兄弟进程之间实现通信。兄弟进程利用管道通信如图2所示,父进程调用pipe()函数新建管道,系统给父进程指定文件描述符fd[0]和fd[1],接着由父进程新建子进程A、B,两个子进程A、B都继承父进程的文件描述符fd[0]、fd[1],这样父进程和子进程A、B三个进程共享无名管道,即三个进程的文件描述符fd[0]、fd[1]分别和管道两端相连接。

图2  兄弟进程之间管道通信示意图

父进程调用fork()创建子进程A、B,子进程A保留写端fd[1],子进程B保留读端fd[0],其余读写端口关闭,父进程的读写端都关闭,这样形成“子进程A写入,子进程B读走数据”的通信通道。子进程A调用函数write(fd[1], s, strlen((const char*)s))将s指向的内存数据写入管道,fd[1]为子进程A的写入端;子进程B调用函数read(fd[0], buf, size)从管道读走数据,fd[0]为子进程B的读端;size为管道缓冲区大小。下面为通信子进程A、B的部分代码:

(1)/*子进程A的部分代码*/

{ /* 子进程A关闭读描述符*/

close(fd[0]);

/* 子进程A向管道写入real_write字节数据 */

real_write = write(fd[1], s, strlen((const char*)s)))

/* 子进程A完成所有写任务后关闭写端 */

close(fd[1]); }

(2)/*子进程B的部分代码*/

{ /*子进程B关闭写端*/

close(pipe_fd[1]);

/* 子进程B向从管道读走real_read字节数据 */

real_read = read(fd[0], buf, size);

/* 完成所有读任务后关闭读端 */

close(pipe_fd[0]); }

2  命名管道通信分析

只是无名管道才能实现血缘关系的进程通信,为了实现无血缘进程通信而提出命名管道。命名管道雖然在外存磁盘上有文件标识,但是利用命名管道通信的两个进程传输的数据并不会存放在磁盘文件中,而是存放在内存缓冲区,命名管道对应外存磁盘上的具体路径下文件,如图3所示,/home/FIFO表示在路径/home下的命名管道文件FIFO。

图3  命名管道通信示意图

命名管道/home/FIFO被创建后,利用命名管道通信的两个进程就可以将命名管道FIFO看成一个磁盘文件来访问,这样传输数据时会调用函数open()、read()和write()和close()等来访问命名管道。由于命名管道采用队列先进先出方式来处理数据,只能单向传送,所以对命名管道实现写功能时将数据添加到管道尾部,实现读功能时从命名管道首部读取数据。

读、写进程访问命名管道FIFO有阻塞和非阻塞两种方式:

1)采用阻塞方式访问命名管道时,对于写进程,在读进程读完管道里数据之前写进程会一直阻塞;而对于读进程,命名管道FIFO中没有数据或写进程没有完成写操作之前读进程会一直阻塞;

2)采用非阻塞方式访问命名管道时,对于写进程,在读进程读完管道里数据之前,写操作只能部分数据写入管道或写操作失败;对于读进程而言,无论管道FIFO有无数据时都会执行读操作,只是当管道FIFO中没有数据时读操作结果返回0而已。

由于管道为临界资源,为了实现互斥访问命名管道,保证数据能正确传输,常常对命名管道实现读、写操作时采用阻塞方式[6]。

以上面图4里创建的命名管道/home/FIFO为例,读、写进程采用阻塞方式打开管道,写进程调用函数write (fd,buf,size)把写进程缓冲区的数据写入管道,读进程调用函数read(fd,buf,size)从管道读取数据到读进程缓冲区,size为管道缓冲区大小,fd指向命名管道/home/FIFO。

1)写进程以只写阻塞方式打开/home/FIFO管道:fd = open(/home/FIFO,O_WRONLY);

向管道中写入nwrite字节数据:nwrite = write(fd,buff,size);

2)读进程以只读阻塞方式打开/home/FIFO管道:fd = open(/home/FIFO,O_RDONLY);

从管道中读走nread 字节数据:nread = read(fd,buff,size)。

3  无名管道和命名管道的异同

无名管道的特点:1)只有血缘关系的进程才能访问无名管道;2)无名管道通过控制文件描述符确定管道的读、写端;3)无名管道不是普通的磁盘文件,通信时传输的数据存放在内存缓冲区。

命名管道的特点:1)命名管道可以在任何没有关联的两个进程之间通信;2)命名管道以磁盘文件形式存在,通信过程中数据存放在内存缓冲区;3)不支持定位lseek()操作。

无名管道和命名管道的特点对比如表1所示。

4  结  论

综上所述,无名管道并不是位于外存的磁盘文件,实际是一个内核缓冲区。无名管道只能应用在有血缘关系的进程之间传输数据,它们共享无名管道,通过控制文件描述符来实现对无名管道的访问。

命名管道虽然被标识成位于外存的一个磁盘文件,但它并不占用磁盘空间,而是与内核缓冲区关联,利用命名管道通信的进程可以无任何关系,只要进程都能够通过路径访问该命名管道就可以实现通信。不管无名管道还是命名管道在通信过程中,数据都存放在内核缓冲区。无名管道和命名管道互为补充,这样就让管道通信体现了其独有通信优势。

参考文献:

[1] 刘玓,陈佳,肖堃,等.Linux操作系统应用编程 [M].北京:人民邮电出版社,2021.

[2] 赵宏,庞伟业,袁继泉,等.Linux教学中进程之间通过特殊文件通信的解析 [J].计算机时代,2022(10):123-126.

[3] 赵宏,朱忠政,常兆斌.Linux系统教学中关于命名管道文件的解析 [J].软件,2020,41(2):108-110.

[4] 乔静,刘宝旨,屈志强,等.Linux中命名管道通信淺析 [J].中国科技信息,2009(20):97-98.

[5] 张龙.Linux下管道通信的实现 [J].企业技术开发,2010,29(19):8-9.

[6] 段莹,管涛.Linux进程间管道通信的研究 [J].软件导刊,2012,11(7):3-5.

作者简介:邓飞(1972—),男,汉族,四川眉山人,讲师,硕士研究生,研究方向:云计算和信息安全;蔡波(1984—),男,汉族,四川南充人,助教,本科,研究方向:计算机网络和信息安全方向。