进程间通信 - 管道
管道是两个或多个相关或相互关联的进程之间的通信介质。它可以是在一个进程内,也可以是子进程和父进程之间的通信。通信也可以是多层次的,例如父进程、子进程和孙进程之间的通信等。通信是通过一个进程向管道写入和另一个进程从管道读取来实现的。要实现管道系统调用,请创建两个文件,一个用于写入文件,另一个用于从文件中读取。
管道机制可以通过实时场景来查看,例如用管道将水注入某个容器(例如水桶),然后有人将其取回(例如用杯子)。填充过程只不过是写入管道,而读取过程只不过是从管道中检索。这意味着一种输出(水)是另一种输出(水桶)的输入。
#include<unistd.h> int pipe(int pipedes[2]);
该系统调用将创建一个用于单向通信的管道,即,它创建两个描述符,第一个描述符连接到从管道读取,另一个描述符连接到写入管道。
描述符pipedes[0]用于读取,pipedes[1]用于写入。写入 Pipedes[1] 的内容都可以从 Pipedes[0] 中读取。
成功时此调用将返回零,失败时返回 -1。要了解失败的原因,请使用 errno 变量或 perror() 函数进行检查。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
尽管文件的基本操作是读取和写入,但在执行操作之前必须打开文件,并在完成所需操作后关闭文件。通常,默认情况下,每个进程都会打开3个描述符,分别用于输入(标准输入-stdin)、输出(标准输出-stdout)和错误(标准错误-stderr),文件描述符分别为0、1和2。
该系统调用将返回一个文件描述符,用于进一步的读/写/查找(lseek)文件操作。通常文件描述符从 3 开始,并随着打开的文件数量增加 1。
传递给 open 系统调用的参数是路径名(相对或绝对路径)、提及打开文件目的的标志(例如,打开以读取、O_RDONLY、写入、O_WRONLY、读取和写入、O_RDWR、附加到现有文件O_APPEND,创建文件(如果不存在则使用 O_CREAT 等)以及为用户或所有者/组/其他人提供读/写/执行权限的所需模式。模式可以用符号来提及。
读取 – 4、写入 – 2 和执行 – 1。
例如:八进制值(从0开始),0764表示所有者有读、写和执行权限,组有读和写权限,其他有读权限。这也可以表示为 S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH,表示0700|0040|0020|0004→0764的或运算。
如果成功,此系统调用将返回新的文件描述符 id,如果出现错误,则返回 -1。错误原因可以通过 errno 变量或 perror() 函数来识别。
#include<unistd.h> int close(int fd)
上述系统调用关闭已打开的文件描述符。这意味着该文件不再使用,并且关联的资源可以由任何其他进程重用。该系统调用成功时返回零,出错时返回-1。错误原因可以通过 errno 变量或 perror() 函数来识别。
#include<unistd.h> ssize_t read(int fd, void *buf, size_t count)
上述系统调用是从指定文件中读取数据,参数为文件描述符 fd、已分配内存的适当缓冲区(静态或动态)以及缓冲区的大小。
文件描述符id用于标识各个文件,在调用open()或pipe()系统调用后返回。在读取文件之前需要打开该文件。在调用 pipeline() 系统调用时它会自动打开。
如果成功,此调用将返回读取的字节数(如果遇到文件末尾,则返回零);如果失败,则返回 -1。返回字节可以小于请求的字节数,以防万一没有数据可用或文件被关闭。发生故障时会设置适当的错误号。
要了解失败的原因,请使用 errno 变量或 perror() 函数进行检查。
#include<unistd.h> ssize_t write(int fd, void *buf, size_t count)
上述系统调用是使用文件描述符 fd、已分配内存(静态或动态)的适当缓冲区以及缓冲区大小的参数写入指定文件。
文件描述符id用于标识各个文件,在调用open()或pipe()系统调用后返回。
在写入文件之前需要先打开该文件。在调用 pipeline() 系统调用时它会自动打开。
如果成功,此调用将返回写入的字节数(如果没有写入任何内容,则返回零);如果失败,则返回 -1。发生故障时会设置适当的错误号。
要了解失败的原因,请使用 errno 变量或 perror() 函数进行检查。
示例程序
以下是一些示例程序。
示例程序 1 - 使用管道写入和读取两条消息的程序。
算法
步骤 1 - 创建管道。
步骤 2 - 向管道发送消息。
步骤 3 - 从管道检索消息并将其写入标准输出。
步骤 4 - 向管道发送另一条消息。
步骤 5 - 从管道检索消息并将其写入标准输出。
注意- 也可以在发送所有消息后检索消息。
源代码:simplepipe.c
#include<stdio.h> #include<unistd.h> int main() { int pipefds[2]; int returnstatus; char writemessages[2][20]={"Hi", "Hello"}; char readmessage[20]; returnstatus = pipe(pipefds); if (returnstatus == -1) { printf("Unable to create pipe\n"); return 1; } printf("Writing to pipe - Message 1 is %s\n", writemessages[0]); write(pipefds[1], writemessages[0], sizeof(writemessages[0])); read(pipefds[0], readmessage, sizeof(readmessage)); printf("Reading from pipe – Message 1 is %s\n", readmessage); printf("Writing to pipe - Message 2 is %s\n", writemessages[0]); write(pipefds[1], writemessages[1], sizeof(writemessages[0])); read(pipefds[0], readmessage, sizeof(readmessage)); printf("Reading from pipe – Message 2 is %s\n", readmessage); return 0; }
注意- 理想情况下,需要检查每个系统调用的返回状态。为了简化流程,不会对所有调用进行检查。
执行步骤
汇编
gcc -o simplepipe simplepipe.c
执行/输出
Writing to pipe - Message 1 is Hi Reading from pipe – Message 1 is Hi Writing to pipe - Message 2 is Hi Reading from pipe – Message 2 is Hell
示例程序 2 - 使用父进程和子进程通过管道写入和读取两条消息的程序。
算法
步骤 1 - 创建管道。
步骤 2 - 创建一个子进程。
步骤 3 - 父进程写入管道。
步骤 4 - 子进程从管道检索消息并将其写入标准输出。
步骤 5 - 再次重复步骤 3 和步骤 4。
源代码:pipewithprocesses.c
#include<stdio.h> #include<unistd.h> int main() { int pipefds[2]; int returnstatus; int pid; char writemessages[2][20]={"Hi", "Hello"}; char readmessage[20]; returnstatus = pipe(pipefds); if (returnstatus == -1) { printf("Unable to create pipe\n"); return 1; } pid = fork(); // Child process if (pid == 0) { read(pipefds[0], readmessage, sizeof(readmessage)); printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage); read(pipefds[0], readmessage, sizeof(readmessage)); printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage); } else { //Parent process printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]); write(pipefds[1], writemessages[0], sizeof(writemessages[0])); printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]); write(pipefds[1], writemessages[1], sizeof(writemessages[1])); } return 0; }
执行步骤
汇编
gcc pipewithprocesses.c –o pipewithprocesses
执行
Parent Process - Writing to pipe - Message 1 is Hi Parent Process - Writing to pipe - Message 2 is Hello Child Process - Reading from pipe – Message 1 is Hi Child Process - Reading from pipe – Message 2 is Hello
使用管道的双向通信
管道通信仅被视为单向通信,即父进程写入且子进程读取,反之亦然,但不能同时执行两者。但是,如果父级和子级都需要同时从管道中写入和读取怎么办,解决方案是使用管道进行双向通信。建立双向通信需要两个管道。
以下是实现双向通信的步骤 -
步骤 1 - 创建两个管道。第一个是供父级写入和子级读取,例如 pipeline1。第二个是让孩子写,父母读,比如pipe2。
步骤 2 - 创建一个子进程。
步骤 3 - 关闭不需要的端点,因为每次通信只需要一端。
步骤 4 - 关闭父进程中不需要的末端,读取管道 1 的末端并写入管道 2 的末端。
步骤 5 - 关闭子进程中不需要的末端,写入管道 1 的末端并读取管道 2 的末端。
步骤 6 - 根据需要执行通信。
示例程序
示例程序 1 - 使用管道实现双向通信。
算法
步骤 1 - 创建 pipeline1 供父进程写入和子进程读取。
步骤 2 - 创建 pipeline2 供子进程写入和父进程读取。
步骤 3 - 从父端和子端关闭管道不需要的末端。
步骤 4 - 父进程写入消息,子进程读取并显示在屏幕上。
步骤 5 - 子进程写入消息,父进程读取并显示在屏幕上。
源代码:twowayspipe.c
#include<stdio.h> #include<unistd.h> int main() { int pipefds1[2], pipefds2[2]; int returnstatus1, returnstatus2; int pid; char pipe1writemessage[20] = "Hi"; char pipe2writemessage[20] = "Hello"; char readmessage[20]; returnstatus1 = pipe(pipefds1); if (returnstatus1 == -1) { printf("Unable to create pipe 1 \n"); return 1; } returnstatus2 = pipe(pipefds2); if (returnstatus2 == -1) { printf("Unable to create pipe 2 \n"); return 1; } pid = fork(); if (pid != 0) // Parent process { close(pipefds1[0]); // Close the unwanted pipe1 read side close(pipefds2[1]); // Close the unwanted pipe2 write side printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage); write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage)); read(pipefds2[0], readmessage, sizeof(readmessage)); printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage); } else { //child process close(pipefds1[1]); // Close the unwanted pipe1 write side close(pipefds2[0]); // Close the unwanted pipe2 read side read(pipefds1[0], readmessage, sizeof(readmessage)); printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage); printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage); write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage)); } return 0; }
执行步骤
汇编
gcc twowayspipe.c –o twowayspipe
执行
In Parent: Writing to pipe 1 – Message is Hi In Child: Reading from pipe 1 – Message is Hi In Child: Writing to pipe 2 – Message is Hello In Parent: Reading from pipe 2 – Message is Hello