APUE学习之管道(pipe)与命名管道(fifo)

目录

一、简介

二、管道(Pipe)

1、管道的基本概念

2、管道的局限性

3、管道的创建

4、管道的读写规则

5、实战演练

三、命名管道(fifo)

1、命名管道的基本概念

2、命名管道的创建

3、实战演练

4、运行结果

四、补充

1、wait()函数

2、acess()函数

3、Linux下文件系统权限


一、简介

        本篇文章主要讲解Linux环境编程中进程间通信的两种常用方法:管道(Pipe)和命名管道(FIFO)。

管道:一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系(通常是指父子进程关系)的进程间使用;

 命名管道:也是一种半双工通信,但是它允许无亲缘关系进程间的通信;

        以上只是向大家简单介绍了管道与命名管道,具体的讲解请继续往下看:

二、管道(Pipe)

1、管道的基本概念

        管道是UNIX系统IPC的最古老的形式,它的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据。

        管道由两个文件描述符引用,一个表示读端,一个表示写端。管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺利地读取数据。

        该缓冲区可以看成一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,数据被读出后,就不会再存在于管道之中了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程是否进入等待队列,当空的缓冲区有新数据写入或者原来的缓冲区有数据读出时,就会唤醒等待队列中的进程继续读写。

2、管道的局限性

(1)半双工通信方式,数据只能在一个方向流动。

(2)管道只能在具有公共祖先之间的两个进程之间使用。

(3)数据一旦被读走,便不在管道中存在,不可反复读取。

(4)管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小)。

(5) 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须实现约定好数据的格式,比如多少字节算一个消息(或命令、或记录)等待;

3、管道的创建

        相信大家在接触Linux的时候都听过一句话——Linux下一切皆是文件!我们也可以把管道看做是一种特殊的文件,对于它的读写我们也可以调用write、read等函数。但是管道不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

        管道是通过调用pipe函数创建的,让我们先看一下函数原型吧:

#include    <unistd.h>

 

int   pipe(int   pipefd[2]);         //返回值:成功返回0,出错返回-1

经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开

那创建管道,我们又如何能够实现进程之间通信呢?

        通常,我们的进程先调用pipe,接着调用fork,从而创建了父进程与子进程之间的IPC通道。调用fork之后,如图所示:

fork()之后做什么取决于我们想要的数据流的反向,共两种情况,让我们分别来看看:

(1)从父进程到子进程方向的管道

        即父写子读,关闭父进程管道的读端fd[0],再关闭子进程管道的写端fd[1],如图:

 (2)从子进程到父进程方向的管道

        即子写父读,关闭父进程管道的写端fd[1],再关闭子进程管道的读端fd[0],如图:

4、管道的读写规则

当没有数据可读时

(1)O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

(2)O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道数据满的时候

(1)O_NONBLOCK disable: write调用阻塞,直到有进程读走数据。

(2)O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。

关闭管道的一端

