【Linux进程间通信】共享内存的使用

背景

最近需要开发一个测试程序,接受Tester端的测试指令,执行一条条外设的测试用例,执行完成后将测试数据的结果上报,上报方式未定,考虑到耦合和配套问题,决定采用共享内存机制,设计共享内存块,分为接受指令和数据上报两部分,主程序运行后就会一直轮询共享内存去等待指令,获取指令后执行对应的测试用例,执行完成后将数据结果写入数据上报区,Tester端什么时间获取不需要关注,只做好自己的数据上报即可。

【Linux进程间通信】共享内存的使用

共享内存

共享内存为什么能作为进程间通信的机制?

共享内存是一种进程间的通信方式,它允许两个不相关的进程访问同一逻辑内存。不同进程之间共享的内存通常为同一段物理内存,进程可以将同一段物理内存连接到他们自己的地址空间中,连接到这段共享内存的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

Linux环境中,每个进程都有属于自己的 进程控制块(PCB)和 虚拟地址空间(Addr Space),并且都有与之对应的页表,负责将进程的虚拟地址和物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,所指向的区域就是共享内存块。

共享内存块通信原理示意图:

【Linux进程间通信】共享内存的使用

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。一般情况下,要确保一个进程在写的时候不能被读,就需要使用信号量来实现同步与互斥,视实际需求而定。

由于共享内存在读写操作时是直接在内存上操作,所以相比于其他进程间通信的方式,共享内存的效率是最高的。

Linux下共享内存相关接口

共享内存的创建与打开:

进程可以通过调用函数shmget()来打开或创建一个共享内存区

int shmget(key_t key,size_t size,int shmflg);

key:为ipc键值,使用IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,但是如果向让其他进程访问这块共享内存区域,则需要创建共享内存块后,就记录下key值,以提供给其他进程访问。

size:申请的共享存储段的长度,一般为内存页(4k)大小的整数倍
Flag: 如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
返回值:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。

共享内存与进程的连接:

如果一个进程已创建或打开一个共享内存,需要调用函数shmat()把该共享内存连接到进程上,即把待使用的共享内存映射到进程空间,

void *shmat(int shmid,char __user *shmaddr,int shmflg);

Shmid : 共享内存标志(句柄)
shmaddr:shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用)
Shmflg: 若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段
返回值: 成功返回共享存储段的指针(虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1

断开共享内存与进程的连接:

调用函数shmdt可以断开共享内存和进程的连接

int shmdt(const void *addr);

addr:共享存储段的地址,调用shmat的返回值
返回值:shmdt将使相关shmid_ds结构中shm_nattch计数器值减1;出错返回-1。

共享内存的控制:

调用函数shmctl()可以对共享内存进行一些控制

int shmctl(int shmid,int cmd,struct shmid_ds * buf);

shmid:共享存储段的ID
cmd:控制命令,IPC_STAT(赋值)、IPC_SET(赋值)、IPC_RMID(删除)、SHM_LOCK(上锁)、SHM_UNLOCK(解锁)
buf:出错返回-1。
返回值:成功返回0,失败返回-1

共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电

共享内存常用shell指令

1.查询当前所有的ipc通信资源情况

ipcs

2.查询当前系统的共享内存资源情况

ipcs -m

3.查询当前系统的信号量资源情况

ipcs -s

4.删除系统中某个共享内存段

ipcrm -m [shmid]

5.删除系统中某个信号量

ipcrm -s [semid]

代码实现

ipc.c

#include"ipc.h"

static int CommShm(int size,int flags)
{
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int shmid = 0;
    if((shmid = shmget(key,size,flags)) < 0)
    {
        perror("shmget");
        return -2;
    }
    return shmid;
}

int DestroyShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,NULL) < 0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

