命名管道和匿名管道
命名管道
命名管道(Named Pipe)是一种特殊类型的文件,它以文件系统路径为标识,允许任意两个进程(无论是否有亲缘关系)通过读写该路径进行数据交换。其本质是内核维护的一个内存缓冲区,遵循“先进先出”(FIFO)原则
命名管道的核心是内核缓冲区与文件系统接口的结合。
创建管道:通过 mkfifo 命令或 mkfifo() 系统调用在文件系统中创建一个特殊文件(类型标识为 p,可通过 ls -l 查看)。
创建时通过 mkfifo -m 600 /tmp/myfifo 设置权限,仅允许所有者读写,防止未授权进程访问。
若以只读(O_RDONLY)打开,open() 会阻塞,直到另一个进程以只写(O_WRONLY)打开该管道。
若以只写(O_WRONLY)打开,open() 同样会阻塞,直到另一个进程以只读打开。
若以读写(O_RDWR)打开,open() 不会阻塞(但不推荐,易引发死锁)。两个进程均以 O_RDWR 打开管道,各自写入后等待读取对方数据,导致永久阻塞。
解决方案:严格使用“一读一写”模式,双向通信分离为两个管道。
数据传输:进程通过 read()/write() 系统调用读写管道:
数据写入时,内核将其暂存于缓冲区,若缓冲区满,write() 会阻塞。
数据读取时,内核从缓冲区提取数据,若缓冲区空,read() 会阻塞。
数据读取后即从缓冲区移除,确保“先进先出”。
关闭与销毁:进程通过 close() 关闭管道,所有进程关闭后缓冲区释放;通过 unlink() 或 rm 命令删除文件系统中的管道路径,彻底销毁管道。
示例:基本读写
写入进程(writer.c)#
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
int main() {
const char *fifo_path = "/tmp/myfifo";
int fd;
char buffer[] = "Hello from C writer!";
// 1. 创建管道(若已存在则忽略错误)
if (mkfifo(fifo_path, 0666) == -1) {
perror("mkfifo failed");
// 若错误为 EEXIST(管道已存在),可继续执行
if (errno != EEXIST) exit(EXIT_FAILURE);
}
// 2. 以只写模式打开管道(会阻塞至读取端打开)
printf("Writer: Waiting for reader...\n");
fd = open(fifo_path, O_WRONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
printf("Writer: Connected to reader.\n");
// 3. 写入数据
write(fd, buffer, strlen(buffer) + 1); // +1 包含字符串结束符 '\0'
printf("Writer: Data written: %s\n", buffer);
// 4. 关闭管道
close(fd);
printf("Writer: Pipe closed.\n");
return 0;
}
读取进程(reader.c)#
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
int main() {
const char *fifo_path = "/tmp/myfifo";
int fd;
char buffer[1024];
// 1. 以只读模式打开管道(会阻塞至写入端打开)
printf("Reader: Waiting for writer...\n");
fd = open(fifo_path, O_RDONLY);
if (fd == -1) {
perror("open failed");
exit(EXIT_FAILURE);
}
printf("Reader: Connected to writer.\n");
// 2. 读取数据
read(fd, buffer, sizeof(buffer));
printf("Reader: Data received: %s\n", buffer);
// 3. 关闭管道
close(fd);
printf("Reader: Pipe closed.\n");
// 4. 清理管道(可选,若需重复使用可省略)
unlink(fifo_path);
return 0;
}
编译与运行:
gcc writer.c -o writer
gcc reader.c -o reader
# 终端 1 运行读取端
./reader # 输出:Reader: Waiting for writer...
# 终端 2 运行写入端
./writer # 输出:Writer: Waiting for reader... → 连接后写入数据
创建管道:
mkfifo /tmp/myfifo # 在 /tmp 目录下创建名为 myfifo 的管道
ls -l /tmp/myfifo # 查看类型:prw-r--r--(p 表示管道)
进程 A(写入端):在终端 1 中执行,此时命令会阻塞,等待读取端打开管道:
echo "Hello from Process A" > /tmp/myfifo # 向管道写入数据
进程 B(读取端):在终端 2 中执行,读取并输出管道数据,此时终端 1 的阻塞解除:
cat < /tmp/myfifo # 从管道读取数据,输出:Hello from Process A
清理管道:
rm /tmp/myfifo # 删除管道路径
无名管道
ulimit -a查看到的pipe size一次原子写入为:512 bytes * 8 = 4096 bytes 。
pipe大小
1,管道缓冲区容量 ulimit -a |grep 'pipe size'
ulimit -a查看到的pipe size一次原子写入为:512 bytes * 8 = 4096 bytes 。
2,pipe buf 和缓冲条目的数目来共同决定其pipe capacity容量
/usr/src/kernels/内核版本/include/linux/pipe_fs_i.h中
管道的容量为:16 * 4096 bytes = 65536 bytes。(64KB)
关于原子性的说明,少于 PIPE_BUF (4KB)的写操作必须原子完成:要写的数据应被连续的写到管道;大于 PIPE_BUF 的写操作可能是非原子的: 内核可能会把此数据与其它进程的对此管道的写操作交替起来。
管道是在内核中由环形队列实现的,管道是字节流通信,没有消息边界
读端对应的文件描述符被关闭,write操作会产生信号,SIGPIPE,进而导致write进程退出
当要写入的数据量不大于管道的容量(PIPE_BUF)时,linux将保证写入的原子性
要写入的数据量大于管道容量(PIPE_BUF)时,linux将不再保证写入的原子性
clude <unistd.h>
int pipe(int pipefd[2]);
例子代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fds[2];
if(pipe(fds) < 0){//创建一个管道,用于父子间进行通信
perror("pipe");
return 1;
}
char buf[1024];//临时数组,用于存放通信的消息
printf("Please enter:");
fflush(stdout);//对标准输出流的清理,但是它并不是把数据丢掉,而是及时地打印数据到屏幕上
ssize_t s = read(0,buf,sizeof(buf)-1);//0对应文件描述符
if(s > 0){//判断读取的字节数
buf[s] = 0;
}
pid_t pid = fork();//fork()子进程
if(pid == 0){//子进程只写,关闭读端
close(fds[0]);
while(1){
sleep(1);
write(fds[1],buf,strlen(buf));//将buf的内容写入管道
}
}
else{//父进程只读,关闭写端
close(fds[1]);
char buf1[1024];
while(1){
ssize_t s = read(fds[0],buf1,sizeof(buf1)-1);//从管道里读数据,放入buf
if(s > 0){
buf1[s-1] = 0;
printf("client->farther:%s\n",buf1);
}
}
}
}
浙公网安备 33010602011771号