Linux 练习七 (IPC 共享内存)

news2024/11/29 12:54:10

文章目录

  • System V 共享内存机制:shmget shmat shmdt shmctl
  • 案例一:有亲缘关系的进程通信
  • 案例二:非亲缘关系的进程通信
    • 内存写端write1.c
    • 内存读端read1.c
  • 案例三:不同程序之间的进程通信
    • 程序一,写者shmwr.c
    • 程序二,读者shmre.c

使用环境:Ubuntu18.04
使用工具:VMWare workstations ,xshell

  作者在学习Linux的过程中对常用的命令进行记录,通过思维导图的方式梳理知识点,并且通过xshell连接vmware中ubuntu虚拟机进行操作,并将练习的截图注解,每句话对应相应的命令,读者可以无障碍跟练。第七次练习的重点在于Linux的进程通信之共享内存。
  

System V 共享内存机制:shmget shmat shmdt shmctl

  • 内存共享的原理及实现:
    共享内存本质是一段特殊的内存区域,进程间需要共享的数据被存放在该共享内存区域中,所有需要访问该共享区域的进程都要把共享区域映射 到本进程的地址空间去,不是拷贝。这样一个使用共享内存的进程可以将信息写入空间,而另一个今后才能可以通过对映射地址进行读内存获取刚刚写入的信息,完成进程间通信
  • 共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享内存的页表条目所引用,同时并不需要在所有进程的虚拟内存中都有相同的地址。进程对象对于共享内存的访问通过key键值来控制,同时通过key键值来进行访问权限检查。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 函数ftok(const char *pathname, int proj_id);用于创建一个关键字,可以用此key关联一个共享内存段
  1. 参数pathname为一个全路径文件名,此文件必须可访问。
  2. 参数proj_jd通常传入一非0字符
  3. 通过pathname和proj_id组合可以创建唯一的key
  4. 如果调用成功,返回一个关键字key,否则返回-1
  • 函数shmget(key_t key, int size, int shmflg);用于创建或打开一共享内存段,该内存段由函数的第一个参数唯一创建。
  1. 创建成功返回唯一的共享内存标识号(类似于进程号),否则返回-1
  2. 参数key是一个共享内存关联的关键字,如果该key已经关联共享内存,则返回内存段标志,表示打开了此内存段。如果该key不存在,则创建一个新的共享内存段。key 的值既可以用 ftok 函数产生,也可以是 IPC_PRIVATE(用于创建一个只属于创建进程的共享内存,主要用于父子通信),表示总是创建新的共享内存段。
  3. 参数size指定共享内存段的大小,以字节为单位。
  4. 参数shmflg是掩码合成纸,可以是访问权限值与(IPC_CREAT 或 IPC_EXCL)的合成。IPC_CREAT 表示如果内存段不存在就创建。IPC_EXCL 表示如果该内存段存在,则函数返回失败结果(-1)。如果调用成功,返回内存段标识,否则返回-1
  • 函数*shmat(int shmid, const void *shmaddr, int shmflg);将共享内存段映射到进程空间的某一地址。
  1. 参数shmid是共享内存段的标识,通常应该是shmget的成功返回值,即共享内存标识号
  2. 参数shmaddr制定的是共享内存连接到当前进程汇总的地址位置。通常是是NULL,表示让系统来选择共享内存出现的地址。
  3. 参数shmflg是一组位标识,指定 shmget 函数的动作,比如传入 IPC_CREAT 表示要创建新的共享内存,通常为0。
  4. 如果函数调用成功,返回映射后的进程空间的首地址,否则返回(char *)-1。
  • 函数shmdt(const void *shmaddr);用于将共享内存段与进程空间分离。
  1. 参数shmaddr通常为shmat的成功返回值,即映射后的进程空间首址。
  2. 函数成功后返回0,失败后返回-1。
  3. 将共享内存分离并不是真的删除,只是使得该共享内存对当前进程不可再用。
  • 函数shmctl(int shmid, int cmd, struct shmid_ds *buf是共享内存的控制函数,可以用来删除共享内存段。
  1. 参数shmid是共享内存段标识,通常是shmget的成功返回值。
  2. 参数cmd是对共享内存段的操作方式,可选为可选为 IPC_STAT,IPC_SET,IPC_RMID。通常为 IPC_RMID,表示删除共享内存段。
  3. 参数buf是表示共享内存段的信息结构体数据,通常为NULL。
  4. 有进程连接,执行返回0,标记删除成功,但是直到最后一个进程结束连接后,共享内存真正被删除。
  5. 结构体shmid_ds
struct shmid_ds {
	struct ipc_perm shm_perm; /* Ownership and permissions */
	size_t shm_segsz; /* Size of segment (bytes) */
	time_t shm_atime; /* Last attach time */
	time_t shm_dtime; /* Last detach time */
	time_t shm_ctime; /* Last change time */
	pid_t shm_cpid; /* PID of creator */
	pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
	shmatt_t shm_nattch; /* No. of current attaches */
	... 
};
struct ipc_perm {
	key_t __key; /* Key supplied to shmget(2) */
	uid_t uid; /* Effective UID of owner */
	gid_t gid; /* Effective GID of owner */
	uid_t cuid; /* Effective UID of creator */
	gid_t cgid; /* Effective GID of creator */
	unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */
	unsigned short __seq; /* Sequence number */
};

案例一:有亲缘关系的进程通信

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<sys/wait.h>
#define PERM S_IRUSR | S_IWUSR //表示用户可读可写 即 0600

int main(int argc,char** argv)
{
	int shmid = shmget(IPC_PRIVATE,1024,PERM);//只有IPC_PRIVATE情况可以不设置IPC_CREAT,让操作系统来开辟空间
	if(shmid == -1) {//如果返回的共享内存标识号不为-1,即创建共享内存失败,错误处理
		fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno));
		exit(1);
	}	
	if(fork() > 0){	//父进程代码
		char *p_addr = (char*)shmat(shmid,NULL,0);	//将共享内存段地址映射到父进程的进程空间中
		memset(p_addr,'\0',1024);	//设置这段地址空间初始化为0
		strncpy(p_addr,"share memory", 1024);//将字符串写入内存
		printf("父进程id:%d,写入缓冲区:%s\n",getpid(),p_addr);
		sleep(2);
		wait(NULL);	//处理结束的进程,防止僵尸进程
		shmctl(shmid,IPC_RMID,0);//通过唯一的共享内存标识号,删除共享内存
		exit(0);
	}
	else{	//子进程代码
		sleep(5);	//给父进程留足写数据的时间
		char* c_addr = (char*)shmat(shmid,NULL,0);	//将共享内存段地址映射到子进程的进程空间中,可以读取其中内容
		printf("子进程id:%d,进程标识号:%d 读缓冲区内容: %s\n",getpid(),shmid,c_addr);
		exit(0);
	}
	return 0;
}

