Linux进程间通讯 – 管道
文章目录
- Linux进程间通讯 -- 管道
- 1. 原理
- 2. 进程间通讯
- 2.1 管道
- 2.1.1 匿名管道 `pipe`
- 2.2.2 有名管道 `FIFO`
- 2.2 信号
- 2.3 共享内存
- 2.4 本地套接字
1. 原理
Linux 进程间通讯,也称为IPC(InterProcess Communication)
在 Linux 中每个进程都具备独立的进程地址空间,对每个进程的独立地址空间进行划分,在0G - 3G部分被划分为用户空间,而3G - 4G部分被划分为内核地址空间。注意此0G - 4G 为虚拟地址空间,实际上会通过MMU映射到物理地址空间。
在进行地址映射的时候,每个进程的用户空间在实际物理空间上将被映射到多个地址空间,而多个进程的内核空间将会被被映射到同一块区域,因此多个进程之间具备相同的内核地址空间,通过此共同的内核地址空间实现线程间数据交互即为进程间通讯,也即IPC。
2. 进程间通讯
Linux进程间通讯的方式主要分为以下四大种类型:
- 管道
pipe
- 信号
signal
- 共享映射区
mmap
- 本地套接字
socket
2.1 管道
使用管道实现进程间通讯的优点是:使用简单!
管道分为有名管道 FIFO
和匿名管道 pipe
,匿名管道仅能用于具备血缘关系的进程间通讯;而有名管道因其具备了名字可以被找到,因此可用于无血缘关系的进程间通讯。
管道本质是一个伪文件,是由内核管理的一个缓冲区,同时此缓冲区被设计成环形,具备两个端口,一端连接数据的写入,一端连接数据的输出,因此管道仅可用于单向通讯的场合。
当管道中无有效数据时,从管道中读取数据时进入阻塞等待状态,直至有数据从管道的写端写入。
当管道中数据被写满时,再次往管道内写入数据会进入阻塞等待状态,直至有数据从管道的读端被读走。
2.1.1 匿名管道 pipe
创建管道:
#include <unistd.h>
int pipe(int pipefd[2]);
- 传入参数:包含两个文件指针的数组,
pipefd[0]
指向管道的读端;pipefd[1]
指向管道的写端 - 返回值:0:成功 -1:失败
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd[2] = { 0 };
int ret = 0;
pid_t pid = 0;
char buf[512] = {0};
ret = pipe(fd);
if (ret < 0) {
perror("create pipe failed");
return -1;
}
pid = fork();
if (pid == 0) {
printf("----------Is child process ------------\n");
close(fd[0]); /* close the read of pipe. */
write(fd[1], "hello world!\n", sizeof("hello world!\n"));
} else {
if (pid < 0) {
perror("fork failed");
return -1;
}
printf("----------Is father process -----------\n");
close(fd[1]); /* close the write of pipe. */
while (1) {
memset(buf, 0, sizeof(buf));
ret = read(fd[0], buf, sizeof(buf));
printf("ret:%d read:%s\n", ret, buf);
sleep(1);
}
}
return 0;
}
运行结果如下:
运行分析:
- 使用
pipe
创建一个匿名管道 - 使用
fork
创建子进程,父子进程均拥有此pipe
- 子进程关闭管道读端,延时1s后往管道写端写入数据
- 父进程关闭管道写端,之后马上对管道读端读取数据,此时由于管道内没有有效数据,因此父进程发生阻塞,直至子线程往管道内写入数据
- 子进程写完数据后进程结束,对应的写端口被系统关闭,因此父进程读取管道直接返回0,不再阻塞。
2.2.2 有名管道 FIFO
创建有名管道:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 传入参数:
- pathname: 有名管道名字
- mode: 有名管道对应文件的权限(8进制),最终文件权限为
(mode & ~umask)
,通常此值使用0664
- 返回值:
- 0: success -1:fail
注意事项:
-
如果存在有名管道对应名字的文件,则会返回错误
errno = EEXIST
,因此没有做特殊处理时,重复运行此程序通常会报错。 -
创建好了有名管道之后,
open
此有名管道时,必须要求此管道的读端和写端均被打开,否则open
函数会进入阻塞状态- 如果采用只读模式
open("test_fifo", O_RDONLY)
打开,则open
函数会阻塞直至其他函数采用O_WRONLY
或O_RDWR
打开写端 - 如果采用只写模式
open("test_fifo", O_WRONLY)
打开,则open
函数会阻塞直至其他函数采用O_RDONLY
或O_RDWR
打开读端 - 如果采用非阻塞方式打开只读端口
open("test_fifo", O_RDONLY | O_NONBLOCK)
不会报错,但采用非阻塞方式打开只写接口open("test_fifo", O_WRONLY | O_NONBLOCK)
会返回No such device or address
错误 - 因此采用读写方式打开不会发送阻塞也不会报错
open("test_fifo", O_RDWR)
,但应用不一定安全!
- 如果采用只读模式
示例:
fifo_r.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char **argv)
{
int fd = 0, ret = 0;
char buf[512] = {0};
if (mkfifo("test_fifo", 0664) < 0 && errno != EEXIST) {
perror("mkfifo error");
return -1;
}
//fd = open("test_fifo", O_RDONLY | O_NONBLOCK);
fd = open("test_fifo", O_RDONLY );
if (fd < 0) {
perror("open fifo error");
return -1;
}
while (1) {
ret = read(fd, buf, sizeof(buf));
printf("read: ret:%d, buf = %s\n", ret, buf);
sleep(1);
}
return 0;
}
fifo_w.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd = 0, ret = 0;
int cnt = 0;
char buf[512] = {0};
if (mkfifo("test_fifo", 0664) < 0 && errno != EEXIST) {
perror("mkfifo error");
return -1;
}
//fd = open("test_fifo", O_RDWR);
fd = open("test_fifo", O_WRONLY);
if (fd < 0) {
perror("open fifo error");
return -1;
}
while (1) {
cnt ++;
snprintf(buf, sizeof(buf), "hello world! cnt:%d", cnt);
ret = write(fd, buf, strlen(buf)+1);
printf("write: ret:%d, buf = %s\n", ret, buf);
sleep(2);
}
return 0;
}
运行结果:
对应创建的test_fifo
文件如下
2.2 信号
//TODO:
2.3 共享内存
//TODO:
2.4 本地套接字
//TODO: