管道的内核实现

一.概述:

管道是进程间通信的手段之一,它实际上是一个存在于内存的特殊文件,而这个文件要通过两个已经打开的文件才能进行操作,这两个文件分别指向管道的两端。

管道是通过在内存中开辟一个缓存区来实现进程间的通信的,这个缓存区的大小是固定的。在linux中,这个缓存区的大小为一页,即4k。但固定的大小会带来问题,当缓存区已经被write操作写满时,之后的write操作会阻塞,等待缓存区内的某些数据被读取,以便腾出空间给写操作调用。

在linux中,管道并没有专门的数据结构,而是通过file结构和inode共同实现。两个file结构指向一个inode,而这个inode对应了内存的某个区域。

注:一个file中的f_op只对应写或者读操作中的一个。

而管道创建的缓存区是一个环形缓存区,即当用到缓存区的尾部时,下一个可用区间为缓存区的首部。缓存区一般采用的是数组形式,即申请的是一个线性的地址空间。而形成环状用的是    模    缓存区长度。而访问这个缓存区的方式是一种典型的“生产者—消费者模型”,即当生产者有大量的数据要写时,而缓存区的大小只有1k,所以当缓存区被写满时,生产者必须等待,等待消费者读取数据,以便腾出空间让消费者写。而当消费者发现缓存区内并没有数据时,消费者必须等待,等待生产者往缓存区内写数据来提供消费者读数据。

那么又如何判断是“空”还是“满”呢。当read和write指向环形缓存区的同一位置时为空或满。为了区别空和满,规定read和write重叠时为空,而当write比read快,追到距离read还有一个元素间隔时,就认为是满。

摘:(不明觉厉)

 并发访问

考虑到在不同环境下,任务可能对环形缓冲区的访问情况不同,需要对并发访问的情况进行分析。

在单任务环境下,只存在一个读任务和一个写任务,只要保证写任务可以顺利的完成将数据写入,而读任务可以及时的将数据读出即可。如果有竞争发生,可能会出现如下情况:

Case1:假如写任务在“写指针加1,指向下一个可写空位置”执行完成时被打断,此时写指针write指向非法位置。当系统调度读任
务执行时,如果读任务需要读多个数据,那么不但应该读出的数据被读出,而且当读指针被调整为0是,会将以前已经读出的数据重复读出。

Case2:假设读任务进行读操作,在“读指针加1”执行完时被打断,此时read所处的位置是非法的。当系统调度写任务执行时,如果
写任务要写多个数据,那么当写指针指到尾部时,本来缓冲区应该为满状态,不能再写,但是由于读指针处于非法位置,在读任务执行前,写任务会任务缓冲区为
空,继续进行写操作,将覆盖还没有来的及读出的数据。

二.用管道实现进程间的通信:

代码:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main()
{
    //.....创建管道
    int pipefd[2] ={-1,-1};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }

//....创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }

else if(id == 0)
    {
         //....子进程关闭读端
         close(pipefd[0]);

char* buf = "i‘m  child";
         int count = 5;
         while(count--)
         {
             write(pipefd[1],buf,strlen(buf));
             sleep(1);
         }
    }
    else
    {
        //....父进程关闭写端
        close(pipefd[1]);

char buf[1024];
        int count = 5;
        while(count--)
        {
              memset(buf, ‘\0‘, sizeof(buf));
              ssize_t size = read(pipefd[0],buf,sizeof(buf) - 1);
              buf[size] = ‘\0‘;
              printf("%s\n",buf);
        }
     }

}

结果截图:

三.使用管道会出现的四种情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

1:当所有写端都关闭时,但还有进程要从管道中读取数据,那么当管道中的数据读完后,再次read时,就会返回0,就像读到文件末尾一样。

代码如下:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main()
{
    //.....创建管道
    int pipefd[2] ={-1,-1};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }

//....创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }

else if(id == 0)
    {
        //....子进程关闭写端
        close(pipefd[1]);

char buf[1024];
        int count = 5;
        while(count--)
        {
              memset(buf, ‘\0‘, sizeof(buf));
              ssize_t size = read(pipefd[0],buf,sizeof(buf) - 1);
              if(size > 0)
              {
                    buf[size] = ‘\0‘;
                    printf("%s\n",buf);
              }
              else
              {
                  printf("read error");
              }
        }
     }
    else
    {
         //....父进程关闭读端
         close(pipefd[0]);

char* buf = "i‘m  father";
         int count = 5;
         int i = 0;
         while(count--)
         {
             if(i == 3)
             {
                 printf("i just want to sleep");
                 break;
             }
             i++;
             write(pipefd[1],buf,strlen(buf));
             sleep(1);
         }
    }
}

执行结果:

