Linux管道共享内存

前言

进程虽然是独立运行的个体,但它们之间有时候需要协作才能完成一项工作,比如有两个进程需要同步数据,进程 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 memoryshmdt(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);//相同的方法创建出相同的keyif(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");//开始通信//detachesshmdt(mesg);printf("client detaches success!\n");return 0;
}

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

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

相关文章

项目七:学会使用python爬虫解析库(小白大成级)

前期我们学会了怎么使用python爬虫请求库和解析库的简单应用和了解&#xff0c;同时能够对爬虫有一个较为清晰的体系&#xff0c;毕竟简单的爬虫基本上都是请求数据——解析数据——存储数据的大概流程。 那么回忆一下&#xff0c;请求库我们学的是requests模块&#xff0c;解…

项目管理-项目开发计划介绍

目录 一、内容总览 二、项目开发计划概述 2.1 概述 2.2 项自开发计划的目的和作用 2.3 项目开发计划的内容 2.3.1 工作计划 2.3.2 人员组织计划 2.3.3 设备采购和资源供应计划 2.3.4 配置管理计划 2.3.5 进度安排计划 2.3.6 成本投资计划 2.3.7 质量保证计划 2.3.8…

实现I.MX6ULL开发板与Windows和Ubuntu系统之间的通信

虚拟机与主机之间的连接方式确实包括桥接模式、NAT模式和仅主机模式。 桥接模式&#xff08;Bridged&#xff09;&#xff1a;在桥接模式下&#xff0c;虚拟机通过虚拟交换机直接连接到主机的物理网络上&#xff0c;就像一台独立的物理机器一样&#xff0c;拥有自己的IP地址&a…

[阅读笔记23][JAM]JOINTLY TRAINING LARGE AUTOREGRESSIVE MULTIMODAL MODELS

这篇论文是24年1月发表的&#xff0c;然后是基于的RA-CM3和CM3Leon这两篇论文。它所提出的JAM结构系统地融合了现有的文本模型和图像生成模型。 主要有两点贡献&#xff0c;第一点是提出了融合两个模型的方法&#xff0c;第二点是为混合模型精心设计的指令微调策略。 下图是一个…

Unity射击游戏开发教程:(1)玩家控制

玩家的移动 玩家控制和移动是视频游戏中最酷的事情之一,因为你正在控制游戏中的某些东西 现在游戏中的玩家是我们的蓝色方块英雄。我在游戏开发中了解到,游戏是用简单的对象制作原型,然后添加所有漂亮的艺术和声音。代码… 我们要做的第一件事是在游戏开始时为玩家提供一个…

MATLAB 体素滤波(62)

MATLAB 体素滤波(62) 一、算法介绍二、算法实现1.代码(已验证,直接运行)一、算法介绍 这里的代码完成文件读入,体素滤波,效果显示,结果输出的操作,下面是效果截图,后面是代码。 体素滤波(Voxel Filtering)是一种用于三维点云数据处理的方法,其原理类似于二维图像…

力扣HOT100 - 142. 环形链表 II

解题思路&#xff1a; public class Solution {public ListNode detectCycle(ListNode head) {Set<ListNode> set new HashSet<>();while (head ! null) {if (!set.add(head)) {return head;}head head.next;}return null;} }

广西建筑模版厂家批发供应,当天发货全国可达

近年来&#xff0c;建筑行业蓬勃发展&#xff0c;对高质量的建筑模板需求量逐渐增加。在这个竞争激烈的市场中&#xff0c;贵港市能强优品木业有限公司以其出色的产品和卓越的服务迅速崭露头角&#xff0c;成为知名的建筑模板生产厂家。 作为一家拥有25年生产经验的公司&#x…

用FRP配置toml文件搭建内网穿透

需求场景 1、一台外网可访问的有固定ip的云服务器&#xff0c;Ubuntu系统 2、一台外网无法访问的无固定ip的本地家用电脑&#xff0c;Ubuntu系统 需求&#xff1a;将云服务器搭建为一台内网穿透服务器&#xff0c;实现通过外网访问家用电脑&#xff08;网页&#xff09;的功能。…

