前言
在 Linux 系统中,管道(pipe)是一种最基础的进程间通信方式,它就像一根虚拟的通道,让一个进程写入的数据能够被另一个进程读取。借助管道,不同的进程可以协同工作,实现数据的传递与共享。这种机制不仅在命令行中被广泛使用(例如 ls | grep txt),也是理解 Linux 通信原理和系统编程的重要起点。
一,管道是什么?
管道是连接两个进程的桥梁,它允许一个进程把数据传递给另一个进程,从而实现进程间的通信。

二,匿名管道
匿名管道就是通过 pipe() 创建的“临时通道”,只能在相关进程之间使用,不能像命名管道那样跨无关进程访问。
pipe()在unistd.h头文件下
#include<unistd.h>
int pipe(fd[2]);
//fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
//返回值:成功返回0,失败返回错误代码
演示代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int fds[2];
char buf[100];
int len;
if(pipe(fds)==-1)
{
perror("make pipe");
exit(1);
}
printf("%d and %d\n", fds[0], fds[1]);
while(fgets(buf,100,stdin))
{
len = strlen(buf);
if(write(fds[1],buf,len)!=len)
{
perror("write to pipe");
break;
}
memset(buf,0x00,sizeof(buf));
if((len=read(fds[0],buf,100))==-1)
{
perror("read from pipe");
break;
}
if(write(1,buf,len)!=len)
{
perror("write to stdout");
break;
}
}
}
演示结果
root@hcss-ecs-f59a:/gch/code/HaoHao/day12# ./exe1
3 and 4
Please input some text:
helloworld
read from stdin:
helloworld
2-1 父子进程&匿名管道
一般来讲匿名管道通常使用在有亲属关系的进程中,也就是子进程和父进程,子进程会继承父进程的文件描述符表,那么当父进程pipe创建两个文件描述符读和写,子进程也会继承下来,一个进程关闭写端,一个进程关闭读端就实现了进程间的单向通信。