int CreateShm(int size)
{
    return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm(int size)
{
    return CommShm(size,IPC_CREAT);
}

ipc.h

#ifndef _IPC_H__
#define _IPC_H__

#include<stdio.h>
#include<sys types.h>
#include<sys ipc.h>
#include<sys shm.h>
#include<unistd.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);

#endif
</unistd.h></sys></sys></sys></stdio.h>

client.c

#include "ipc.h"

int main()
{
    // &#x83B7;&#x53D6;&#x5171;&#x4EAB;&#x5185;&#x5B58;&#x5757;&#x7684;key&#x503C;
    int shmid = GetShm(4096);
    usleep(1*1000*1000);
    // &#x8FDE;&#x63A5;&#x5171;&#x4EAB;&#x5185;&#x5B58;&#x5757;
    char *addr = shmat(shmid,NULL,0);
    usleep(2*1000*1000);
    int i = 0;
    while(i < 26)
    {
        // &#x6301;&#x7EED;&#x5199;&#x5165;&#x5171;&#x4EAB;&#x5185;&#x5B58;&#x5757;&#x6570;&#x636E;
        addr[i] = 'A' + i;
        i++;
        addr[i] = 0;
        usleep(1*1000*1000);
    }
    shmdt(addr);
    usleep(2*1000*1000);
    return 0;
}

server.c

#include"ipc.h"

int main()
{
    // &#x521B;&#x5EFA;&#x5171;&#x4EAB;&#x5185;&#x5B58;&#x533A;&#x57DF;
    int shmid = CreateShm(4096);
    // &#x8FDE;&#x63A5;&#x5171;&#x4EAB;&#x5185;&#x5B58;
    char *addr = shmat(shmid,NULL,0);
    usleep(2*1000*1000);
    int i = 0;
    while(i++ < 26)
    {
        // &#x6301;&#x7EED;&#x8BFB;&#x53D6;&#x5171;&#x4EAB;&#x5185;&#x5B58;&#x5757;&#x4E2D;&#x7684;&#x6570;&#x636E;
        printf("client# %s\n",addr);
        usleep(1*1000*1000);
    }
    shmdt(addr);
    usleep(2*1000*1000);
    DestroyShm(shmid);
    return 0;
}

makefile

.PHONE:all
all:server client

client:client.c ipc.c
    gcc -o $@ $^
server:server.c ipc.c
    gcc -o $@ $^

.PHONE:clean
clean:
    rm -f client server

运行结果

在命令行中输入make all,编译生成server和client,先./server执行server,这时server会创建共享内存,再运行client,连接server创建好的共享内存块,并向内存块持续写入数据。

【Linux进程间通信】共享内存的使用

参考文章:
https://blog.csdn.net/sunxiaopengsun/article/details/79817688
https://cloud.tencent.com/developer/article/1551288
https://blog.csdn.net/yanghaoran321/article/details/7872722

Original: https://www.cnblogs.com/Wangzx000/p/16572790.html
Author: _Wangzx
Title: 【Linux进程间通信】共享内存的使用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/607521/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java秒杀系统三:web层

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月11日
    0102
  • python一键探测编码

    程序功能 按文件输出编码or按编码输出文件 源码 主要代码功能 1.实现文件遍历 2.chardet获取编码 3.传参,对符合编码条件的文件输出 4.打开文件夹选择对话框 程序功能…

    Linux 2023年6月7日
    0128
  • 初识MySQL数据库

    一 、引言 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住双十一期间全国的购票需求,你怎么写? 由于在同一时段抢票的人数太多,所以你的程序不可…

    Linux 2023年6月14日
    0122
  • rpm简单使用

    rpm描述:利用源码包编译成rpm时,会去指定安装好这个包的位置本质:解压,然后拷贝到相关的目录,然后执行脚本 查询所有已经安装过的包 查看安装位置 解压rpm 查看脚本 查看配置…

    Linux 2023年6月7日
    077
  • ztreejs树 metro风格 鼠标经过 显示用户自定义控件 新增,编辑,删除,向下,向上操作

    php;gutter:true; ztreejs树功能说明:自定义控件功能新增,编辑,删除,向下移动,向上移动已实现,只是前端,如果需要跟后台交互,封装对应的函数,在相应位置调用即…

    Linux 2023年6月7日
    087
  • Golang中通过go-redis操作Redis

    参考地址:https://github.com/go-redis/redis 定义上下文以及连接的相关信息 var ctx = context.Background() var r…

    Linux 2023年5月28日
    0106
  • Docker清理日志脚本

    Docker清理日志脚本 #!/bin/sh 此脚本为日常清理docker日志 docker 容器的路劲日志为 /var/lib/docker/containers/ 下-json…

    Linux 2023年6月8日
    091
  • 表中添加唯一字段报错解决方案

    添加唯一字段的迁移 应用向具有现有行的表添加唯一不可为空字段的”普通”迁移将引发错误,因为用于填充现有行的值仅生成一次,从而破坏了唯一约束。 因此,应采取以…

    Linux 2023年6月14日
    094
  • MSSQL中完整备份及完整还原的T-SQL实践

    | 0.37分钟 | 596.8字符 | 1、引言&背景 2、完整备份 3、完整还原 4、声明与参考资料 | SCscHero | 2022/5/27 AM12:47 | …

    Linux 2023年6月14日
    085
  • Linux ARM中断控制器注册(4)【转】

    本文以S5PV210芯片为参照,S5PV210的中断控制器采用了ARM VIC(Vectored Interrupt Controller,PL192 ,ARM PrimeCell…

    Linux 2023年6月8日
    090
  • 【凸优化】3 多面体,单纯形,半正定锥

    1 多面体 Polyhedra 定义:多面体为一系列的(有限个)线性等式和不等式的解集: [\mathcal{P}={x|a_j^T x \leq b_j, j=1,……

    Linux 2023年6月7日
    0108
  • JAVA中如何将以Date型的数据保存到数据库以Datetime型的字段中

    用Timestamp就行了 recordOuttime是Date类型 import java.sql.Timestamp; Record record = recordMapper…

    Linux 2023年6月8日
    083
  • USB_ModeSwitch for Android 7

    测试步骤: 2.运行命令 adb shell usbmodeswitch -W -v 12d1 -p 1f01 -M ‘555342431234567800000000…

    Linux 2023年6月7日
    079
  • python 正则匹配

    正则匹配 现公司要开发一个业务管理系统,要求注册环节的密码需要提示用户其安全等级,密码按如下规则进行计分,并根据不同的得分为密码进行安全等级划分;此外,密码的组成可以由字母,数字,…

    Linux 2023年6月8日
    0103
  • jmeter学习记录–05–Beanshell2

    学习beanshell时有不少的例子、遇到不少问题。在此记录下。 测试实例列表 A1:使用Beanshell请求作为测试请求 一个打包的Jar包,直接对其内的方法进行测试。 第一步…

    Linux 2023年5月28日
    0111
  • 【微服务】- 服务调用-OpenFeign

    服务调用 – OpenFeign 😄生命不息,写作不止🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆 一个有梦有戏的人 @怒放吧德德🌝分享学习心得…

    Linux 2023年6月6日
    0102
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球