Linux内核为每个用户进程开辟了独立的内存空间,各个进程间互不影响,任何一个进程的全局变量,其他进程都访问不到。此种情况下进程间要通信只能通过内核,在内核空间中开辟一块内存空间作为缓存区,进程1将数据从用户空间拷贝到缓存区,进程2将数据从缓冲区拷贝到用户空间,从而实现进程间数据的交换。内核提供的这种机制称为进程间通信(IPC)
1. 进程间的通信方式有:
管道(无名管道和有名管道)
信号
消息
共享内存
套接字
2. 无名管道通信
2.1 无名管道通信
管道是一种较早的进程间通信实现方式之一,无名管道通信要求进程间具有亲缘关系,比如父子进程,兄弟进程。因为这样的进程拥有同一个父进程所创建的管道文件。父子进程通过管道通信的过程大致如下:
1. 父进程调用pipe()函数在内核中开辟一个缓冲区(称为管道),并返回两个文件描述符filedes[0]执行管道的读端, 和filedes[1]执行管道的写端
#include <unistd.h> int pipe(int filedes[2]);
2. 父进程调用fork()创建子进程,子进程继承了父进程的文件描述符。此时父子进程的文件描述符的读、写端均指向了同一个管道。
3. 若父进程写端向管道拷贝数据,则子进程读端就可以从管道读取数据
示例:
#include <unistd.h> int main(void) { int fd[2]; pid_t pid; char line[12]; pipe(fd); pid = fork(); //create childe process if (pid > 0) { close(fd[0]); write(fd[1], "Hello world\n", 12); } else { close(fd[1]); read(fd[0], line, 12); write(STDOUT_FILENO, line, 12); } return 0; }
2.2 shell管道的实现
在shell中我们经常使用管道将一个命令的标准输出传到另一个命令的标准输入中, 这其中就使用到了无名管道通信机制。
ls -1 | wc -l
其实现过程是:
1. shell 调用fork()创建进程1
2. 进程1调用pipe()在内核中开辟缓存区(管道),并返回两个文件描述符fd[0](连接管道的读端),fd[1]写端(连接管道的写端)
3. 进程1调用fork()创建子进程(姑且称为进程2),进程2继承父进程的文件描述符
4. 进程1调用dup2()将文件描述符1(标准输出)绑定到文件描述符fd[1](连接管道的写端)上
5. 进程1调用两次close()关闭fd[0]和fd[1]
6. 进程1调用exec系列函数执行ls -1命令
7. 进程2调用dup2()将文件描述符0(标准输入)绑定到文件描述符fd[0](连接管道的读端)上
8. 进程2调用两次close()关闭fd[0]和fd[1]
9. 进程2调用exec系列函数执行wc -l命令
示例:
#include <unistd.h> int main(void) { int fd[2]; pipe(fd); if (fork() == 0) { dup2(fd[0],0); close(fd[1]); close(fd[0]); execl("/usr/bin/wc","wc","-l",NULL); } else { dup2(fd[1],1); close(fd[0]); close(fd[1]); execl("/bin/ls","ls","-1",NULL); } return 0; }
3. 有名管道
无名管道为了继承相同的文件描述符,要求通信的进程具有亲缘关系方可。为了实现无此关系的进程也可以通过管道进行通信,我们可以使用有名管道通信FIFO, shell模式下通过命令可以实现:
1. 打开两个终端,在终端1下创建FIFO文件file1, 并从标准输入中读取一段字符,此时会发现进程处于阻塞状态,等待另外一个进程从管道中将字符读走。
$ mkfifo file1
$ echo "Hello World" > file1
2. 另外一个终端中读取管道file1中的字符,此时再查看发现进程1不再阻塞。
# cat < file1 Hello World