运行结果:
在这里插入图片描述

案例二:非亲缘关系的进程通信

内存写端write1.c

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<sys/wait.h>

int main()
{
	key_t key = ftok("./file1",1);			//1 写端使用ftok函数获取此文件的唯一关键字
	if(key == -1){	//获取失败的处理
		perror("fotk");
		exit(-1);
	}
	int shmid = shmget(key,512,IPC_CREAT|0666);	//2 按照key创建512B大小的共享内存段,返回该共享内存段的标识符
	if(shmid == -1){	//创建失败的处理
		perror("shmget");
		exit(-1);
	}
	char *pMap = (char *)shmat(shmid,NULL,0);	//3 获得共享内存段的首地址
	memset(pMap,'\0',512);
	strcpy(pMap,"hello world");		//4 想共享内存段中写入内容
	if(shmdt(pMap) == -1){			//5 关闭共享内存段
		perror("shmdt");
		exit(-1);
	}	
	return 0;
}

内存读端read1.c

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
	key_t key = ftok("./file1",1);	//1 读端使用ftok函数获取此文件的唯一关键字
	if(key == -1){			//获取失败的处理
		perror("fotk");
		exit(-1);
	}
	int shmid = shmget(key,512,0666|IPC_CREAT);	//2 按照key创建4096大小的共享内存段,权限设可读,返回该共享内存段的标识符
	if(shmid == -1){	//创建失败的处理
		perror("shmid");
		exit(-1);
	}
	char* pMap = shmat(shmid,NULL,0);	//3 获取共享内存段的首地址
	printf("读到的内容:%s\n",pMap);		//4 读取共享内存段的内容
	if(shmctl(shmid,IPC_RMID,0) == -1){	//5 删除共享内存段,注意和shmdt作区分	
		perror("shmctl");
		exit(-1);
	}
	return 0;
}

**注意:**如果运行时出错,再运行会出现“错误的参数”、“段错误”等,需要检查共享内存段是否关闭了,可以按如下操作,有可能会出现程序创建了共享内存段,然后没删除的情况,导致想再次运行报错。
在这里插入图片描述
再次运行调试就ok了
在这里插入图片描述

案例三:不同程序之间的进程通信

程序一,写者shmwr.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<sys/wait.h>
struct text
{
	int useful;	//是否可用的标志
	char buf[1024];
};

int main()
{
	int shmid = shmget((key_t)5080,sizeof(struct text),0600|IPC_CREAT);//创建唯一key,大小为text的共享内存段,返回唯一内存标识号
	if(shmid == -1){	//创建失败的处理
		perror("shmget");
		exit(-1);
	}
	struct text* ptext = (struct text*)shmat(shmid,NULL,0);//获得shmid共享内存段的首地址
	ptext->useful = 0;
	while(1){
		if(ptext->useful == 0){	//判断此内存段是否被用
			int iret = read(STDIN_FILENO,ptext->buf,1024);	//从标准输入到buf缓冲中,如果read函数不输入会阻塞
			ptext->useful = 1;	//将缓冲区改为占用状态
			if(strncmp("end",ptext->buf,3) == 0){	//如果输入的end,则结束
				break;
			}
			ptext->useful = 0; //将缓冲区改为未占用状态,新一次传输
		}
		sleep(1);
	}
	shmdt((void*)ptext);	//将此进程和共享内存段分离
	return 0;
}

程序二,读者shmre.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdlib.h>
#include<sys/wait.h>
struct text
{
	int useful;	//是否可用的标志
	char buf[1024];
};

int main()
{
	int shmid = shmget((key_t)5080,sizeof(struct text),0600|IPC_CREAT);//创建唯一key,大小为text的共享内存段,返回唯一内存标识号
	if(shmid == -1){	//创建失败的处理
		perror("shmget");
		exit(-1);
	}
	struct text* ptext = (struct text*)shmat(shmid,NULL,0);//获得shmid共享内存段的首地址
	ptext->useful = 0;
	while(1){
		if(ptext->useful == 1){
			write(STDOUT_FILENO,ptext->buf,strlen(ptext->buf));//将缓冲区中的内容打印到标准输出窗口中,如果没有内容write会阻塞
			ptext->useful = 0;
			if(strncmp("end",ptext->buf,3) == 0){	//输入end退出循环
				break;
			}
		}
		sleep(1);
	}
	shmdt((void*)ptext);		//将此进程和共享内存段分离
	shmctl(shmid,IPC_RMID,0);	//清除该进程空间
	return 0;
}

演示结果,读者结合代码自行体会,end覆盖了内存空间的起始字符,如何修改可以不覆盖呢?
在这里插入图片描述

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

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

相关文章

2022-06-14至2022-08-11 关于复现MKP算法的总结与反思

Prerequisite 自2022年6月14日至2022年8月11日的时间内&#xff0c;我致力于完成A Hybrid Approach for the 0–1 Multidimensional Knapsack problem 论文的复现工作&#xff0c;此次是我第一次进行组合优化方向的学习工作&#xff0c;下面介绍该工作内容发展过程以及该工作结…

