uc_12_进程间通信IPC_有名管道_无名管道

1  内存壁垒

       进程间天然存在内存壁垒,无法通过交换虚拟地址直接进行数据交换:

        每个进程的用户空间都是0~3G-1(32位系统),但它们所对应的物理内存却是各自独立的。系统为每个进程的用户空间维护一张专属于该进程的内存映射表,记录虚拟内存到物理内存的对应关系,因此在不同进程之间交换虚拟内存地址是毫无意义的。

        所有进程的内核空间都是3G~4G-1,它们所对应的物理内存只有一份,系统为所有进程的内核空间维护一张内存映射表init_mm.pgd,记录虚拟内存到物理内存的对应关系,因此不同进程通过系统调用所访问的内核代码和数据是同一份。

        用户空间的内存映射表会随着进程的切换而切换,内核空间的内存映射表不变:

        

        Unix/Linux系统(32位)中的每个进程都拥有4G字节大小的专属于自己的虚拟内存空间,出去内核空间的1G,每个进程都有一张独立的内存映射表(内存分页表)记录着虚拟内存页和物理内存页之间的映射关系。

        同一个虚拟内存地址,在不同的进程中,会被映射到完全不同的物理内存区域,因此在多个进程之间以交换虚拟内存地址的方式交换数据是不可能的。

        鉴于进程之间天然存在的内存壁垒,要想实现多个进程间的数据交换,就必须提供一种专门的机制,这就是所谓的进程间通信(InterProcessCommunication,IPC

2  进程间通信(IPC)的种类

2.1  命令行参数

        在通过exec ()函数创建新进程时,可以为其指定命令行参数——借助命令行参数,可将创建者进程的某些数据传入新进程

        execl ("./login", "login", "username", "password", NULL);

2.2  环境变量

        类似地,也可在调用exec ()函数时为新进程提供环境变量:

        sprintf (envp[0], "USERNAME=%s", username);

        sprintf (envp[1], "PASSWORD=%s", password);

        execl ("./login", "login", NULL, envp);

2.3  内存映射文件

        通信双方分别将自己的一段虚拟内存映射到同一个文件中:mmap()

2.4  管道

        管道是Unix系统中最古老的进程间通信方式,并且所有的Unix系统和包括Linux系统在内的各种类Unix系统也都提供这种进程间通信机制。管道有2种限制:

        1  管道都是半双工的,数据只能沿着一个方向流动,类似对讲机,而非手机。

        2  管道只能在具有公共祖先的进程之间使用。通常一个管道由一个进程创建,然后该进程通

            过fork()函数创建子进程,父子进程之间通过管道交换数据。

        大多数Unix/Linux系统出了提供传统意义上的无名管道以外,还提供有名管道,对后者而言,第2中限制已不复存在。

2.5  共享内存

        共享内存允许两个或两个以上的进程共享同一块给定的内存区域。因为数据不需要在通信诸方之间来回复制,所以这是速度最块的一种进程间通信方式。

2.6  消息队列

        消息队列是由系统内核负责维护并可在多个进程之间共享存取的消息链表。优点是:

        传输可靠、流量受控、面向有结构的记录、支持按类型过滤。

2.7  信号量

        与共享内存和消息队列不同,信号量并不是为了解决进程间的数据交换问题。

        信号量关注的是有限的资源如何在无限的用户间合理分配,即资源竞争问题。

2.8  本地套接字

        BSD版本的有名管道。编程模型和网络通信统一。

3  有名管道(FIFO)

        有名管道是一种特殊的文件,它的路径名存在于文件系统中。

        有名管道文件在磁盘上只有i节点,没有数据块,也不保存数据。数据由内核操作。

3.1  mkfifo 命令

         通过shell命令mkfifo,基于有名管道实现进程间通信的逻辑模型:

        

        通过mkfifo命令可以创建有名管道文件:

                $ mkfifo myfifo

        即使是毫无亲缘关系的进程,也可以通过有名管道文件通信:

                $ echo 'Hello, FIFO!' > myfifo

                $ cat myfifo

                Hello, FIFO!

3.2  mkfifo()函数

        通过mkfifo()函数,基于有名管道实现进程间通信的编程模型:

        

        #include <sys/stat.h>

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

                功能:创建有名管道(文件)

                pathname:有名管道文件的路径

                mode:权限模式

                返回值:成0-1 

//wfifo.c  写入有名管道文件
#include<stdio.h>
#include<string.h>
#include<unistd.h>// write() close()
#include<fcntl.h>// open()
#include<sys/stat.h>// mkfifo()

int main(void){
    //创建有名管道
    printf("%d进程:创建有名管道\n",getpid());
    if(mkfifo("./fifo",0664) == -1){
        perror("mkfifo");
        return -1;
    }
    //打开有名管道
    printf("%d进程:打开有名管道\n",getpid());
    int fd = open("./fifo",O_WRONLY);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //写入有名管道
    printf("%d进程:发送数据\n",getpid());
    for(;;){
        //通过键盘获取数据 scanf fgets read fread fscanf 
        char buf[64] = {};
        fgets(buf,sizeof(buf),stdin); //这里不用减1,fgets()会自动减!!
        //当输入!时退出循环
        if(strcmp(buf,"!\n") == 0){
            break;
        }
        //写入管道文件
        if(write(fd,buf,strlen(buf)) == -1){
            perror("write");
            return -1;
        }
    }
    
    //关闭有名管道
    printf("%d进程:关闭有名管道\n",getpid());
    close(fd);
    //删除有名管道
    printf("%d进程:删除有名管道\n",getpid());
    unlink("./fifo");
    printf("%d进程:大功告成\n",getpid());
    return 0;
}//编译后,开两终端,一个执行wfifo,一个执行rfifo
//rfifo.c  读取有名管道文件
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    //打开有名管道
    printf("%d进程:打开有名管道\n",getpid());
    int fd = open("./fifo",O_RDONLY);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //读取有名管道
    printf("%d进程:接收数据\n",getpid());
    for(;;){
        //读取有名管道
        char buf[64] = {};
        ssize_t size = read(fd,buf,sizeof(buf) - 1);
        if(size == -1){
            perror("read");
            return -1;
        }
        if(size == 0){
            printf("%d进程:对方关闭管道文件\n",getpid());
            break;
        }
        //显示
        printf("%s",buf);
    }
    //关闭有名管道
    printf("%d进程:关闭有名管道\n",getpid());
    close(fd);
    printf("%d进程:大功告成\n",getpid());
    return 0;
}//编译后,开两终端,一个执行wfifo,一个执行rfifo

 

