首页 > 系统相关 >linux pipe 无名管道

linux pipe 无名管道

时间:2023-06-01 18:06:17浏览次数:38  
标签:int 无名 pipe 管道 fd linux 进程 读端


管道的概念:

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
① 数据自己读不能自己写。
② 数据一旦被读走,便不在管道中存在,不可反复读取。
③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
④ 只能在有公共祖先的进程间使用管道。
常见的通信方式有,单工通信、半双工通信、全双工通信。

pipe函数

linux pipe 无名管道_嵌入式linux系统


创建管道

int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:

linux pipe 无名管道_数据_02

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
//父子进程使用管道通信,父写入字符串,子进程读出并,打印到屏幕。

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

void sys_err(const char *str)  //错误
{  
    perror(str);  
    exit(1);  
}  

int main(void)  
{  
    pid_t pid;  
    char buf[1024];  
    int fd[2];  
    char *p = "test for pipe\n";  

   if (pipe(fd) == -1)   
       sys_err("pipe");  

   pid = fork();  //创建子进程
   if (pid < 0)      //错误
    {  
       sys_err("fork err");  
   } 
   else if (pid == 0)    //子进程
   {  
        close(fd[1]);     
        int len = read(fd[0], buf, sizeof(buf));  
        write(STDOUT_FILENO, buf, len);  
        close(fd[0]);  
   } else             父进程
   {  
       close(fd[0]);  
       write(fd[1], p, strlen(p));  
       wait(NULL);  
       close(fd[1]);  
   }  

    return 0;  
}