演示代码
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(int argc,char*argv[])
{
int pipefd[2];
if(pipe(pipefd) == -1)
ERR_EXIT("pipe error");
pid_t pid;
pid =fork();
if(pid == -1)
{
ERR_EXIT("fork error");
}
if(pid == 0)
{
close(pipefd[0]);
write(pipefd[1],"hello",5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10]={0};
read(pipefd[0],buf,10);
printf("buf=%s\n",buf);
return 0;
}
演示结果
gch@hcss-ecs-f59a:/gch/code/HaoHao/day12$ ./exe
buf=hello
2-2管道读写规则
当没有数据可读时(管道为空)- O_NONBLOCK disable(默认阻塞 I/O)
read会阻塞,进程停在read调用那里,直到有数据写入管道才能继续→ 典型的生产者-消费者模型:消费者在等生产者。- O_NONBLOCK enable(非阻塞 I/O)
read直接返回 -1,并把errno设置为EAGAIN,告诉你“现在没有数据,稍后再试”→ 适合做 轮询/异步 I/O。
当管道满的时候(Linux 管道有大小限制,通常是64 KB左右)- O_NONBLOCK disable(默认阻塞 I/O)
write会阻塞,进程停在write调用那里,直到有数据被读走腾出空间→ 防止数据丢失。- O_NONBLOCK enable(非阻塞 I/O)
write直接返回 -1,并把errno设置为EAGAIN,表示“写不进去,等会再试”。
管道的写端都关闭了- 如果所有 写端 关闭了,再去
read: read返回 0(表现得像读到文件结尾 EOF)→ 这时读端知道“再也不会有数据了”
- 如果所有 写端 关闭了,再去
管道的读端都关闭了- 如果所有 读端 关闭了,再去
write: - 内核会给写进程发一个 SIGPIPE 信号,默认行为是终止进程。即使程序捕获信号继续执行,write 也会返回 -1,
errno=EPIPE。→ 这是防止“没人收数据还拼命写”导致浪费内存
- 如果所有 读端 关闭了,再去
2-3管道的特点
-
管道只能用在有
“亲戚关系”的进程之间,比如一个进程先建好管道,再用fork创建子进程,这样父子进程之间就能通过管道来通信。 -
管道就像水流一样,数据是连续流动的。
-
一般来说,进程一旦退出,管道也就自动释放了,所以它的寿命跟进程绑定。
-
管道的读写由内核负责协调,保证不会乱套。
-
管道是半双工的,也就是说数据只能单向传输;如果要实现双方都能说话,就得建两个管道。

三,命名管道
- 匿名管道有个限制:它只能用在有“血缘关系”的进程之间通信。比如一个进程先创建管道,再通过
fork生成子进程,这样父子进程就能借助这个匿名管道来传数据。 - 但如果两个进程之间没有父子关系(比如两个完全独立启动的程序),匿名管道就帮不上忙了。这时就需要用到
命名管道(FIFO) - 命名管道跟匿名管道的本质差不多,区别在于它有个名字(路径),存在于文件系统里。只要两个进程都知道这个路径,就能通过它来读写数据。这样即使它们没有任何“亲缘关系”,也能顺利通信。
3-1 创建命名管道
创建命名管道有两种方式,一种是函数创建,一种是命令创建
mkfifo filename
int mkfifo(const char *filename,mode_t mode);
演示代码
#include<sys/stat.h>
int main()
{
mkfifo("p2",664);
return 0;
}
演示结果
root@hcss-ecs-f59a:/gch/code/HaoHao/day12# ll
total 44
drwxrwxr-x 2 gch gch 4096 Aug 19 15:57 ./
drwxrwxrwx 14 root root 4096 Aug 19 09:53 ../
-rwxr-xr-x 1 root root 16696 Aug 19 15:57 exe*
-rw-rw-r-- 1 gch gch 54 Aug 19 15:51 makefile
p-w---x--T 1 root root 0 Aug 19 15:57 p2|
-rwxrwxrwx 1 root root 71 Aug 19 15:57 text.c*
这里的这个p2就是我们运行程序创建的管道文件
3-2匿名管道和命名管道的区别
- 匿名管道:通过
pipe()函数直接创建并打开。 - 命名管道:需要先用
mkfifo()创建一个特殊文件,然后再通过open()打开使用。 - 它们的本质区别只在于==“创建和打开”的方式不同==。一旦这一步完成,后续的读写语义和使用方式基本是一样的。
3-3命名管道的打开规则
- 以读方式打开
FIFO- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有另一个进程以写方式打开该
FIFO。 - 如果设置了 O_NONBLOCK(非阻塞模式):立即返回成功,即使还没有写端。
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有另一个进程以写方式打开该
- 以写方式打开 FIFO
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有进程以读方式打开该
FIFO。 - 如果设置了 O_NONBLOCK(非阻塞模式):会立刻返回失败,并设置
errno = ENXIO,表示没有可用的读端。
- 如果没有设置 O_NONBLOCK(阻塞模式):会一直等待,直到有进程以读方式打开该
演示代码
clientPipe.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define ERR_EXIT(m)\
do\
{ \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main()
{
int wfd = open("mypipe",O_WRONLY);
if(wfd<0)
{
ERR_EXIT("open");
}
char buf[1024];
while(1)
{
buf[0]=0;
printf("please Enter#");
fflush(stdout);
ssize_t s= read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
write(wfd,buf,strlen(buf));
}
else if(s<=0)
{
ERR_EXIT("read");
}
}
close(wfd);
return 0;
}
serverPipe.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
int main()
{
umask(0);
if(mkfifo("mypipe",0664)<0)
{
ERR_EXIT("mkfifo");
}
int rfd = open("mypipe",O_RDONLY);
if(rfd<0)
{
ERR_EXIT("open");
}
char buf[1024];
while(1)
{
buf[0]=0;
printf("please wait...\n");
ssize_t s = read(rfd,buf,sizeof(buf));
if(s>0)
{
buf[s-1]=0;
printf("client say#%s\n",buf);
}
else if(s==0)
{
printf("client quit,exit now!\n");
exit(EXIT_SUCCESS);
}
else
{
ERR_EXIT("read");
}
}
close(rfd);
return 0;
}
makefile
.PHONY:all
all: clientPipe serverPipe
clientPipe:clientPipe.c
gcc -o $@ $^
serverPipe:serverPipe.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f clientPipe serverPipe
演示结果:
进程一
root@hcss-ecs-f59a:/gch/code/HaoHao/day13# ./clientPipe
please Enter#hello,world
进程二
root@hcss-ecs-f59a:/gch/code/HaoHao/day13# ./serverPipe
please wait...
client say#hello,world
当我用完后可以使用==unlink(“./log.txt”);==关闭管道
7158

被折叠的 条评论
为什么被折叠?