2.如果指向管道写端的文件标识符没关闭,但写端也不向管道中写数据时,如果此时有进程从管道中读数据,那么当缓存区内的数据被读完之后,再次read,读进程会进入阻塞状态,直到缓存区内有数据才进行读数据。

代码如下:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main()
{
    //.....创建管道
    int pipefd[2] ={-1,-1};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }

//....创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }

else if(id == 0)
    {
        //....子进程关闭写端
        close(pipefd[1]);

char buf[1024];
        int count = 5;
        while(count--)
        {
              memset(buf, ‘\0‘, sizeof(buf));
              ssize_t size = read(pipefd[0],buf,sizeof(buf) - 1);
              if(size > 0)
              {
                    buf[size] = ‘\0‘;
                    printf("%s\n",buf);
              }
        }
     }
    else
    {
         //....父进程关闭读端
         close(pipefd[0]);

char* buf = "i‘m  father";
         int count = 5;
         int i = 0;
         while(count--)
         {
             if(i == 3)
             {
                 printf("i just want to sleep\n");
                 sleep(5);
             }
             i++;
             write(pipefd[1],buf,strlen(buf));
             sleep(1);
         }
    }
}

执行结果:

3.如果所有指向管道的读端都关闭了,但还有进程要往管道中写数据,那么进程就会收到信号SIGPIPE,通常会导致进程异常终止。(因为此时再写也没用,又没进程要读)

代码如下:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main()
{
    //.....创建管道
    int pipefd[2] ={-1,-1};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }

//....创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }
    else if(id == 0)
    {
        //....子进程关闭读端
        close(pipefd[0]);

char buf[] = "i‘m  child";
        int count = 5;
        while(count--)
        {
              write(pipefd[1],buf,strlen(buf));
              sleep(1);
        }
    }
    else
    {
         //....父进程关闭写端
         close(pipefd[1]);
         char buf[1024];
         int count = 5;
         int i = 0;
         while(count--)
         {
             if(i == 3)
             {
                   close(pipefd[0]);
                   break;
             }
             i++;
 
            int ret = 0;
            memset(buf, ‘\0‘, sizeof(buf));
            ret =  read(pipefd[0],buf,sizeof(buf) - 1);
            buf[ret] = ‘\0‘;
            if(ret > 0)

{
                 printf("%s\n",buf);
                 fflush(stdout);
            }
         }
         int status = 0;
         pid_t pid = waitpid(id, &status, 0);
         printf("pid: [%d]  signal:[%d]\n",id,status|0xff);
         fflush(stdout);
    }
}

执行结果:

4.如果指向管道读端的文件标识符没有关闭,但又不读缓存区内的内容,那么如果此时还有进程要往缓存区内写,而缓存区已被写满时,写进程会阻塞,直到缓存区内有空位置之后才能写。

代码如下:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main()
{
    //.....创建管道
    int pipefd[2] ={-1,-1};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
        perror("pipe");
        return -1;
    }

//....创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return -1;
    }
    else if(id == 0)
    {
        //....子进程关闭读端
        close(pipefd[0]);
 
        char buf[] = "i‘m  child";
        int count = 5;
        while(count--)
        {
              write(pipefd[1],buf,strlen(buf));
              sleep(1);
        }
    }
    else
    {
         //....父进程关闭写端
         close(pipefd[1]);
         char buf[1024];
         int count = 5;
         int i = 0;
         while(count--)
         {
             if(i == 3)
             {
                   printf("i just want to sleep\n");
                   fflush(stdout);
                   sleep(5);
             }
             i++;
 
            int ret = 0;
            memset(buf, ‘\0‘, sizeof(buf));
             ret =  read(pipefd[0],buf,sizeof(buf) - 1);
            buf[ret] = ‘\0‘;
            if(ret > 0)
            {
                 printf("%s\n",buf);
                 fflush(stdout);
            }
         }
         int status = 0;
         pid_t pid = waitpid(id, &status, 0);
         printf("pid: [%d]  signal:[%d]\n",id,status|0xff);
         fflush(stdout);
    }
}

执行结果:

时间: 2024-10-29 10:57:32

管道的内核实现的相关文章

进程间通信 之 管道