管道的读写行为

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
  1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
  2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
  3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
  4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
    总结:
    ① 读管道:
  1. 管道中有数据,read返回实际读到的字节数。
  2. 管道中无数据:
    (1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
    (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
    ② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
  1. 管道读端没有全部关闭:
    (1) 管道已满,write阻塞。
    (2) 管道未满,write将数据写入,并返回实际写入的字节数。

从管道中读数据

如果某个进程要读取管道中的数据,那么该进程应当关闭fd[1],同时向管道写数据的进程应当关闭fd[0]。因为管道只能用于具有亲缘关系的进程间的通信,在各个程进行通信时,

他们共享文件描述符。在使用前,应及时地关闭不需要的管道的另一端,以避免意外错误的发生。

进程在管道的读端读数据时,如果管道的写端不存在,则读进程认为已经读到了数据的末尾,读函数返回读出的字节数为0;管道的写端如果存在,且请求读取的字节数大于

PIPE_BUF,则返回管道中现有的所有数据;如果请求的字节数不大于PIPE_BUF,则返回管道中现有的所有数据(此时,管道中数据量小于请求的数据量),或者返回请求的

字节数(此时,管道中数据量大于等于请求的数据量)。

注意:PIPE_BUF在/usr/include/linux/limits.h中定义,不同的内核版本可能会有所不同。

实验

管道的一般用法是,进程在使用fork函数创建子进程前先创建一个管道,该管道用于在父子进程间通信,然后创建子进程,

之后父进程关闭管道的读端,子进程关闭管道的写端。父进程负责向管道写数据而子进程负责读数据。当然父进程可以关闭管道的写端而子进程关闭管道的读端。

这样管道就可以用于父子进程间的通信,也可以用于兄弟进程间的通信。

//无名管道读数据
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int n;
    int fd[2];  
    int count = 0;
    char buf[100] = {0};

    if(pipe(fd) < 0)   //错误
    {
        perror("Fail to create pipe");
        exit(EXIT_FAILURE);
    }

    close(fd[1]);        //关闭写

    if((n = read(fd[0],buf,sizeof(buf))) < 0)
    {
        perror("Fail to read pipe");
        exit(EXIT_FAILURE);
    }

    printf("Rread %d bytes : %s.\n",n,buf);

    return 0;
}

向管道写数据

如果某进程希望向管道中写入数据,那么该进程应当关闭fd[0]文件描述符,同时管道另一端的进程关闭fd[1]。向管道中写入数据时,linux不保证写入的原子性(原子性是指操作在

任何时候都不能被任何原因所打断,操作要么不做要么就一定完成)。管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么

写操作将一直被阻塞等待。

在写管道时,如果要求写的字节数小于等于PIPE_BUF,则多个进程对同一管道的写操作不会交错进行。但是,如果有多个进程同时写一个管道,而且某些进程要求写入的字节数超过

PIPE_BUF所能容纳时,则多个写操作的数据可能会交错。

/*********************
project: 无名管道的读写


写入管道由getchar输入
读管道由printf打印输出
**********************/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

//进程读函数
void read_data(int *);
//进程写函数 
void write_data(int *);


int main(int argc,char *argv[])
{
    int pipes[2],rc;//pipe[0]读管道,pipe[1]写入管道道
    pid_t pid;

    rc = pipe(pipes);   //创建管道                 
    if(rc == -1){
        perror("\npipes\n");
        exit(1);
    }

    pid = fork();   //创建进程 

    switch(pid){
        case -1:
            perror("\nfork\n");
            exit(1);
        case 0:         //子进程
            read_data(pipes);   //相同的pipes,父进程写,子进程读,pid为0子进程
        default:        //父进程
            write_data(pipes);  //相同的pipes,父进程写,子进程读,pid为子进程号是父进程
    }   
    return 0;
}

//进程读函数
void read_data(int pipes[])
{
    int c,rc;

    //由于此函数只负责读,因此将写描述关闭(资源宝贵)
    close(pipes[1]);

    //阻塞,等待从管道读取数据
    //int 转为 unsiged char 输出到终端
    while( (rc = read(pipes[0],&c,1)) > 0 ){        
        putchar(c);                                    
    }

    exit(0);
}

//进程写函数
void write_data(int pipes[])
{
    int c,rc;

    //关闭读描述字
    close(pipes[0]);                          

    while( (c=getchar()) > 0 ){
        rc = write( pipes[1], &c, 1);   //写入管道
        if( rc == -1 ){
            perror("Parent: write");
            close(pipes[1]);
            exit(1);
        }
    }

    close( pipes[1] );
    exit(0);
}

注意:

只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号。应用程序可以处理也可以忽略该信号,如果忽略该信号或者

从其处理程序返回,则write出错,错误码为EPIPE。

必须在系统调用fork之前调用pipe,否则子进程将不会继承管道的文件描述符。


标签:int,无名,pipe,管道,fd,linux,进程,读端
From: https://blog.51cto.com/u_16147764/6397222

相关文章

  • linux命名管道
    管道是进程间通信的主要手段之一。一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端。管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构。根据管道的适用范围将其分为:无名管道和命......
  • linux消息队列
    经典进程间通信机制(IPC):管道、FIFO、消息队列、信号量以及共享储存。这些机制允许在同一台计算机上运行的进程可以相互通信。但是当考察到不同计算机(通过网络相连)的进程相互通信时就必须借助网络通信机制(networkIPC),在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境......
  • VMware中安装Linux-kali
    VMware中安装Linux-kali(详细图文教程)萌褚于2022-05-2010:33:30发布9499收藏94文章标签:Linux版权华为云开发者联盟该内容已被华为云开发者联盟社区收录加入社区镜像下载、域名解析、时间同步请点击阿里云开源镜像站一,VMware配置。  因为要装kali,所以要用到Debian......
  • Linux进程管理、计划任务笔记
    一、Linux进程管理1.1、进程概念进程是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存,网络资源等。并发程序和顺序程序有本质上的差别,为了能更好地描述程序的并发执行,实现操作系统的并发性和共享性,引入“进程”的概念。进程是具有一定独立......
  • Linux系统管理---时区和时间管理
    一、时区查看时区:cat/etc/sysconfig/clock时区存放文件:/etc/localtime修改时区:cp/usr/share/zoneinfo/Asia/Shanghai /etc/localtime修改验证:date  返回有CST,表示修改正确了二、时间查看时间:date 或者  date-R或者按指定格式:date"+%Y-%m-%d%H:%M:%S"[root@hadoo......
  • Linux软件安装--二进制发布包安装、rpm发布包安装(案例:jdk和mysql安装)
    Linux软件安装的4种方式一、二进制发布包指软件已经根据平台编译并且打包,拿到这个包后解压并配置环境变量,如jdk包、mysql包、Tomcat包。示例:二进制发布包安装jdk。示例:二进制发布包安装jdk1.获取安装包1).直接从linux网上下载安装包-->wget https://download.oracle.com/otn-pu......
  • Linux的SSH免密登录配置
    一、SSH概念SSH为SecureShell(安全外壳协议)的缩写,简单说,SSH只是一种网络协议,用于计算机之间的加密登录传输,很多ftp、pop和telnet在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,别有用心的人非常容易就可以截获这些口令和数据。而SSH就是专为远程登录会话和其他网络服务......
  • linux配置静态ip
    转:https://blog.csdn.net/weixin_46560589/article/details/1248148601进入配置文件目录cd/etc/sysconfig/network-scripts 2编辑配置文件vimifcfg-ens32ens32是虚拟网卡名称,根据自己虚机上的网卡名称修改如果有多个网卡,编辑你要用的那个网卡 3......
  • linux 中 删除指定匹配特定字符指定次数之前或者之后的内容
     001、[root@PC1test]#lsa.txt[root@PC1test]#cata.txt0102030405060708091011121314151617181920[root@PC1test]#sed's//tag/2'a.txt|sed's/.*tag//'##删除第二个空格之前的所有内容030405080910131415181920......
  • linux下查看 SELinux状态及关闭SELinux
    SELinux全称为安全增强式Security-EnhancedLinux(SELinux),是一个在内核中实践的强制存取控制(MAC)安全性机制。SELinux首先在CentOS4出现,并在其后的CentOS发行版本获得重大改善。这些改善代表用SELinux解决问题的方法亦随著时间而改变。SELinux的原理与架构SELinux的整体......