人工智能|机器学习——基于机器学习的信用卡办卡意愿模型预测项目

一、背景介绍 在金融领域&#xff0c;了解客户的信用卡办卡意愿对于银行和金融机构至关重要。借助机器学习技术&#xff0c;我们可以根据客户的历史数据和行为模式预测其是否有办理信用卡的倾向。本项目通过Python中的机器学习库&#xff0c;构建了两个常用的分类模型&#xff…

Ardupilot无人船(车)自动调参

无人船&#xff08;车&#xff09;快速调参脚本简化了为无人船&#xff08;车&#xff09;角速率和速度控制器调参的过程。 该脚本应该在无人船&#xff08;车&#xff09;在Circle模式下循环绕圈时运行。它记录转向和油门输出以及由此产生的响应(例如无人船&#xff08;车&am…

高精度算法(2)

前言 延续上次所讲的内容再对乘法和除法进行说明&#xff0c;希望有所帮助 注意这里的乘除法都是针对于整数如果要是涉及到小数&#xff0c;我们得使用二分法 通过二分同样可以解决小数精度问题 高精度乘法 思路 我们只能用字符串来读取一个很大很大的数&#xff0c;所以…

阿赵UE学习笔记——28、粒子系统Niagara简介

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的使用。这次开始学习粒子系统的使用。 一、Cascade系统 在介绍UE5的Niagara系统之前&#xff0c;必须先介绍一下旧版本的粒子系统。   在UE4的时候&#xff0c;虚幻引擎的粒子系统叫做Cascade&#x…

【论文阅读】Attention is all you need

摘要 主要的序列转换模型是基于复杂的循环或卷积神经网络&#xff0c;其中包括一个编码器和一个解码器。性能最好的模型还通过一种注意力机制将编码器和解码器连接起来。我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xff0c;完全基于注意机制&#xff0c;完全…

杜牧是唐朝最风流的诗人

杜牧&#xff0c;有“杜紫薇”之称&#xff0c;是宰相杜佑的孙子。李白、杜甫是“大李杜”。杜牧、李商隐是“小李杜”。 杜牧23岁写了《阿房宫赋》&#xff0c;一朝成名天下知。 唐朝晚年&#xff0c;科举和仕途没有出路&#xff0c;杜牧寄情享乐。杜牧怀才不遇&#xff0c;…

C语言--函数递归

目录 1、什么是递归&#xff1f; 1.1 递归的思想 1.2 递归的限制条件 2. 递归举例 2.1 举例1&#xff1a;求n的阶乘 2.2 举例2&#xff1a;顺序打印⼀个整数的每⼀位 3. 递归与迭代 扩展学习&#xff1a; 早上好&#xff0c;下午好&#xff0c;晚上好 1、什么是递归&…

Ubuntu 22.04.4安装Docker引擎

正文共&#xff1a;1024 字 13 图&#xff0c;预估阅读时间&#xff1a;1 分钟 我们前面安装了几次Ubuntu的操作系统&#xff08;Ubuntu 23.10通过APT安装Open vSwitch&#xff09;&#xff0c;在开始之前&#xff0c;我还是简单提醒一下&#xff0c;从Ubuntu下载页面&#xff…

因式分解技巧1-----一次提净

什么是因式分解&#xff1f; 在小学&#xff0c;我们都学过质因数分解。就比如&#xff1a;&#xff0c;然而我们可以发现&#xff01;4此时还没有被分解完&#xff0c;于是&#xff1a; 则&#xff1a; 这是小学的质因数分解。那么我们一起看看初中的因式分解。 例1&…

SpringBoot3 函数式web 小记

前言&#xff1a;函数式web是spring5.2之后的一个新特性&#xff0c;Spring Boot 3 进一步优化了这一模型&#xff0c;为开发现代 Web 应用提供了更加灵活、简洁的方法&#xff1b; 函数式web的四大核心对象 - RouterFunction&#xff1a;定义路由信息 - RequestPredicates&am…