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.hqwc.cn/news/637338.html

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

相关文章

Redis学习-Redis的九种数据结构

String &#xff08;字符串&#xff09; 虽然redis是用C语言编写&#xff0c;但是redis中的string是redis自己实现的字符串结构&#xff0c;叫Simple Dynamic String简称&#xff08;SDS&#xff09;&#xff0c;因为redis做为中间件会接受不同语言编写的程序传过来的字符串&a…

Mysql The last packet sent successfully to the server was 0 milliseconds ago.

项目启动后&#xff0c;报错&#xff0c;但是我的navicat 数据库连接工具是连接上的&#xff0c;没有问题的&#xff0c;但是程序就是连接不上。端口放开了&#xff0c;防火墙也放开了 先说问题&#xff1a;是网络问题&#xff0c; 如何解决&#xff1a;因为我的机子上又跑了…

vue-textarea光标位置插入指定元素

vue-textarea光标位置插入指定元素 需求 点击插入关键字的时候把内容插入到光标所在的位置 效果图 实现 html <div class"temlate-container"><div class"template-content"><el-inputref"modelContent"v-model"mould.m…

HarmonyOS NEXT 使用XComponent + Vsync 实现自定义动画

介绍 XComponent 提供了应用在 native 侧调用 OpenGLES 图形接口的能力&#xff0c;本文主要介绍如何配合 Vsync 事件&#xff0c;完成自定义动画。在这种实现方式下&#xff0c;自定义动画的绘制不在 UI 主线程中完成&#xff0c;即使主线程卡顿&#xff0c;动画效果也不会受…

Android 获取手机整体流量使用情况以及某个应用的流量的统计

源代码下载地址&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/b6ab9000c0bd

java高校办公室行政事务管理系统设计与实现(springboot+mysql源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的闲一品交易平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于mvc的高校办公室行政…

嵌入式Linux开发实操(十七):Linux Media Infrastructure userspace API

视频和无线电流媒体设备使用的Linux内核到用户空间API,包括摄像机、模拟和数字电视接收卡、AM/FM接收卡、软件定义无线电(SDR)、流捕获和输出设备、编解码器设备和遥控器。典型的媒体设备硬件如下: 媒体基础设施API就是用于控制此类设备的,分五个部分。 第一部分V4L2 API…

【编程Tool】VS code安装与使用配置保姆级教程

目录 1.软件介绍 2.软件下载&#xff1a; 3.安装 3.1. 双击可执行文件 3.2. 同意协议 3.3. 选择安装路径&#xff0c;默认在C盘 3.4. 点击下一步 3.5. 可选择所有附加任务 3.6. 点击安装 3.7. 等待安装 3.8. 点击完成 3.9. 安装成功 4.下载MinGW64 4.1. MinGW-64下载地址 &…

labview中循环停止事件的深入研究

1.错误用法 第一次值事件运行的时候空白按钮给的F值&#xff0c;第二次值事件运行的时候空白按钮给的T值&#xff0c;这时循环才真正结束。 2.正确用法之一 赋值和值改变事件从同时进行变成按顺序执行。 3.正确用法之二 值事件发生以后超时事件将T值赋值给结束条件&#xff…

堆的概念、堆的向下调整算法、堆的向上调整算法、堆的基本功能实现

目录 堆的介绍 堆的概念 堆的性质 堆的结构 堆的向下调整算法 基本思想&#xff08;以建小堆为例&#xff09; 代码 堆的向上调整算法 基本思想&#xff08;以建小堆为例&#xff09; 代码 堆功能的实现 堆的初始化 HeapInit 销毁堆 HeapDestroy 打印堆 HeapPrint …

【ZYNQ】Zynq 芯片介绍

Zynq 是 Xilinx 公司提出的全可编程 SoC 架构&#xff0c;集成了单核或多核 ARM 处理器与 Xilinx 16nm 或 28nm 可编程逻辑&#xff0c;包括 Zynq 7000 Soc&#xff0c;Zynq UltraScale MPSoC 和 Zync UltraScale RFSoC 等系列。本文主要介绍 Xilinx Zynq 7000 系列芯片架构、功…

[阅读笔记20][BTX]Branch-Train-MiX: Mixing Expert LLMs into a Mixture-of-Experts LLM

这篇论文是meta在24年3月发表的&#xff0c;它提出的BTX结构融合了BTM和MoE的优点&#xff0c;既能保证各专家模型训练时的高度并行&#xff0c;又是一个统一的单个模型&#xff0c;可以进一步微调。 这篇论文研究了以高效方法训练LLM使其获得各领域专家的能力&#xff0c;例如…