1. 进程通信概述
Linux下进程通信的八种方法:
-
匿名管道(pipe),
-
命名管道(FIFO),
-
内存映射(mapped memeory),
-
消息队列(message queue),
-
共享内存(shared memory),
-
信号量(semaphore),
-
信号(signal)
-
套接字(Socket)
2. 匿名管道(pipe)
匿名管道用于进程之间通信,且仅限于本地父子进程之间通信,结构简单
- 只提供单向通信,也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西,那么进程2 就只能读取文件的内容。
- 只能用于具有血缘关系的进程间通信,通常用于父子进程建通信
- 管道是基于字节流来通信的
- 依赖于文件系统,它的生命周期随进程的结束结束(随进程)
- 其本身自带同步互斥效果
在LINUX编程中匿名管道通常使用pipe系统调用完成。shell命令行中**管道符|**的本质也是匿名管道
2.1 pipe
用法:
int pd[2];
int r = pipe(pd);
pipe()
函数在内核中创建一个管道并在传入参数pd[2]
中返回两个文件描述符,其中pd[0]
用于从管道读取,pd[1]
用于从管道写入数据。如果成功返回0,失败返回-1。
需要注意,管道是多进程间通信的概念,因此在一个进程中通过pipe系统调用创建一个管道后并不能直接从中读取数据,会永远等待下去。因为创建管道后该管道中没有数据,此时读取端进程读数据时需要先等待写入端进程写入数据后才能读。然而这里写进程和读进程都是同一个进程,因此会永远等待。因此进程只能是管道的一个读进程或者写进程之一,而不能二者都是。
因此正确方式是创建一个管道后fork
复刻一个子进程来共享该管道。由于fork的子进程会继承父进程的所有打开文件描述符,因此子进程也有pd[0]
(读取端)和pd[1]
(写入端)。然后将父进程和子进程分别指定为该管道的读取端或者写入端(即为将一个进程指定为管道的读取端,另一个进程指定为该管道的写入端),且父子进程都要关闭掉他不需要的文件描述符。例如父进程指定为写进程,子进程指定为读进程。那么父进程关闭掉pd[0]
,子进程关闭掉pd[1]
。之后就可以父进程向管道写数据,子进程从该管道读取数据。
示例:父进程创建管道并fork子进程,指定父进程为管道写入端、子进程为读取端,进程父子进程间单向通信
main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
int pd[2];
int r = pipe(pd);//创建管道
printf("pipe: read: %d write: %d\n",pd[0],pd[1]);
int pid = fork();//fork子进程
if(pid){
//父进程执行此部分
close(pd[0]); //设置父进程为管道的写入端,因此关闭读取端文件描述符pd[0]
char buf[] = "this is some datas";
sleep(3); //父进程sleep三秒
printf("parent Process begin write datas\n");
write(pd[1],buf,strlen(buf)); //向管道写入数据
printf("parent Process write datas done\n");
}
else{
//子进程执行此部分
close(pd[1]); //设置子进程为管道的读取端,因此关闭写入端文件描述符pd[1]
char buf[30];
int num = read(pd[0],buf,sizeof(buf)-1); //从管道读取数据
buf[num] = '\0'; //字符串结尾
printf("son Process get %d datas : %s\n",num,buf);
}
}
运行结果
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ gcc pipe_test.c
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ ./a.out
pipe: read: 3 write: 4
parent Process begin write datas
parent Process write datas done
son Process get 18 datas : this is some datas
可见父进程成功将数据写入管道,子进程成功从管道读出数据。由于父进程在写入前sleep了三秒,可以证实管道读取端要读数据时,如果管道为空且存在写进程时会等待写入端写入数据后再读取。
2.2 管道命令
管道符号:|
用法:
cmd1 | cmd2
cmd1
的输出编程cmd2
的输入,这是通过管道实现的。sh通过一个进程运行cmd1,一个进程运行cmd2,它们之间通过一个管道连在一起完成进程间通信。
其具体实现逻辑大致如下:
-
sh
进程获取命令行cmd1 | cmd2
,复刻出一个子进程sh并等待子进程sh终止 -
该子进程sh执行以下代码:需要注意exec更改进程执行映像并不会关闭原先进程已经打开的文件描述符,因此exec之前打开的文件描述符在更改执行映像后还能继续使用(除非该文件描述符是
FD_CLOEXEC
)。close-on-exec
标志(FD_CLOEXEC
):内核为每个文件描述符提供了执行时关闭标志,当exec()
执行成功之后,会自动关闭设置了FD_CLOEXEC
标志的文件描述符,如果exec()
调用失败,文件描述符依然会保持打开状态int pd[2]; pipe(pd);//创建管道 int pid = fork();//复刻一个子进程 if(pid){ //父进程执行此部分