Linux 进程间通信
Linux 管道
管道是允许单向通信的通信设备。数据从管道的一端写入并从管道的另一端读出。管道是串行设备;数据总是以写入时的顺序被读取出来。通常,管道用于同一进程的两个不同线程或在父子进程间通行。
在shell中,使用|符号创建管道。例如,下面的shell命令将导致shell创建两个子进程,分别用于ls和less命令:
% ls | less
shell同时也创建了一个管道用于连接ls子进程的标准输出和less进程的标准输入。通过ls列出的文件名传输到less中,并且其顺序与它直接在终端显示一致。
管道的数据容量是有限的。如果写进程速度快于读进程读取数据,并且管道不能存储过多的数据,那么写进程将会阻塞直到有更多的容量可以使用。因此,管道将自动同步两个进程。
创建管道
要创建一个管道,调用pipe命令。指定一个大小为2的整数数组。Pipe调用将读文件描述符存放在数组第0号位置,写文件描述符存放在数组第1号位置。例如,考虑下面的代码:
int pipe_fds[2]; int read_fd; int write_fd; pipe(pipe_fds); read_fd = pipe_fds[0]; write_fd = pipe_fds[1];
通过write_fd文件描述符写入的数据可以通过read_fd文件描述符读取回来。
父子进程间通信
通过pipe创建的文件描述符只在当前进程和其子进程中有效。进程文件描述符不能传递给不相关进程;然而,当进程调用fork时,文件描述符被复制到子进程中。因此,管道只能连接相关进程。
如下列出的程序中,fork创建一个子进程。子进程继承父进程的管道文件描述符。父进程写入字符串在管道中,子进程把它读取出来。这个程序使用fdopen将文件描述符转换为FILE*的文件流。因为使用流而不是文件描述符,可以让我们使用高层的标准C的I/O库函数,例如printf和fgets。
#include <stdlib.h> #include <stdio.h> #include <unistd.h> /* Write COUNT copies of MESSAGE to STREAM, pausing for a second * between each. */ void writer(const char *message, int count, FILE *stream) { for (; count > 0; --count) { /* Write the message to the stream, and send it off immediately. */ fprintf(stream, "%s\n", message); fflush(stream); /* Snooze a while. */ sleep(1); } } /* Read random strings from the stream as long as possible. */ void reader(FILE *stream) { char buffer[1024]; /* Read until we hit the end of the stream. fgets reads until * either a newline or the end-of-file. */ while (!feof(stream) && !ferror(stream) && fgets(buffer, sizeof(buffer), stream) != NULL) fputs(buffer, stdout); }<pre name="code" class="cpp">int main() { int fds[2]; pid_t pid; /* Create a pipe. File descriptors for the two ends of the * pipe are placed in fds. */ pipe(fds); /* Fork a child process. */ if ((pid = fork()) == 0) { FILE *stream; /* This is the child process. Close our copy the write end of * the file descriptor. */ close(fds[1]); stream = fdopen(fds[0], "r"); reader(stream); close(fds[0]); } else if (pid == -1) { } else { /* This is the parent process. */ FILE *stream; /* Close our copy of the read end of the file descriptor. */ close(fds[0]); stream = fdopen(fds[0], "r"); reader(stream); close(fds[1]); } return 0; }
在main函数的开始,声明大小为2的整数数组。通过pipe创建管道,并将读写文件描述符放在数组中。程序接着创建子进程。在关闭了读管道后,父进程开始向管道中写入字符串。子进程在关闭写管道后,从读管道中读出字符串。
注意,在writer函数每次写入数据后,父进程通过调用fflush刷新管道。否则,字符串可能不会字符通过管道发送出去。
当调用ls | less命令时,出现两个进程:一个ls子进程和一个less子进程。这两个进程都继承管道文件描述符,因此,它们可以通过管道通信。为了使不相关的进程通信,可以使用FIFO代替。
重定向标准输入、输出和错误输出
通常,你需要创建一个子进程,并设置它管道的一端为标准输入或输出。使用dup2函数,你可以将一个文件描述符等同于另一个文件描述符。例如,为了将fd文件描述符的输入重定向到标准输入,可以使用以下方式:
dup2 (fd,STDI_FILENO);
常量STDIN_FILENO代表标准输入的文件描述符,其值为0。该函数关闭标准输入,随后作为fd的副本重新打开标准输入,这样,这两个文件描述符就可交换使用。Equated file descriptors share the same file position and the sameset of file status flags.Thus, characters read from fd are not reread fromstandard input.
popen和pclose
管道通常用于向程序的子进程发送或接收数据。popen和pclose函数简化了调用pipe,fork,dup2,exec和fdopen。
#include <stdio.h> #include <unistd.h> int main() { FILE *stream = popen("sort", "w"); fprintf(stream, "This is a test.\n"); fprintf(stream, "Hello, world.\n"); fprintf(stream, "My dog has fleas.\n"); fprintf(stream, "This program is great.\n"); fprintf(stream, "One fish, two fish.\n"); return pclose(stream); }
popen函数创建子进程执行sort命令。第二个参数“w”表明该进程想要向子进程中写入数据。popen函数的返回值是管道的一端;另一端用于连接子进程的标准输入。在写入数据完成后,调用pclose函数关闭子进程的流,等待进程终止,并返回状态值。
函数popen的第一个参数的执行类似于执行shell命令。shell按照通常的方式搜寻PATH环境变量找到程序执行。如果第二个参数是“r”,函数返回子进程的标准输出流,这样,父进程便可读取输出。如果第二个参数是“w”,函数返回子进程的标准输入流,父进程便可写入数据。如果出现错误,函数popen返回NULL指针。
调用pclose关闭由popen返回的流。在关闭指定的流后,pclose等待子进程终止。
FIFOs
先进先出(FIFO)文件是在文件系统中有名称的管道。任何进程都可打开或关闭FIFO;管道两端的进程不需要彼此相关。FIFOs通常也被称为命名管道。
要创建命名管道,可以使用mkfifo命令。在命令行上指定命名管道的路径。例如,创建一个/tmp/fifo命名管道:
% mkfifo /tmp/fifo % ls –l /tmp/fifo prw-rw-rw- 1 samuel users 0 Jan 16 14:04 /tmp/fifo
ls命令输出的第一个字符是p,表示这个文件实际上是FIFO(命名管道)文件。在一个窗口中,通过下面的命令读取FIFO:
% cat < /tmp/fifo
在另一个窗口中,通过下面的命令向FIFO写入数据:
% cat > /tmp/fifo
接着,输入一些文本。每次按下Enter键时,一行的文本内容通过FIFO发送并在第一个窗口中显示。通过在第二个窗口中按下Ctrl+D关闭FIFO。通过以下命令删除FIFO:
% rm /tmp/fifo
1 创建FIFO
通过mkfifo以编程的方式创建FIFO。第一个参数是FIFO创建的路径;第二个参数指定管道的拥有者,组和权限。因为,关闭必定有读和写,因此权限必须包含读和写的权限。如果管道不能创建(例如,如果该文件已经存在),mkfifo返回-1。如果调用mkfifo,需要包含<sys/types.h>和<sys/stat.h>。
2 访问FIFO
对FIFO的访问就像访问普通文件一样。通过FIFO通信,一个程序必须以写的方式打开,另一个程序则以读的方式打开。低级I/O函数或者标准C库I/O函数都可使用。
例如,使用低级I/O向FIFO中写入数据,可使用下述代码:
int fd = open(fifo_path, O_WRONLY); write(fd, data, data_length); close(fd);
使用标准C库I/O函数读取FIFO中的数据,可使用下述代码:
FILE *fifo = fopen(fifo_path, “r”); fscanf(fifo, “%s”, buffer); fclose(fifo);
一个FIFO可以有多个读进程或写进程。Bytesfrom each writer are written atomically up to a maximum size of PIPE_BUF (4KBon Linux). Chunks from simultaneous writers can be interleaved. Similar rulesapply to simultaneous reads.
3 与Windows命名管道的不同
Win32操作系统中的管道类似与Linux中的管道。主要的区别集中在命名管道上,对于Win32来说,命名管道的功能更像是套接字。Win32命名管道可以通过网络连接分离的计算机的进程。同样,Win32允许多个读写连接到同一命名管道上,并不会存在交叉数据,并且支持双向通信。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。