一 无名管道: 特点: 具有亲缘关系的进程间通信,但不仅仅指父子进程之间哦. (1)无名管道的创建 int pipe(int pipefd 参数: pipefd  数组的首地址 返回值: 成功返回0,失败返回-1 注意: 无名管道存在内核空间,创建成功会给用户空间两个文件描述符,fd[0]:读管道 fd[1]:写管道 思考:为什么无名管道只能用于亲缘关系间进程通信? 因为只有具有亲缘关系的进程存在数据拷贝 [拷贝文件描述符] 二.有名管道 特点: (1)任意进程间通信 (2)文件系统中存在文件名

Linux进程通信——管道

进程间通信(IPC:Inner Proceeding Communication) 进程是操作系统实现程序独占系统运行的假象的方法,是对处理器.主存.I/O设备的抽象表示.每个进程都是一个独立的资源管理单元,每个进程所看到的是自己独占使用系统的假象,因此各个进程之间是不能够直接的访问对方进程的资源的,不同的进程之间进行信息交互需要借助操作系统提供的特殊的进程通信机制. 进程之间的通信,从物理上分,可以分为同主机的进程之间的通信和不同主机间的进程之间的通信.从通信内容方式上分,可以分为数据交互.同

第11章进程间通信(1)_管道

1. 进程间通信概述 (1)概述 ①数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间. ②共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到. ③通知事件:一个进程需要向另一个(组)进程发送消息,通知它们发生了某种事件(如进程终止时要通知父进程). ④资源共享:多个进程之间共享同样的资源.为了做到这一点,需要内核提供锁和同步机制. ⑤进程控制:有些进程希望完全控制另一个进程的执行(如Degub进程),此时控制进程希望能够拦截另

1.8命令执行顺序控制与管道(学习过程)

命令执行顺序控制与管道 实验介绍 顺序执行.选择执行.管道.cut 命令.grep 命令.wc 命令.sort 命令等,高效率使用 Linux 的技巧. 一.命令执行顺序的控制 1.顺序执行多条命令 通常情况下,我们每次只能在终端输入一条命令,按下回车执行,执行完成后,我们再输入第二条命令,然后再按回车执行…… 你可能会遇到如下使用场景:我需要使用apt-get安装一个软件,然后安装完成后立即运行安装的软件(或命令工具),又恰巧你的主机才更换的软件源还没有更新软件列表(比如之前我们的环境中,每次

Linux 基础入门----命令执行顺序控制与管道

介绍 顺序执行.选择执行.管道.cut 命令.grep 命令.wc 命令.sort 命令等,高效率使用 Linux 的技巧. 一.命令执行顺序的控制 1.顺序执行多条命令 通常情况下,我们每次只能在终端输入一条命令,按下回车执行,执行完成后,我们再输入第二条命令,然后再按回车执行…… 你可能会遇到如下使用场景:我需要使用apt-get安装一个软件,然后安装完成后立即运行安装的软件(或命令工具),又恰巧你的主机才更换的软件源还没有更新软件列表(比如之前我们的环境中,每次重新开始实验就得sudo a

命令执行顺序控制与管道

命令执行顺序控制与管道 实验介绍 顺序执行.选择执行.管道.cut 命令.grep 命令.wc 命令.sort 命令等,高效率使用 Linux 的技巧. 一.命令执行顺序的控制 1.顺序执行多条命令 通常情况下,我们每次只能在终端输入一条命令,按下回车执行,执行完成后,我们再输入第二条命令,然后再按回车执行……   你可能会遇到如下使用场景:我需要使用apt-get安装一个软件,然后安装完成后立即运行安装的软件(或命令工具),又恰巧你的主机才更换的软件源还没有更新软件列表(比如之前我们的环境中,

Linux的进程间通信 - 管道

Linux的进程间通信 - 管道 版权声明: 本文章内容在非商业使用前提下可无需授权任意转载.发布. 转载.发布请务必注明作者和其微博.微信公众号地址,以便读者询问问题和甄误反馈,共同进步. 微博ID:orroz 微信公众号:Linux系统技术 前言 管道是UNIX环境中历史最悠久的进程间通信方式.本文主要说明在Linux环境上如何使用管道.阅读本文可以帮你解决以下问题: 什么是管道和为什么要有管道? 管道怎么分类? 管道的实现是什么样的? 管道有多大? 管道的大小是不是可以调整?如何调整? 什

四十六、进程间通信——管道的分类与读写

46.1 管道介绍 46.1.1 管道通信 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,建立管道后,实际获得两个文件描述符:一个用于读取而另一个用于写入 最常见的 IPC 机制,通过 pipe 系统调用 管道是单工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道 数据的读出和写入: 一个进程向管道中写的内容被管道另一端的进程读出. 写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据 管道实际上就是建立在内核区域的一块缓存 46.1.2 管道分

Linux第一周实验报告总结

北京电子科技学院(BESTI) 实 验 报 告 课程:信息安全系统设计基础 班级:1352 姓名:马悦 学号:20135235 成绩: 指导教师:娄嘉鹏 实验日期:2015.9.20 实验密级: 预习程度: 实验时间: 仪器组次: 必修/选修:必修 实验序号:1 实验名称: Linux简介  实验目的与要求:1. 了解Linux. 2.学习,安装Linux. 实验仪器: 名称           型号           数量  PC          ThinkPad        1 虚拟机