4  无名管道(PIPE)

        通过pipe()函数,基于无名管道实现进程间通信的编程模型(5步):

        1)父进程调用pipe()函数在系统内核中创建无名管道对象,

             并通过该函数的输出参数pipefd,

             获得分别用于写该管道的两个 文件描述符pipefd[0]和pipefd[1]。

                

        2) 父进程调用fork()函数,创建子进程。

               子进程复制父进程的文件描述符表,因此子进程同样持有pipefd[0]和pipefd[1]  。

                

        3) 负责写数据的进程关闭无名管道对象的读端描述符pipefd[0],

               复测读数据的进程关闭无名管道对象的写端描述符pipefd[1]。

                

        4)父子进程通过无名管道对象以半双工的方式传输数据。 

              如果需要在父子进程间双向通信,一般会创建两个管道,一个从父流向子,一个相反。

                

        5)父子进程分别关闭自己所持有的写端或读端文件描述符。

              在相关联的所有文件描述符都被关闭后,该无名管道对象即从内核中被销毁。

                

//pipe.c  无名管道演示
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    //父进程创建无名管道
    printf("%d进程:创建无名管道\n",getpid());
    int fd[2];//用来输出管道读端写端描述符
    if(pipe(fd) == -1){
        perror("pipe");
        return -1;
    }
    printf("fd[0] = %d\n",fd[0]);
    printf("fd[1] = %d\n",fd[1]);
    //父进程创建子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    //子进程代码,从管道中读取数据
    if(pid == 0){
        printf("%d进程:接受数据\n",getpid());
        printf("%d进程:关闭写端\n",getpid());
        close(fd[1]);
        for(;;){
            //通过读端描述符读取数据
            char buf[64] = {};
            ssize_t size = read(fd[0],buf,sizeof(buf)-1);
            if(size == -1){
                perror("read");
                return -1;
            }
            if(size == 0){
                printf("%d进程:对方关闭写端描述符\n",getpid());
                break;
            }
            //显示
            printf("--->%s",buf);
        }
        printf("%d进程:关闭读端\n",getpid());
        close(fd[0]);
        printf("%d进程:大功告成\n",getpid());
        return 0;//!!!!
    }
    //父进程代码,向管道中写入数据
    printf("%d进程:发送数据\n",getpid());
    printf("%d进程:关闭读端\n",getpid());
    close(fd[0]);
    for(;;){
        //通过键盘获取数据
        char buf[64] = {};
        fgets(buf,sizeof(buf),stdin);
        //!退出
        if(strcmp(buf,"!\n") == 0){
            break;
        }
        //通过管道写端写入
        if(write(fd[1],buf,strlen(buf)) == -1){
            perror("write");
            return -1;
        }
    }
    printf("%d进程:关闭写端\n",getpid());
    close(fd[1]);
    //父进程收尸
    if(wait(NULL) == -1){
        perror("wait");
        return -1;
    }
    printf("%d进程:大功告成\n",getpid());
    return 0;
}//编译执行

