一、匿名管道
pipe的基本描述和用法
匿名管道是linux中的一种通信方式,为有血缘关系的进程提供数据通信。相关的api函数为:
#include <unistd.h>
int pipe(int pipefd[2]);
pipe
函数传入一个长度为2的int数组,执行成功将在内核区域分配一块内存区域,并设置pipefd为管道的文件描述符。
创建子进程后,子进程也会继承这两个描述符,对于同一个文件描述符来说,父端写,子端就可以读,字段写,父端也可以读。
工作流程可以描述为:
但是由于管道内部实现是用的环形队列,因此父子不能同时对一个管道进行读操作或者写操作,队列是单向的,两端写就有问题。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#define SEND_BUFF "HelloWorld"
#define CLOSE_FD(fd) \
do { \
if (fd != -1) { \
close(fd); \
fd = -1; \
} \
} while (0)
int main(){
int fd[2], ret, n;
char buf[1025];
ret = pipe(fd);
if (ret == -1)
goto _err;
memset(buf, 0, 1025);
pid_t pid = fork();
if (pid == -1) {
goto _err;
} else if (pid == 0) {
// 子端关闭fd1,打开fd0读
CLOSE_FD(fd[1]);
n = read(fd[0], buf, 1024);
if (-1 == n) goto _err;
printf("child read %d byte data: %s\n", n, buf);
CLOSE_FD(fd[0]);
} else {
// 父端关闭fd0,打开fd1写
CLOSE_FD(fd[0]);
n = write(fd[1], SEND_BUFF, sizeof(SEND_BUFF));
if (n == -1) goto _err;
printf("parent send %d bytes data: %s\n",
sizeof(SEND_BUFF), SEND_BUFF);
// 等待子进程退出
wait(0);
CLOSE_FD(fd[1]);
}
return 0;
_err:
perror("Error");
CLOSE_FD(fd[0]);
CLOSE_FD(fd[1]);
return -1;
}
1.2 非阻塞pipe
管道还有一个函数pipe2
提供了更高级的选项,可以使得管道成为非阻塞读:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
其中,当flags置为O_NONBLOCK
时,管道为非阻塞,从非阻塞的管道读取数据时,如果此时没有数据可读,将返回-1,并设置errno为EAGAIN
。
修改子进程部分代码为:
if (pid == 0){
CLOSE_FD(fd[1]);
while (1) {
n = read(fd[0], buf, 1024);
if (-1 == n) {
if (errno == EAGAIN) {
printf("READ AGAIN\n");
// 没有数据可读时,休眠一秒,继续读
sleep(1);
continue;
}
goto _err;
}
printf("child read %d byte data: %s\n", n, buf);
break;
}
CLOSE_FD(fd[0]);
}
父进程在写数据前先睡眠5秒:
else {
CLOSE_FD(fd[0]);
sleep(5);
n = write(fd[1], SEND_BUFF, sizeof(SEND_BUFF));
// ...
}
运行结果:
1.3 管道数据读取的原则
- 如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
- 如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
- 如果所有指向管道读端的文件描述符都关闭,这时有进程指向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
- 如果有指向管道读端的文件描述符没关闭,而持有管道写端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再write会阻塞,直到管道中有空位置了才写入数据并返回。
二、有名管道fifo
2.1 fifo的基本用法
fifo管道是linux文件系统中的一种文件类型,创建在磁盘上,任何进程都可以打开这个文件进行读写。
它可以解决无血缘关系进程的通信问题,创建管道的函数为:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
当管道文件被创建之后,就可像文件一样打开它进行读写:
// read.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>
#include <errno.h>
#include<unistd.h>
#define FIFO_FILE "fifo"
#define BUFF_SIZE 1024
int main() {
int ret, fd = -1;
char buf[BUFF_SIZE] = { 0 };
ret = mkfifo(FIFO_FILE, 0755);
if (ret == -1 && errno != EEXIST)
goto _err;
fd = open(FIFO_FILE, O_RDONLY);
if (fd == -1)
goto _err;
bzero(buf, BUFF_SIZE);
ret = read(fd, buf, sizeof(buf) - 1);
if (ret == -1)
goto _err;
printf("read: %s\n", buf);
close(fd);
fd = -1;
return 0;
_err:
perror("Error");
if (fd > 0) {
close(fd);
fd = -1;
}
return -1;
}
写管道程序:
// write.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#define FIFO_FILE "fifo"
int main(){
int ret, fd;
fd = open(FIFO_FILE, O_WRONLY);
if (fd == -1)
goto _err;
unlink(FIFO_FILE);
ret = write(fd, "HelloWorld", 11);
if (ret == -1)
goto _err;
close(fd);
fd = -1;
return 0;
_err:
perror("Error");
if (fd > 0) {
close(fd);
fd = -1;
}
return -1;
}
此时运行./read
,会创建管道文件,新开一个窗口就可以看到,管道的颜色和普通文件不一样,并且ls的第一个字符是p
:
默认情况下,读是阻塞的(类似于读文件),直到有数据过来为止,所以./read
执行后,会阻塞,直到运行./write
之后才会收到数据。
read: HelloWorld
2.2 非阻塞pipe
非阻塞pipe有一个注意事项:当打开的管道没有其他进程也打开的话(即只有当前进程在读,没有其他进程了),直接返回0,不会返回EAGAGIN
。
// 加上O_NONBLOCK标记
fd = open(FIFO_FILE, O_RDONLY | O_NONBLOCK);
if (fd == -1)
goto _err;
bzero(buf, BUFF_SIZE);
printf("fd = %d\n", fd);
while (1) {
ret = read(fd, buf, sizeof(buf) - 1);
if (ret == -1) {
if (errno == EAGAIN) {
printf("READ AGAIN\n");
sleep(1);
continue;
}
goto _err;
} else if (ret == 0) {
printf("read end\n");
close(fd);
fd = -1;
break;
} else {
printf("read: %s\n", buf);
}
}
编译后如果直接执行,程序会直接退出,因为此刻没有其他进程在写,不会返回EAGAIN
此处评论已关闭