X86/Debian Linux/gcc
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。
Figure1.进程通信机制
进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道。
此笔记练习管道和FIFO。
管道是一种最基本的IPC机制,由pipe函数创建:
int pipe(int filedes[2]); |
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。
开辟了管道之后实现两个进程间的通信,一般是用下列步骤:
文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。
用mkfifo 来创建一个FIFO
mkfifo fifo ls -l fifo prw-r--r-- 1 lly lly 0 Aug 10 16:10 fifo |
FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道,各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。
UNIXDomain Socket和FIFO的原理类似,也需要一个特殊的socket文件来标识内核中的通道。这些文件在磁盘上也没有数据块。
#include <unistd.h> int pipe(int filedes[2]); |
进程通信的一种手段,见1.1。
pipe函数调用成功返回0,调用失败返回-1。
/* Filename: pipe.c * Brife: Create one pipe to be used communication beteween two progress * Author: One fish * Date: 2014.8.10 Sunday */ #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #define MAXPPSIZE 80 #define MSG "Hello, world\n" int main(void) { int n; int fd[2]; pid_t pid; char pp_content[MAXPPSIZE]; if (pipe(fd) < 0) { perror("pipe"); exit(1); } if ( (pid = fork() ) < 0 ) { perror("fork"); exit(1); } if (pid > 0) { //In parent progress write(fd[1], MSG, strlen(MSG) ); n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); } else { //In child progress ; } return 0; }
程序运行结果如下:
Hello, world |
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress write(fd[1], MSG, strlen(MSG) ); wait(NULL); } else { //In child progress n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); }
根据fork创建子进程的机制,父子进程同时拥有管道的读、写端描述符。程序运行结果如下:
Hello, world |
这个Hello world是子进程读出来并输出来的。
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress write(fd[1], MSG, strlen(MSG) ); wait(NULL); n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); } else { //In child progress n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); //Read again printf("Child read again\n"); n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); }
程序运行结果如下:
Hello, world Child read again
|
如果是在Linux字符界面下,可以看到光标在最后一行闪动,说明子进程阻塞。管道读端将数据读完后,再次从管道读端read就会遭遇阻塞。
将父进程调用fork()后的一段代码改为:
if (pid > 0) { //In parent progress write(fd[1], MSG, strlen(MSG) ); wait(NULL); printf("After child, read pipe's data which child write in:\n"); n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); } else { //In child progress printf("Read pipe's data which parent write in:\n"); n = read(fd[0], pp_content, MAXPPSIZE); write(STDOUT_FILENO, pp_content, n); //Child progress write data for parent progress write(fd[1], MSG, strlen(MSG)); }
程序运行结果如下:
Read pipe's data which parent write in: Hello, world After child, read pipe's data which child write in: Hello, world |
可见,在这个例子里面,父子进程至少可以用管道实现一个轮回的相互通信。
用mkfifo 来创建一个FIFO
mkfifo fifo ls -l fifo prw-r--r-- 1 lly lly 0 Aug 10 16:10 fifo |
/*Filename: fifo.c *Brife: Two progress communicate by fifo *Author: One fish *Date: 2014.8.10 Sunday */ #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define FIFO_FILE "fifo" #define MSG "Hello, world\n" #define MAX_CT 20 int main(void) { int fd; pid_t pid; fd = open(FIFO_FILE, O_RDWR); if (-1 == fd) { perror("open"); exit(1); } write(fd, "HW\n", 3); if ( ( pid = fork() ) < 0 ) { perror("fork"); exit(1); } if (pid > 0) { //Parent write(fd, MSG, strlen(MSG)); wait(NULL); } else { //Child int n; char buf[MAX_CT]; n = read(fd, buf, MAX_CT); write(STDOUT_FILENO, buf, n); } return 0; }
程序运行结果:
HW Hello, world |
在父进程中写write(fd, "HW\n",3);语句主要是为了测试如果子进程先运行的情况。手动多运行了几次都是以上运行结果(虽然很可能是碰巧)。