(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。

(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1 。

5、实战演练

题目:

        编写一个程序,实现父进程给子进程方向发送数据。

思考过程:

        首先父进程调用pipe创建管道之后fork(),这时子进程会继承父进程所有打开的文件描述符(包括管道),这时对于一个管道就有四个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然后子进程中关闭读端。

代码如下:

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

#define MSG_STR         "This message is from parent:Hello,child process!"

int main(int argc,char *agv[])
{

        int             pipe_fd[2];
        int             rv;
        int             pid;
        char            buf[512];
        int             status;

        if(pipe(pipe_fd) < 0)
        {
                printf("Create pipe failure:%s\n",strerror(errno));
                return -1;
        }

        if((pid = fork()) < 0)
        {
                printf("Create child prcess failure:%s\n",strerror(errno));
                return -2;
        }

        else if(pid == 0)
        {
                /*child process close write ,then read data from parent process*/
                close(pipe_fd[1]);

                memset(buf,0,sizeof(buf));
                rv = read(pipe_fd[0],buf,sizeof(buf));
                if(rv < 0)
                {
                        printf("Child process read from pipe failure:%s\n",strerror(errno));
                        return -3;
                }

                printf("Child process read %d bytes data from pipe:[%s]\n",rv,buf);
                return 0;
        }

        /*parent process close read,then write data to child process*/
        close(pipe_fd[0]);
        if(write(pipe_fd[1],MSG_STR,strlen(MSG_STR)) < 0)
        {
                printf("Parent process write data to pipe failure:%s\n",strerror(errno));
                return -4;
        }

        printf("Parent start wait child process exit...\n");
        wait(&status);

        return 0;
}
                                     

运行结果:

三、命名管道(fifo)

1、命名管道的基本概念

        命名管道通信也属于进程间通信IPC的一种常见方式,他与管道的不同是:命名管道可以实现两个毫不相干的独立进程间通信。 

        FIFO不同于管道之处在于它提供一个路径与之相关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但是没有数据块。换句话说就是,命名管道只是拥有一个名字和相应的访问权限,并且命名管道的大小为0 ,命名管道就是一个内存文件。

       大家不用把命名管道想的很复杂,你就可以把它理解为在文件系统中挂了个名字,但也仅仅是个挂个名而已,没有实际的大小。目的就是能叫进程看到这个文件,因为文件系统中的文件都可以被进程看见。  

2、命名管道的创建

        我们可以通过调用mkfifo()来创建命名管道。一旦建立,任何进程都可以通过文件名将其打开和进行书写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被进程使用时,FIFO在内存中释放,但磁盘节点仍在。

        让我们先来看一下mkfifo()函数的原型吧:

#include <sys/types.h>
#include <sys/stat.h>

int  mkfifo(const  char *pathname, mode_t  mode);

参数说明:

(1)第一个参数pathname: 在这里既可以传绝对路径,也可以传相对路径(关于绝对路径与相对路径)。当然,绝对路径更灵活,但是也更长。

(2)第二个参数mode:其实就是创建命名管道时的初始权限,实际权限需要经过umask掩码进行计算。(关于文件权限我在文章最后给大家讲解,就不在这里赘述了)

3、实战演练

题目---实现进程间聊天:

        在程序中创建两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行通信。该程序需要运行两次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0。

        请利用命名管道编写一个程序来实现上述要求。

代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/select.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_FILE1      ".fifo_chat1"
#define FIFO_FILE2      ".fifo_chat2"

int g_stop = 0;

void sig_pipe(int signum)
{
        if(SIGPIPE == signum)
        {
                printf("get pipe broken signal and let programe exit\n");
                g_stop = 1;
        }
}

int main(int argc,char *argv[])
{
        int             fdr_fifo;
        int             fdw_fifo;
        int             rv;
        fd_set          rdset;
        char            buf[1024];
        int             mode = -1;

        if(argc != 2)
        {
                printf("Usage:%s [0/1]\n",basename(argv[0]));
                printf("This chat program need run twice,1st time run with [0] and 2nd time with [1]\n");
                return -1;
        }
        mode = atoi(argv[1]);

        /*管道是一种半双工的通信方式,如果要实现两个进程间的双向通信则需要两个管道,即两个管道分别作为两个进程的读端和写端*/

        /*测试文件是否存在,不存在则建立*/
        if(access(FIFO_FILE1,F_OK))
        {
                printf("FIFO file \"%s\" not exist and create it now\n",FIFO_FILE1);
                mkfifo(FIFO_FILE1,0666);
        }

        if(access(FIFO_FILE2,F_OK))
        {
                printf("FIFO file \"%s\" not exist and create it now\n",FIFO_FILE2);
                mkfifo(FIFO_FILE2,0666);
        }

        signal(SIGPIPE,sig_pipe);

        if(0 == mode)
        {
                /*这里以只读模式打开命名管道FIFO_FILE1的读端,默认是阻塞模式;如果命名管道的写端打不开,则open()将会一直阻塞,所以另外一个进程必须首先以>
写模式打开该文件FIFO_FILE1,否则会出现死锁*/
                printf("start open '%s' for read and it will blocked untill write endpoint opened...\n",FIFO_FILE1);
                if((fdr_fifo = open(FIFO_FILE1,O_RDONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat read endpoint failure:%s\n",FIFO_FILE1,strerror(errno));
                        return -1;
                }

                printf("start open '%s' for write...\n",FIFO_FILE2);
                if((fdw_fifo = open(FIFO_FILE2,O_WRONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat write endpoint failure:%s\n",FIFO_FILE2,strerror(errno));
                        return -1;
                }
        }

         else
        {
                /*这里以只写模式打开命名管道FIFO_FILE1的写端,默认是阻塞模式;如果命名管道的读端不打开,则open()将会一直阻塞,因为前一个进程是先以读模式>
打开该管道文件的读端,所以这里必须先以写模式打开该文件的写端,否则将会出现死锁*/
                printf("start open '%s' for write and it will blocked untill read endpoint opened..\n",FIFO_FILE1);
                if((fdw_fifo = open(FIFO_FILE1,O_WRONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat write endpoint failure:%s\n",FIFO_FILE1,strerror(errno));
                        return -1;
                }

                printf("start open '%s' for read...\n",FIFO_FILE2);
                if((fdr_fifo = open(FIFO_FILE2,O_RDONLY)) < 0)
                {
                        printf("Open fifo[%s] for chat read endpoint failure:%s\n",FIFO_FILE2,strerror(errno));
                        return -1;
                }
        }

        printf("start chating with another program now,please input message now:\n");
        while( !g_stop )
        {
                FD_ZERO(&rdset);
                FD_SET(STDIN_FILENO,&rdset);
                FD_SET(fdr_fifo,&rdset);

                /*select()多路复用监听标准输入和作为输入的命名管道读端*/
                rv = select(fdr_fifo+1,&rdset,NULL,NULL,NULL);
                if(rv < 0)
                {
                        printf("Select get timeout or error:%s\n",strerror(errno));
                        continue;
                }

                /*如果是作为输入的命名管道上有数据到来则从管道上读入数据并打印到标准输出上*/
                if(FD_ISSET(fdr_fifo,&rdset))
                {
                        memset(buf,0,sizeof(buf));
                        rv = read(fdr_fifo,buf,sizeof(buf));
                        if(rv < 0)
                        {
                                printf("read data from FIFO get error:%s\n",strerror(errno));
                                break;
                        }
                        else if(0 == rv)
                        {
                                printf("Another side of FIFO get closed and program will exit now\n");
                                break;
                        }

                        printf("<--%s",buf);
                }

                /*如果是作为输入上有数据到来,则从标准输入上读入数据后,将数据写入到作为输出的命名管道的另外一个进程*/
                if(FD_ISSET(STDIN_FILENO,&rdset))
                {
                        memset(buf,0,sizeof(buf));
                        fgets(buf,sizeof(buf),stdin);
                        write(fdw_fifo,buf,strlen(buf));
                }
        }
}

4、运行结果

./fifo_chat

 由图我们可以看图,我们仅仅输入一个参数是不行的。

./fifo_chat  0

./fifo_chat  1

         通过命名管道,我们成功实现了进程0和进程1聊天的程序。并且,我们也可以在文件系统中看到创建的两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2),并且大小为0 。

四、补充

        最后,就上文提到的一些知识点做一些简单的补充。

1、wait()函数

函数原型如下:

#include  <sys/types.h>
#include  <wait.h>

 int wait(int * status)

(1)函数功能:

         父进程一旦调用wait函数就立即开始阻塞,然后wait会分析当前进程的某个子进程是否已经退出,如果让它找到了这样一个退出的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回,如果没有找到,就一直阻塞,直至找到一个结束的子进程或接收到了一个指定的信号为止。

【注意:  当父进程忘记调用wait()等待已终止的子进程,子进程就会进入一种没有父进程的状态,此时子进程就是zombie(僵尸)进程。】

(2)参数status:

        用来保存被收集进程退出时的状态,它是一个指向int类型的指针,如果我们对这个子进程如何死掉的不在意,只想这把这个被僵尸进程消灭掉,就把这个参数置为NULL。如果status的值不是NULL,wait把子进程的退出状态取出并存入其中,这是一个整数值(int)。

2、acess()函数

函数原型如下:

#include    <unistd.h>

 
int access(const char * pathname, int mode)

参数说明:

(1)第一个参数pathname:需要检测的文件路径名。

(2)第二个参数mode:需要测试的操作模式。

mode说明如下:

R_OK              测试读许可权
W_OK              测试写许可权
X_OK              测试执行许可权
F_OK              测试文件是否存在 

3、Linux下文件系统权限

         对于一个文件来说能访问它的用户大致分为三种: 拥有者(owner)、所属组(grouper)、其他人(other)。

输入ls -al,出现如图所示的情况,第一列即为文件权限。

 接下来给大家简单讲解一下mkfifo()里mode值的计算:

第一个字母代表文件的类型,如“d”代表目录、“p”代表管道、“-”代表文件等等。

接下来共有九位,三位为一组,分别对应owner权限、grouper权限和other权限。r代表可读、w代表可写、x代表可执行(文件代表可执行、目录代表可进入),“-”则代表该成员没有相应的权限。

        接下来讲一下“421”法,“r”用4来表示,“w”用2来表示,“x”用1来表示。那举个例子,计算drwxrwxrx-就是(4+2+1)(4+2+1)(4+2+0),也就是该目录的权限为“0777”(第一位0表示特殊权限,暂时不用理会)。

文章到这里就结束了,如果哪里有问题,欢迎大家在评论区指出!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/353306.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

flyway使用配置参数和注意事项介绍

文章目录 业务场景参数介绍initSqlsbaselineOnMigratebaselineVersiontargetvalidateOnMigrate SQL注意事项 业务场景 对于生产环境&#xff0c;随着项目版本迭代&#xff0c;数据库结构也会变动。如果一个项目在多个地方实施部署&#xff0c;且版本不一致&#xff0c;就需要一…

lqb日志08

一只小蒟蒻备考蓝桥杯的日志 文章目录 笔记坐标相遇判断工作调度问题&#xff08;抽象时间轴绘制&#xff09; 刷题心得小结 笔记 坐标相遇判断 我是小懒虫&#xff0c;碰了一下运气&#xff0c;开了个“恰当”的数&#xff08;7000&#xff09;如果&#xff0c;7000次还不能…

使用sdbg执行smali简单片段解混淆

https://github.com/CalebFenton/simplify/releases/download/v1.3.0/sdbg-0.1.0.jar "C:\Program Files\Java\jre-1.8\bin\java.exe" -jar sdbg-0.1.0.jar smali "Lu/ad;->c()V"其中smali为文件夹名称。 ###### Class p124u.C12414ad (u.ad) .class …

Modern C++ std::unique_ptr的实现原理

​ unique_ptr是一个非常简单的类,没有计数没有原子操作,非常类似纯指针。 它的类定义也非常简单: 它针对数组做了模板偏特化,因为它得支持数组操作比如Arr[i]。 unique_ptr的本质就是std::tuple, 里面第一项为指针指向管理对象,第二项为deleter:是一个函数指针或仿函数…

电脑屏幕色彩调整

显卡驱动 如果你的电脑是笔记本且没有独显直连&#xff0c;那你就不能在独显里面调屏幕色彩&#xff0c;就要去下载对应核显的驱动&#xff0c;然后去核显的驱动程序里面可以调节。比如&#xff1a;我的笔记本是华硕天选2&#xff0c;无独显直连&#xff0c;锐龙处理器&#x…

Nginx基础篇【一】

Nginx基础篇【一】 一、Nginx基础篇【一】1.1.背景介绍1.2.名词解释1.2.1. WEB服务器&#xff1a;1.2.2. HTTP:1.2.3. POP3/SMTP/IMAP&#xff1a;1.2.4. 反向代理1.2.5.常见服务器对比1.2.5.1.IIS1.2.5.2.Tomcat1.2.5.3.Apache1.2.5.4.Lighttpd1.2.5.5.其他的服务器 1.3.Nginx…

跟着cherno手搓游戏引擎【12】渲染context和首个三角形

渲染上下文&#xff1a; 目的&#xff1a;修改WindowsWindow的结构&#xff0c;把glad抽离出来 WindowsWindow.h:新建m_Context #pragma once #include "YOTO/Window.h" #include <YOTO/Renderer/GraphicsContext.h> #include<GLFW/glfw3.h> #include…

Ps:将文件载入堆栈

Ps菜单&#xff1a;文件/脚本/将文件载入堆栈 Scripts/Load Files into Stack 将文件载入堆栈 Load Files into Stack脚本命令可用于将两个及以上的文件载入到同一个 Photoshop 新文档中。 载入的每个文件都将成为独立的图层&#xff0c;并使用其原始文件名作为图层名。 Photos…

GraphicsMagick 的 OpenCL 开发记录(二十五)

文章目录 如何修复R6025 pure virtual function call问题 <2022-04-19 周二> 如何修复R6025 pure virtual function call问题 运气好&#xff0c;修复了这个问题。即&#xff0c;在ExitInstance()函数中调用一下MagickLib::DestroyMagick();即可。 过程中也经历了尝试…

CSS探索浏览器兼容性

学习如何探索浏览器的兼容性对于编写跨浏览器兼容的CSS代码非常重要。以下是一些学习CSS兼容性的方法&#xff1a; MDN文档&#xff1a;Mozilla开发者网络&#xff08;MDN&#xff09;提供了广泛而详细的CSS文档&#xff0c;其中包含有关CSS属性、选择器和功能的信息。在MDN上…

最新技术实战 | 无视杀软使用远控工具进行横向移动Tips

最新技术实战 | 无视杀软使用远控工具进行横向移动Tips。 杀软是什么意思&#xff1f;杀软是杀毒软件的简称&#xff0c;取的杀毒首字与软件首字组合而成&#xff0c;将杀毒软件简要的称之为杀软&#xff0c;所以&#xff0c;杀软的意思就是杀毒软件&#xff0c;专注于信息领域…

day34_js

今日内容 0 复习昨日 1 事件 1.1 事件介绍 1.2 事件绑定方式 1.3 不同事件的演示 2 DOM操作 2.1 概述 2.2 查找元素 2.3 元素内容的查找和设置 2.4 元素属性的查找和设置 2.5 元素CSS样式的查找和设置 2.6 创建元素 2.7 创建文本节点 2.8 追加元素 2.9 删除元素 3 案例练习 0 复…

基于springboot+vue的明星周边产品销售网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

如何快速在阿里云上更新幻兽帕鲁服务器?

如何快速在阿里云上更新幻兽帕鲁服务器&#xff1f;幻兽帕鲁更新之后&#xff0c;服务器需要同步更新才能继续游戏&#xff0c;大家可以按照文章操作完成服务升级。 1、如果大家是通过阿里云计算巢部署的&#xff0c;请参考&#xff1a;计算巢部署更新方式。 2、如果不是通过阿…

前端——HTML

目录 文章目录 前言 一.HTML的基本标签 二.HTML标签 1.块级标签 1.1块级标签特征 1.2标题标签 ​编辑 1.3 水平线标签 1.4 段落标签 1.5 无序列表标签 1.6 有序列表标签 1.7 表格标签 1.8层标签 1.9 表单 2. 行级标签 2.1行级标签特征 2.2图像标签 2.3 范围…

epoll示例

一、服务端 下面是一个使用epoll机制在Linux上编写的简单套接字程序示例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include &l…

一天吃透面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 分享50道Java并发高频面试题。 线程池 线程池&#xff1a;一个管理线程的池子。 为什么平时都是使用线程池创建线程&#xff0c;直接new一个线程不好吗&#xff1f; 嗯&#xff0c;手动创建线程有两个缺点 不受控风险频繁创…

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编 一、Switch语句1、switch语句 是if语句的简写2、break加与不加有什么特点?default语句可以省略吗&#xff1f;3、游戏中的switch语句&#xff08;示例&#xff09;4、添加case后面的值&#xff0c;一个一个增加&…

LLM之llm-viz:llm-viz(3D可视化GPT风格LLM)的简介、安装和使用方法、案例应用之详细攻略

LLM之llm-viz&#xff1a;llm-viz(3D可视化GPT风格LLM)的简介、安装和使用方法、案例应用之详细攻略 目录 llm-viz的简介 1、LLM可视化 2、CPU模拟&#xff08;WIP&#xff1b;尚未公开&#xff01;&#xff09; llm-viz的安装和使用方法 llm-viz的案例应用 1、三维可视化…

【云原生】k8s图形化管理工具之rancher

k8s的图形化工具-----rancher rancher是一个开源的企业级多集群的k8s管理平台。 rancher和k8s区别: 都是为了容器的调度和编排系统&#xff0c;但是rancher不仅能够调度&#xff0c;还能管理k8s集群&#xff0c;自带监控(普罗米修斯)&#xff0c;大公司都是图形化。 ranche…
最新文章