5  管道须知

         1)从写端已被关闭的管道读取,只要管道中还有数据,依然可以被正常读取,一致到管道中没有数据了,这时read()函数会返回0(不是返回-1,也不是阻塞),指示读到文件尾。

        2)向读端已被关闭的管道写入,会直接出发SIGPIPE(13)信号。该信号的默认操作是终止执行写入动作的进程。但如果执行写入动作的进程事先13信号的处理设置为忽略或捕获,则write()函数会返回-1,并置errno为EPIPE。

        3)系统内核通常为每个管道维护一个4096字节的内存缓冲区(新系统更大)。如果写管道时发现缓冲区的空闲空间不足以容纳此次write()函数所要写入的字节,则write()函数阻塞,直到缓冲区的空闲空间变得足够大为止。

        4)读取一个写段处于开放状态的空管道,直接导致read()函数阻塞

6  管道符 | 的原理

        1)Unix/Linux系统中的多数shell环境都支持,

              通过管道符号 "|" 将前一个命令的输出作为后一个命令的输入:

                $ ls -l /etc | more           实现按空格键翻页

                $ ifconfig | grep inet      过滤得到ip地址

        2)系统管理员通常用这种方法,把多个简单的命令连接成一条工具链,解决复杂问题:

                $ 命令1  |  命令2  |  命令3

        3)假设用户输入以下命令:a | b,管道符工作原理如下:

                Shell进程调用fork()函数创建子进程A

                子进程A调用pipe()函数创建无名管道,而后执行:

                                dup2 (pipefd[1], STDOUT_FILENO);

                子进程A调用fork()函数创建孙进程B,孙进程B执行:

                                dup2 (pipefd[0], STDOUT_FILENO);

                子进程A和孙进程B分别调用exec ()函数创建a、b进程。

                a进程所有的输出都通过写段进入管道,而b进程所有的输入则来自管道的读端。

                

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

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

相关文章

【每日一题】1657. 确定两个字符串是否接近-2023.11.30

题目&#xff1a; 1657. 确定两个字符串是否接近 如果可以使用以下操作从一个字符串得到另一个字符串&#xff0c;则认为两个字符串 接近 &#xff1a; 操作 1&#xff1a;交换任意两个 现有 字符。 例如&#xff0c;abcde -> aecdb操作 2&#xff1a;将一个 现有 字符的…

linux 消息队列apache-activemq服务的安装

1.下载 官网下载地址&#xff1a;https://activemq.apache.org/ 操作如下&#xff1a; 2. 解压 执行&#xff1a;tar -zxvf apache-activemq-5.18.3-bin.tar.gz -C /user/ 3. 进入目录 执行&#xff1a;cd /user/apache-activemq-5.18.3 4.修改配置文件 执行&#xff1…

物流实时数仓ODS层——Mysql到Kafka

目录 1.采集流程 2.项目架构 3.resources目录下的log4j.properties文件 4.依赖 5.ODS层——OdsApp 6.环境入口类——CreateEnvUtil 7.kafka工具类——KafkaUtil 8.启动集群项目 这一层要从Mysql读取数据&#xff0c;分为事实数据和维度数据&#xff0c;将不同类型的数据…

王道数据结构课后代码题p40 4.在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值唯一) (c语言代码实现)

本题代码为 void deletemin(linklist* L)//找到最小值并删除 {lnode* p (*L)->next, * pre *L;lnode* s p,*sprepre;while (p ! NULL)//找到最小值{if (p->data < s->data){s p;spre pre;}p p->next;pre pre->next;}p s->next;spre->next p;…

「黄钊的AI日报·第二季」早鸟票,最后48小时~

每天5条AI内容点&#xff1a;不是新闻汇总&#xff0c;而是站在11年AI产品经理的视角&#xff0c;将原AI信息中的干货认知&#xff0c;提炼成我自己的文字、展示“what I see”。 做社群“AI产品经理大本营”6年以来&#xff0c;我都是在非常用心的输出AI干货&#xff1b;这份“…

时序预测 | Python实现TCN时间卷积神经网络价格预测

时序预测 | Python实现TCN时间卷积神经网络时间序列预测 目录 时序预测 | Python实现TCN时间卷积神经网络时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 时间卷积网络,TCN。 利用CNN技术处理时间序列数据。 卷基础层有三种,第一种是一维CNN,用于输…

Python语言学习笔记之七(JOSN应用)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 1、认识Json JSON (JavaScript Obiect Notation)是一种轻量级的数据交换格式&#xff0c;它是ECMAScript的一…

python高级练习题库实验1(A)部分

文章目录 题目1代码实验结果题目2代码实验结果题目3代码实验结果题目4代码实验结果题目总结题目1 输入一个整数,用于控制输出*的个数,输入日期,按照特定格式输出 研究下面的例子,并编写一个与这些例子完全相同的程序。 代码 import datetime# ask user for length of b…

ZPLPrinter Emulator SDK for .NET 6.0.23.1123​ Crack

ZPLPrinter Emulator SDK for .NET 适用于 .NET 的 ZPLPrinter 仿真器 SDK 允许您通过编写 C# 或VB.NET 代码针对任何 .NET Framework、.NET CORE、旧版 ASP.NET MVC 和 CORE、Xamarin、Mono 和通用 Windows 平台 (UWP) 作业。 适用于 .NET 的 ZPLPrinter 仿真器 SDK 允许您将…

ubuntu0.22.04.1安装mysql8.0及root密码注意

先看一下你的安装包是什么版本 apt list |grep mysql基本都是默认的8.0版本&#xff0c;然后安装&#xff1a; apt-get install mysql-server-8.0安装以后 &#xff0c;mysql默认启动&#xff1b; 一般root 是没有密码的&#xff0c;在本地直接回车登录 我们看一下密码插件 …

Kubernetes(K8s)_15_CNI

Kubernetes&#xff08;K8s&#xff09;_15_CNI CNI网络模型UnderlayMAC VLANIP VLANDirect Route OverlayVXLAN CNI插件FlannelCalico CNI配置内置实现 CNI CNI(Container Network Interface): 实现容器网络连接的规范 Kubernetes将网络通信可分为: Pod内容器、Pod、Pod与Se…

一个菜单两个二级路由的搭建

效果如下&#xff0c;而且这是最上方的菜单&#xff0c;需要进入以后重定向。 {path: /,name: HOME,component: ConsoleLayout, //这里也有router-viewmeta: {menu: false},redirect: {name: ManagerList},children: [{path: /rightsManage,name: RightsManage,component: () &…

【刷题笔记】串联所有单词的子串||暴力通过||滑动窗口

串联所有单词的子串 1 题目描述 https://leetcode.cn/problems/substring-with-concatenation-of-all-words/ 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 …

EasyMicrobiome-易扩增子、易宏基因组等分析流程依赖常用软件、脚本文件和数据库注释文件

啥也不说了&#xff0c;这个好用&#xff0c;给大家推荐&#xff1a;YongxinLiu/EasyMicrobiome (github.com) 大家先看看引用文献吧&#xff0c;很有用&#xff1a;https://doi.org/10.1002/imt2.83 还有这个&#xff0c;后面马上介绍&#xff1a;YongxinLiu/EasyAmplicon: E…

将项目放到gitee上

参考 将IDEA中的项目上传到Gitee仓库中_哔哩哔哩_bilibili 如果cmd运行ssh不行的话&#xff0c;要换成git bash 如果初始化后的命令用不了&#xff0c;直接用idea项放右键&#xff0c;用git工具操作

【Linux】进程控制--进程创建/进程终止/进程等待/进程程序替换/简易shell实现

文章目录 一、进程创建1.fork函数2.fork函数返回值3.写时拷贝4.fork常规用法5.fork调用失败的原因 二、进程终止1.进程退出码2.进程退出场景3.进程常见退出方法 三、进程等待1.为什么要进行进程等待2.如何进行进程等待1.wait方法2.waitpid方法3.获取子进程status4.进程的阻塞等…

【双向链表的实现】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. 双向链表的结构 2. 双向链表的实现 2.1 头文件 ——双向链表的创建及功能函数的定义 2.2 源文件 ——双向链表的功能函数的实现 2.3 源文件 ——双向链表功能的…

计算机网络:应用层(下篇)

文章目录 前言一 、电子邮件&#xff08;Email&#xff09;1.邮件服务器2.SMTP[RFC 2821]3.邮件报文格式4.邮件访问协议 二、DNS&#xff08;域名系统&#xff09;1.DNS的历史2.DNS总体思路和目标&#xff08;1&#xff09;问题1&#xff1a;DNS名字空间&#xff08;2&#xff…

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动 链接&#xff1a;https://pan.baidu.com/s/16lpEW6L5jrHfhsG7EXocLw?pwdkryy 提取码&#xff1a;kryy <!--社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 s…

rest_framework_django 学习笔记二(视图路由)

rest_framework_django 学习笔记二&#xff08;视图路由&#xff09; rest_framwork_django学习笔记一(序列化器) 一、rest framework 中Request 与 Response 1、Request REST framework 传入视图的request对象不再是Django默认的HttpRequest对象&#xff0c;二是REST Fame…