pipe相关代码的实现

本文介绍了管道(Pipe)的基本特点,包括其互斥与同步机制、单向通信性质等,并通过代码示例解析了如何在Unix/Linux系统中实现进程间的通信。讨论了在不同场景下,如读写端的不同操作,管道通信的行为,包括读端不读、写端不写等情况对进程的影响,以及涉及的同步和阻塞问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pipe

头文件:#include<unistd.h>
功能:创建一个无名管道
函数原型:int pipe(int fd[2]);//分别以读、写方式打开,所以有两个文件描述符
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误码

管道的特点
-管道自带互斥与同步机制
-管道只能单向通信
-只要有血缘关系的两个进程就可以进行进程间通信
-管道也是文件
-管道的生命周期随进程(进程退出管道随即释放)
-提供面向字节流的服务

这里写图片描述

用fork创建一个子进程(以父进程为模板),子进程PCB的大部分信息都是来源于父进程,子进程也有一个文件表和父进程的文件表保持一致,数组也保持一致,数组中的内容也是相同的,指向也是相同的。进程间通信的本质是让两个不同的进程看到一份公共的资源。file里写的数据肯定会被刷新到硬盘上,但在刷新到内存前,会有一个缓冲区,这个缓冲区就可以被父、子进程同时利用起来。

这里写图片描述

但管道只能单向通信,若想让父进程读,子进程写,就要关闭父进程的写端,关闭子进程的读端。那为什么不直接以读的方式打开父进程,写的方式打开子进程呢???为什么还要关???若父进程不以写方式打开,子进程就拿不到写方法,所以必须得读写打开。此时我们就有了一个单向数据通信的信道。

实现代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            sleep(1);//子进程每隔一秒写一条
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
            }
        }
    }
}

运行结果:
这里写图片描述

下面分析匿名管道的五种情况:
1.如果管道的写端一直在写,而读端不关闭自己的读文件描述符,但也不读

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
            printf("%d\n",count++);//写一次count++
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {

            }
        }
    }
}

写满了不能重头覆盖写,一旦覆盖写会出现数据的二异性问题。不能再写子进程就会被卡住,count值不再变化

这里写图片描述
一瞬间写了4千多次,写满了数字不再变,为保证管道在读端读,写端才写,所以子进程卡住了。

2.让子进程先写满父进程再读

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
            printf("%d\n",count++);//写一次count++
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                sleep(5);
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
            }
        }
    }
}

这里写图片描述
此时一瞬间子进程写满,父进程5秒之后读,父进程读完管道就空出来了,父进程继续每隔5秒读一条消息,当父进程在读的时候,子进程也继续再写。
小结
写方一直在写,读端不读,但读端也不关闭文件描述符,写方在把管道写满之后就会停下来,等待别人取数据。

3(1).写方写一条消息后,10秒钟之后再写下一条消息,读方立即把数据读出来,管道里没有数据,读方会阻塞式等待。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
            sleep(10);
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            int count = 0;
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
                printf("f read %d\n",count);
            }
        }
    }
}

这里写图片描述

管道里没有数据,读方会阻塞式等待,直到有信息(10秒钟之后)

这里写图片描述

如果读写双方都不关文件描述符,一个不写或一个不读都会导致对方要等你,这就叫同步。

3(2).如果写端一直在写,然后不再写并且把写端关闭,而读端一直在读,直到把管道里的数据读完

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        close(fd[0]);
        int count = 0;
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
            printf("%d\n",count++);
            if(count > 10)
            {
                close(fd[1]);//关闭写端
                break;
            }
            sleep(1);//每隔1秒写一次
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];

            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                }
                else if(s == 0)//将管道里的数据读完后会返回一个0值,代表读到文件结尾
                {
                    printf("pipe done,break!\n");
                    break;
                }
            }
        }
    }
}

这里写图片描述

读10次后,子进程会关闭自己的写文件描述符,父进程就读到0了

这里写图片描述

4.写端一直在写,读端不但不读,还关闭掉了自己的读文件描述符

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe");
        return 1;
    }
    pid_t id = fork();
    if(id == 0)
    {
        //child->w
        int count = 0;
        close(fd[0]);
        const char* msg = "hello f,I am c!\n";
        while(1)
        {
            write(fd[1],msg,strlen(msg));//往管道里写就是往文件里写
            printf("%d\n",count++);
            sleep(1);//每隔1秒写一次
        }
        else
        {
            //father->r
            close(fd[1]);
            char buf[64];
            while(1)
            {
                ssize_t s = read(fd[0],buf,sizeof(buf)-1);
                if(s > 0)
                {
                    buf[s] = '\0';
                    printf("f say : %s\n",buf);
                    sleep(3);
                    break;
                }
                close(fd[0]);
            }
            int status = 0;
            wait(&status);
            printf("sig : %d\n",status&0x7F);//status数字当中的次7位表示退出信号
        }
    }
}

写端的进程会立即被操作系统用信号终止

这里写图片描述
子进程一直在写,3秒钟后父进程读取到子进程以13号信号退出

13号信号为SIGPIPE

这里写图片描述

当写端一直在写,读端不但不读还把自己的读文件描述符关闭,写端就会因为触发异常而被操作系统直接终止,操作系统向目标进程发送13号信号

总结一下以上四种情况:

(1)写端一直在写,读端不读也不关,写端写满后就会阻塞
(2)读端一直在读,写端也一直在写,可是写端突然不写了,但不关闭写文件描述符,读端一直在读,当管道数据为空时,就会被阻塞
(3)写端不但不写了,还关闭了写文件描述符,读端一直在读,管道内的数据越来越少,最后读到0(即文件结尾)
(4)写端一直在写,读端不但不读而且还关闭了读文件描述符,一旦再写入就会触发操作系统用13号信号(SIGPIPE)异常终止目标进程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值