Linux管道共享内存

news2024/11/15 9:35:26

前言

进程虽然是独立运行的个体,但它们之间有时候需要协作才能完成一项工作,比如有两个进程需要同步数据,进程 A 把数据准备好后,想把数据发往进程 B,进程 B 必须被提前通知有数据即将到来,或者进程 A 想发送信号给进程 B,以控制进程 B 的运行模式,再或者数据被多个进程共享时,数据变更后应该被所有进程看到,总之诸如此类的需求很多,操作系统必须要实现进程间的相互通信。

进程间通信方式有很多种,有管道、消息队列、共享内存、socket 网络通信等。在认识通信方式之前,先来认识几个概念。

公共资源

可以是公共内存、公共文件、公共硬件等,总之是被所有任务共享的一套资源。

临界区

若多个任务都访问同一公共资源,那么各任务中访问公共资源的指令代码组成的区域就称为临界区。临界区是指程序中那些访问公共资源的指令代码,即临界区是指令,并不是受访的静态公共资源。

互斥

是指某一时刻公共资源只能被 1 个任务独享,即不允许多个任务同时出现在自己的临界区中。公共资源在任意时刻只能被一个任务访问,即只能有一个任务在自己的临界区中执行,其他任务想访问公共资源时,必须等待当前公共资源的访问者完全执行完他自己的临界区代码后(使用完资源后)再开始访问。

管道

管道是进程间通信的方式之一,在 Linux 中一切皆文件,因此管道也被视为文件,只是该文件并不存在于文件系统上,而是只存在于内存中。

既然是文件,管道就要按照文件操作的函数来使用,因此也要使用open 、close 、read 、write 等方法来操作管道。管道通常被多个进程共享,而且存在于内存之中,因此共享的原理是所有进程在地址空间中都可以访问到它,管道其实就是内核空间中的内存缓冲区。

管道是用于存储数据的中转站,当某个进程往管道中写入数据后,该数据很快就会被另一个进程读取,之后可以用新的数据覆盖老数据,继续被别的进程读取,因此管道属于临时存储区,其中的数据在读取后可被清除。

管道是个环形缓冲区,环形缓冲区中一个指针用于读数据,另一个用于写数据。当缓冲区己满时,生产者要睡眠,并在睡眠前唤醒消费者,当缓冲区为空时,消费者要睡眠,并在睡眠前唤醒生产者。当缓冲区满或空时,使一方休眠,这是保证数据不丢失的方法。

管道有两端,一端用于从管道中读入数据,另一端用于往管道中写入数据。这两端使用文件描述符的方式来读取,故进程创建管道实际上是内核为其返回了用于读取管道缓冲区的文件描述符,一个描述符用于读,另一个描述符用于写。fd[0]用于读取管道,fd[1]用于写入管道。

image-20240420110732072

一般情况下,父子进程中都是一个读数据,一个写数据,并不会存在一方又读又写的情况,因此在父子进程中会分别关掉不使用的管道描述符。比如父进程负责往管道中写数据,它只需要fd[1]描述符,因此只可以通过 close系统调用关闭fd[0]。子进程负责从管道中读数据,它只需要fd[0]描述符,因此只可以通过close系统调用关闭fd[1]。

匿名管道

pipe()

image-20240420111055102

flags介绍

当没有数据可读时
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. 读端不读或者读的慢,写端要等读端
  2. 读端关闭,写端收到SIGPIPE信号直接终止
  3. 写端不写或者写的慢,读端等写端
  4. 写端关闭,读端读完pipe内部的数据,读到0说明到文件结尾。
  5. 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  6. 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
//管道是单向,面向字节流,管道是有大小的,自带同步机制
int main()
{
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)
    {
        perror("pipe false!");
        return 1;
    }
    printf("pipefd[0]:%d\n", pipefd[0]);
    printf("pipefd[1]:%d\n", pipefd[1]);

    // pipefd[0] refers to theread end of the pipe
    // pipefd[1] refers to the  write  end  of  the  pipe.

    //子进程写
    if(fork() == 0)
    {
        close(pipefd[0]);
        const char* msg = "hello world\n";
        while(1)
        {
            //只要有缓冲区就一直写
            sleep(1);
             write(pipefd[1], msg, strlen(msg));
        
            //验证管道的大小
            //write(pipefd[1], "c", 1);
            // count++;
            // printf("%d\n", count);
        }
       
    }
    close(pipefd[1]);
    //父进程读
    while(1)
    {
        char buffer[1024] = {0};
        //只要有数据就一直读
        ssize_t s = read(pipefd[0], buffer, sizeof(buffer));
        if(s == 0)
        {
            printf("写端退出\n");
            break;
        } 
        else if(s > 0) 
        {
           
            buffer[s] = '\0';
            printf("父进程读到子进程发来的:%s", buffer);
        }
        else break;
    }
    int status = 0;
    waitpid(-1, &status, 0);
    printf("exit code: %d\n", (status >> 8)&0xFF);
    printf("exit signal: %d\n", (status)&0x7F);

}

总结

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道
  2. 管道提供流式服务
  3. 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  4. 一般而言,内核会对管道操作进行同步与互斥
  5. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

命名管道相比匿名管道,可以在不同的进程之前进行通信。相比直接继承父进程资源,命名管道要想让不同进程看到同一份资源,是通过文件名来实现的。

mkfifo()

image-20240420112229698
实现一个client和server通信的例子。

client

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define MY_FIFO "./fifo"
int main()
{
   int fd = open(MY_FIFO, O_WRONLY);
   if(fd < 0)
   {
    perror("open error\n");
    return 1;
   }
   while(1)
   {
    printf("请输入字符");
    fflush(stdout);
    char buffer[32] = {0};
    ssize_t s = read(0, buffer, sizeof(buffer) - 1);
    if(s > 0)
    {
        buffer[s - 1] = 0;
        //printf("%s\n", buffer);
        write(fd, buffer, strlen(buffer));
    }
   }
   close(fd);
}

server

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#define MY_FIFO "./fifo"

int main()
{
    umask(0); //避免影响mkfifo的权限设置
    if(mkfifo(MY_FIFO, 0666) < 0)
    {
        perror("mkfifo error");
        return 1;
    }

    int fd = open(MY_FIFO, O_RDONLY);
    if(fd < 0)
    {
        perror("open error");
        return 2;
    }
    while(1)
    {
        char buffer[32] = {0};
        ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
        if(strcmp(buffer, "ls") == 0)
        {
            if(fork() == 0)
            {
                execl("/usr/bin/ls", "ls", "-a","-l", NULL);
                exit(1);
            }
            waitpid(-1, NULL, 0);
        }
            printf("client#:%s\n", buffer);
        }
        else if(s == 0)
        {
            printf("client quit...\n");
        }
        else
        {
            perror("read error");
        }
    }
    close(fd);
    return 0;
}

image-20240420094254168

共享内存

共享内存是最快的通信方式,systemV的IPC资源生命周期是随内核的,要通过显示释放[ipcrm -m shmid 删除memory ],或者os重启。不提供同步互斥机制。

创建共享内存shmget()

image-20240420163631476

参数

key是共享内存的名字,key能保证这个共享内存的名字是唯一的。

size:共享内存大小

shmflg:IPC_CREAT、IPC_EXCL 等IPC_CREAT和IPC_EXCL一起用能保证创建出来的共享内存是最新的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

共享内存链接到进程地址空间shmat()

image-20240420164101869

参数

shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

shmaddr为NULL,核心自动选择一个地址。
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。

公式:shmaddr-(shmaddr%SHMLBA)

shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

共享内存和当前进程脱离shmdt()

int shmdt(const void *shmaddr);

参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段。

控制共享内存shmctl()

image-20240420164835293

参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

CMD

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够的权限下,把共享内存的当前关联值设置为shmid_ds给出的值
IPC_RMID删除共享内存段

查看共享内存

image-20240420103030140

例子

server

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "./"
#define PROJ_ID 0x6666

#define SIZE 4096 //最好是4096的整数倍

int main()
{
    //ftok创建key (自定义路径,自定义ID)
    key_t key=ftok(PATH_NAME,PROJ_ID);
    if(key<0)
    {
        perror("ftok"); return 1;
    }
    
    //创建共享内存,返回的是共享内存的标识码
     int shmid=shmget(key,SIZE,IPC_CREAT);
    if(shmid<0)
    {
        perror("shmget"); return 2;
    }

    printf("key:%u,shmid:%d\n",key,shmid);

    //attaches the shared memory
    //返回指向共享内存的一个地址的指针
    char* mesg=(char*)shmat(shmid,NULL,0);

    printf("attaches the shared memory success!\n");

    //通信开始
    while(1){
        sleep(1); printf("%s\n",mesg);
    }

    //detaches the shared memory
    shmdt(mesg);
    printf("detaches shm success!\n");

    //删除共享内存段
    shmctl(shmid,IPC_RMID,NULL);
    printf("key:0x%x,shmid:%d->shm delete success\n",key,shmid);
    return 0;

}

client

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATH_NAME "./"
#define PROJ_ID 0x6666

#define SIZE 4096 //最好是4096的整数倍

int main()
{
    
    key_t key=ftok(PATH_NAME,PROJ_ID);//相同的方法创建出相同的key
    if(key<0)
    {
        perror("ftok"); return 1;
    }
    
    //创建共享内存,返回的是共享内存的标识码
     int shmid=shmget(key,SIZE,IPC_CREAT);
    if(shmid<0)
    {
        perror("shmget"); return 1;
    }

    //attaches the shared memory
    //返回指向共享内存的一个地址的指针
    char* mesg=(char*)shmat(shmid,NULL,0);
    printf("client attaches the shared memory success!\n");

    //开始通信
 	
    //detaches
    shmdt(mesg);
    printf("client detaches success!\n");
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark集群的搭建

1.1搭建Spark集群 Spark集群环境可分为单机版环境、单机伪分布式环境和完全分布式环境。本节任务是学习如何搭建不同模式的Spark集群&#xff0c;并查看Spark的服务监控。读者可从官网下载Spark安装包&#xff0c;本文使用的是spark-2.0.0-bin-hadoop2.7.gz。 1.1.1搭建单机版…

《乱弹篇(30)厌战的杜诗》

时下地球村有一伙成天叫嚣着“打打杀杀”、鼓吹快快发动战争的狂人&#xff0c;他们视老百姓的生命如草芥&#xff0c;毫不珍惜。没有遭受过战火焚烧的人&#xff0c;也跟着成天吠叫“快开战吧”。然而中国唐朝大诗人却是个“厌战派”&#xff0c;他对战争的厌恶集中表现在诗《…

实在RPA设计器试用导引

一、产品概述 实在RPA设计器是一款将人工智能(AI)与机器人流程自动化(RPA)深度融合的可视化自动流程编辑器。它通过AI推荐与桌面嵌入式交互&#xff0c;极大简化了RPA的使用难度&#xff0c;让普通业务人员也能轻松使用。实在RPA设计器具备以下核心优势&#xff1a; 兼容性&a…

我与C++的爱恋:类和对象(四)

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 ​ 朋友们大家好&#xff01;本篇是类和对象的最后一个部分。 一、static成员 声明为static的类成员称为类的静态成员&#xff0c;用static修饰的成员变量&#xff0c;称之…

系统架构最佳实践 -- 相关JAVA架构

1. java 类加载器架构 2. JVM 架构 3. Java 技术体系 4. 线程运行架构 5. Java 体系&#xff08;编译与运行&#xff09;结构 6. JMS 技术架构 7. JMX 技术架构 8. Spring 架构 9. Hibernate 架构 10. ibatis 架构 11. Struts2 架构 12. Struts1 架构 13. JBPM 14. EJB 技术架构…

Java面试八股之marshalling和demarshalling

marshalling和demarshalling Marshalling&#xff08;序列化&#xff09;是将内存中的对象状态转化为适合传输或存储的格式&#xff08;如字节流、JSON、XML&#xff09;&#xff0c;以便进行网络通信、持久化存储或跨平台/语言交互操作。Demarshalling&#xff08;反序列化&a…

渗透测试入门教程,从零基础入门到精通(非常详细)

目录 什么是渗透测试 渗透测试的重要性 渗透测试的前置技能 开始入门学习路线 什么是渗透测试 渗透测试&#xff0c;通常被视为模拟黑客的一种安全评估行为&#xff0c;其目的在于全面挖掘目标网站或主机的潜在安全漏洞。与真实的黑客攻击不同&#xff0c;渗透测试旨在发现…

9.MMD 基础内容总结及制作成品流程

前期准备 1. 导入场景和模型 在左上角菜单栏&#xff0c;显示里将编辑模型时保持相机和光照勾选上&#xff0c;有助于后期调色 将抗锯齿和各向异性过滤勾掉&#xff0c;可以节省资源&#xff0c;避免bug 在分辨率设定窗口&#xff0c;可以调整分辨率 3840x2160 4k分辨率 1…

05 MySQL--字段约束、事务、视图

1. CONSTRAINT 约束 创建表时&#xff0c;可以给表的字段添加约束&#xff0c;可以保证数据的完整性、有效性。比如大家上网注册用户时常见的&#xff1a;用户名不能为空。对不起&#xff0c;用户名已存在。等提示信息。 约束包括&#xff1a; 非空约束&#xff1a;not null检…

kafka实验部署

一、前期准备 二、kafka实验 在zookeeper后继续进行操作 2.1 为ndoe1、node2、node3作出部署 2.1.1 解压kafka压缩包&#xff08;node1举例&#xff09; 2.1.2 操作 将解压后的kafka移动到kafka&#xff0c;进入到kafka下的config中&#xff0c;复制文件 2.1.2.1 编辑server.pr…

LWIP开发之静态IP为什么接收和发送不了数据

使用的硬件开发板是探索者F4 V3版本 这里用的LWIP的lwIP例程7 lwIP_NETCONN_UDP实验 问了开发板的官方和其他人都说不清楚&#xff1b;搞了两天&#xff0c;浪费了两天时间&#xff1b; 最奇葩的问题还在于只能单片机发送&#xff0c;上位机能接收。而上位机发送单片机不能接…

虚拟机扩容方法

概述 我的虚拟机开始的内存是40G,接下来要扩成60GB 扩容步骤 步骤1 步骤2 步骤3 修改扩容后的磁盘大小&#xff0c;修改后的值只可以比原来的大&#xff0c;修改完成后点击扩展&#xff0c;等待扩展完成 步骤4 虽然外面扩展成功&#xff0c;但是新增的磁盘空间虚拟机内部还…

自媒体个人品牌IP策划打造孵化运营方案

【干货资料持续更新&#xff0c;以防走丢】 自媒体个人品牌IP策划打造孵化运营方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 ppt可编辑&#xff08;完整资料包含以下内容&#xff09;目录个人IP孵化方案概要&#xff1a; 1. 目标定位与市场分析 - 女性…

QMT和Ptrade有什么区别?该如何选择?

QMT&#xff08;Quantitative Model Trading&#xff09;和Ptrade&#xff08;Professional Trading&#xff09;是两种不同的交易策略和方法&#xff0c;它们在金融市场中被广泛应用。了解它们的区别有助于投资者根据自己的需求和目标做出选择&#xff1a; QMT&#xff08;量…

AI+PS快捷键大全!

hello&#xff0c;我是小索奇&#xff0c; 你会用Photoshop&#xff08;PS&#xff09;或者&#xff08;Illustrator&#xff09;AI吗&#xff1f;相信很多人都会接触到吧&#xff0c;但有一部分人很少用快捷键&#xff0c;仅凭借鼠标点击来实现功能&#xff0c;殊不知快捷键能…

在线拍卖系统,基于SpringBoot+Vue+MySql开发的在线拍卖系统设计和实现

目录 一. 系统介绍 二. 功能模块 2.1. 管理员功能模块 2.2. 用户功能模块 2.3. 前台首页功能模块 2.4. 部分代码实现 一. 系统介绍 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系…

感知机学习算法中的Novikoff定理证明中的隐含背景知识

一、引言 《统计学习方法》&#xff08;李航著&#xff09;第二章感知机学习时&#xff0c;其中的Novikoff定理是关于感知机算法收敛性的一个重要定理。这个定理保证了对于线性可分的数据集&#xff0c;感知机学习算法最终能够收敛到一个解&#xff0c;即存在一个权重向量 w 和…

投稿没被采纳不是水平问题可能是投稿方法不对

每周一次的信息宣传投稿任务,如同一面镜子,映照出我们在媒体传播领域的探索与成长。起初,我在执行这项任务时,怀着满腔热情,挥洒汗水创作出一篇篇通信稿件,却在投稿阶段屡遭挫折,多次尝试均未果,一度让我质疑自己的写作能力是否足以胜任这项工作。 那时,我采用的是传统的邮箱投…

智慧煤矿/智慧矿区视频汇聚存储与安全风险智能分析平台建设思路

一、建设背景 目前我国非常重视煤矿安全生产&#xff0c;并投入大量资金用于煤矿安全综合远程监控系统的研发。视频监控系统作为实现煤矿智能化无人开采的关键系统与煤矿安全生产的多系统协同分析与处理的关键信息源&#xff0c;在智慧矿山管控平台的建设中发挥着重要的作用。…

【微信小程序】解决分页this.setData数据量太大的限制问题

1、原始方法&#xff0c;每请求一页都拿到之前的数据concat一下后整体再setData loadData() {let that thislet data {}data.page this.data.pagedata.size this.data.sizefindAll(data).then(res > {if (res.data.code 1) {this.setData({dataList: this.data.dataLi…