JavaScript Array 数组对象实例集合

文章目录JavaScript Array 数组对象实例集合创建数组合并两个数组 - concat()合并三个数组 - concat()用数组的元素组成字符串 - join()删除数组的最后一个元素 - pop()数组的末尾添加新的元素 - push()反转一个数组中的元素的顺序 - reverse()删除数组的第一个元素 - shift()从…

数字化时代,企业的商业模式建设

随着新一代信息化、数字化技术的应用&#xff0c;众多领域通过科技革命和产业革命实现了深度化的数字改造&#xff0c;进入到以数据为核心驱动力的&#xff0c;全新的数据处理时代&#xff0c;并通过业务系统、商业智能BI等数字化技术和应用实现了数据价值&#xff0c;从数字经…

Vue项目打包部署总结配合nginx部署

你可能还想了解&#xff1a;https://blog.csdn.net/weixin_52901235/article/details/129437990?spm1001.2014.3001.5502使用Vue做前后端分离项目时&#xff0c;通常前端是单独部署&#xff0c;用户访问的也是前端项目地址&#xff0c;因此前端开发人员很有必要熟悉一下项目部…

C#要点技术(二) - Dictionary 底层源码剖析

Dictionary 底层代码我们知道 Dictionary 字典型数据结构&#xff0c;是以关键字Key 和 值Value 进行一一映射的。Key的类型并没有做任何的限制&#xff0c;可以是整数&#xff0c;也可以是的字符串&#xff0c;甚至可以是实例对象。关键字Key是如何映射到内存的呢&#xff1f;…

【python】如何用python写一个下拉选择框和页签?

文章目录前言ttk模块下拉选择框combobox下拉选择框2页签Notebook前言 python学习之路任重而道远&#xff0c;要想学完说容易也容易&#xff0c;说难也难。 很多人说python最好学了&#xff0c;但扪心自问&#xff0c;你会用python做什么了&#xff1f; 刚开始在大学学习c语言&…

【玩转c++】stack和queue的介绍和模拟实现

本期主题&#xff1a;list的讲解和模拟实现博客主页&#xff1a; 小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐stack的介绍和使用1.1.stack的介绍1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上…

论文阅读-MGTAB: A Multi-Relational Graph-Based Twitter Account DetectionBenchmark

目录 摘要 1. 引言 2. 相关工作 2.1. 立场检测 2.2.机器人检测 3.数据集预处理 3.1.数据收集和清理 3.2.专家注释 3.3. 质量评估 3.4.特征分析 4. 数据集构建 4.1.特征表示构造 4.2.关系图构建 5. 实验 5.1.实验设置 5.2.基准性能 5.3训练集大小的研究 5.4 社…

Matlab进阶绘图第6期—雷达图/蜘蛛图/星图

雷达图&#xff08;Radar Chart&#xff09;&#xff0c;又称星图、蜘蛛图、蜘蛛网图、网络图、Kiviat图等&#xff0c;是一种以从同一点开始的轴上表示的三个以上变量的二维图表的形式&#xff0c;来显示多变量数据的图形方法。 雷达图可以直观地对多维数据集目标对象的性能、…

三步搞定OOM内存溢出,记一次使用Arthas处理OOM内存溢出问题java.lang.OutOfMemoryError: Java heap space

记一次OOM内存溢出问题修复java.lang.OutOfMemoryError: Java heap spaceOutOfMemoryError1.使用article找到问题线程2.分析线程运行链路&#xff0c;找出问题代码位置3.使用堆文件确认问题Arthas 是Alibaba开源的Java诊断工具&#xff0c;功能强大&#xff0c;操作简单 Arthas…

我们为什么使用docker 优点 作用

1. 我们为什么使用Docker? 当我们在工作中&#xff0c;一款产品从开发设计到上线运行&#xff0c;其中需要开发人员和运维工程师&#xff0c;开发人员负责代码编写&#xff0c;开发产品&#xff0c;运维工程师需要测试环境&#xff0c;产品部署。这之间就会有分歧。 就好比我…

信创国产化,试试 Solon v2.2.2

Solon 是一个高效的 Java 应用开发框架&#xff1a;更快、更小、更简单。它不是 Spring、没有用 Servlet、也无关 JavaEE&#xff0c;是一个有自己接口标准的开放生态。可以为应用软件国产化提供支持&#xff0c;助力信创建设。 150来个生态插件&#xff0c;覆盖各种不同的应用…

不知道Redis?来这里可以带你快速学完Redis,干活满满!

文章目录一、NoSQL的基本介绍二、为什么要使用NoSQL&#xff0c;难道SQL不够你用吗&#xff1f;三、Redis的基本概念四、Redis基本操作命令五、Redis五大数据类型及其操作命令六、三种特殊的数据类型及其操作命令七、 Redis事务八、Redis对key的监控九、Redis数据库密码十、Jed…

广州蓝景分享—8大Web前端开发的趋势

2023 年 1 月 11 日&#xff0c;2022 年度 StateOfJS 调查结果正式公布&#xff01;StateOfJS 是前端生态圈中比较有影响力的且规模较大的数据调查。本文就来解读一下 2022 年 StateOfJS 的调查结果&#xff01; JavaScript 发展很快&#xff0c;但似乎 JavaScript 开发人员的…

《传感器技术》考试学习笔记

文章目录一、选择题二、简答题1.什么是传感器&#xff1f;传感器的共性是哪些&#xff1f;2.差动变气隙式传感器电感传感器的灵敏度推导过程是什么&#xff08;推导公式&#xff09;&#xff1f;与单极性进行比较它们的优缺点是哪些&#xff1f;3.霍尔传感器如何进行微位移测量…

uniapp上实现左右关联滚动

先看效果&#xff1a; 代码&#xff1a; <template><view class"container"><!-- 左侧fixed导航区域 --><view class"left"><viewv-for"item in leftList":key"item.id"class"left_item":class…

JVM结构-类加载(类加载子系统,类加载的角色,类加载的过程,类加载器分类,双亲委派机制,类的主/被动使用)

JVM 结构-类加载2.1类加载子系统2.2类加载的角色2.3类加载的过程2.3.1加载2.3.2链接2.3.3初始化2.4类加载器分类2.4.1 引导类加载器2.4.2扩展类加载器2.4.3应用程序类加载器2.5双亲委派机制2.6类的主动/被动使用2.1类加载子系统 类加载器子系统负责从文件系统或者网络中加载 cl…

【深度强化学习】(1) DQN 模型解析,附Pytorch完整代码

大家好&#xff0c;今天和各位讲解一下深度强化学习中的基础模型 DQN&#xff0c;配合 OpenAI 的 gym 环境&#xff0c;训练模型完成一个小游戏&#xff0c;完整代码可以从我的 GitHub 中获得&#xff1a; https://github.com/LiSir-HIT/Reinforcement-Learning/tree/main/Mod…

腾讯云新用户怎么配置服务器的方法教程

腾讯云新用户怎么配置服务器&#xff1f;腾讯云服务器配置选择攻略&#xff0c;先选择云服务器地域和可用区&#xff0c;然后根据用户使用场景需要平衡型、计算型或高IO型等特性来选择云服务器CVM实例规格&#xff0c;主机教程网来详细说下腾讯云服务器配置选择攻略。 1、腾讯云…

政府工作报告连提9年科技创新 企业研发如何“又快又好”

今年的政府工作报告&#xff0c; “科技创新” 这一描述连续出现7次&#xff0c;这也是自2015年开始&#xff0c; “科技创新” 这一概念在全国“两会”政府工作报告中连续九年被提到。政府工作报告指出&#xff0c;科技政策要聚焦自立自强&#xff0c;完善新型举国体制&#x…