【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)

大家都在看

  • 学生管理系统(初级)

    #include #include #include #define Size sizeof(struct Student) struct Student *p = NULL; t…

    Linux 2023年6月7日
    0106
  • c++仿照go语言的error,函数返回值封装

    c++仿照go语言,程序返回错误时,可以附加错误信息 #ifndef __ERRORMSG_H_ #define __ERRORMSG_H_ #include struct Err…

    Linux 2023年6月14日
    092
  • 011 Linux 打包与解压 tar

    01 压缩、打包命令有哪些? Linux上有着各种压缩、打包的工具:tar、gzip、zip、7z,而 tar 应该算是 Linux 官宣的压缩工具了。tar 的核心压缩工具其实是…

    Linux 2023年5月27日
    084
  • Centos部署Loki日志聚合系统

    有关一些日志聚合的起源和原则,请参阅我的另一篇文章“用于编程入门的日志聚合系统”。 [En] For the origin and principles of some log a…

    Linux 2023年5月27日
    076
  • windows 使用挂载盘提供FTP服务

    环境说明: NFS服务器:centos7.0 IP:192.168.18.10 FTP服务器:windows server 2008 IP:192.168.18.41 准备工作: …

    Linux 2023年6月8日
    0171
  • 【socket】基于poll和epoll通信温度上报

    网络socket通信 * – poll函数 – epoll函数 – poll代码实现 – epoll代码实现 poll函数 poll…

    Linux 2023年6月13日
    0105
  • JCL 日志门面

    JCL( Jakarta Commons Logging ),是 Apache 提供的一个 通用日志 API 。用户可以自由选择第三方的日志组件作为具体实现,像 Log4j 或 J…

    Linux 2023年6月8日
    088
  • Weblogic页面应用查询oracle数据库后台报错或页面日期格式显示错误

    问题:在生产环境中有两台WEB服务器,分别为227和228,部署的应用代码都是每日同步的,两边完全一致,但是某些页面查询数据时,227无结果,并且后台报java数组越界的错误,而2…

    Linux 2023年6月14日
    096
  • 在使用amoeba连接数据库时,报错java.lang.Exception: poolName=slaves, no valid pools

    搭建3台MySQL服务器,完成主从复制,搭建一台amoeba服务器,完成MySQL的读写分离 问题描述: 问题1、 在服务搭建完毕后,利用客户机连接amoeba服务器登录数据库,无…

    Linux 2023年6月13日
    088
  • LeetCode-补充题9. 36进制加法

    题目来源 题目详情 36进制由0-9,a-z,共36个字符表示。 要求按照加法规则计算出任意两个36进制正整数的和,如1b + 2x = 48 (解释:47+105=152) 要求…

    Linux 2023年6月7日
    0105
  • 设计模式——创建型设计模式

    创建型设计模式 争对 &#x5BF9;&#x8C61;/&#x7C7B;创建时的优化 工厂方法模式(了解) 通过定义顶层抽象工厂类,通过继承的方式,针对于每…

    Linux 2023年6月7日
    0100
  • 单片机 MCU 固件打包脚本软件

    ​ 1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名…

    Linux 2023年6月7日
    0105
  • Netty源码解读(三)-NioEventLoop

    先看看EventLoop类图 我们在Netty第二篇文章中的代码中,看到有多次用到eventLoop.execute()方法,这个方法就是EventLoop开启线程执行任务的关键,…

    Linux 2023年6月7日
    097
  • 聊聊客户档案模型的设计与管理

    可以简单,更需要复杂; 一、基础描述 围绕客户管理通常分为售前、售中、售后、三个核心阶段,即营销、销售、服务三个核心流程与策略,在之前的文章中有聊过CDP系统的设计,本篇从客户档案…

    Linux 2023年6月14日
    089
  • docker安装详细过程

    ubuntu安装docker 我这里用mobaxterm远程连接安装的,如何远程连接在上一篇文章 1、解压tar -zxvf docker-19.03.5.tgz 拷贝文件至bin…

    Linux 2023年6月7日
    0117
  • macbook air 2019 安装win10单系统

    目前不考虑写的太详细了,如果有同学遇到问题了我再完善,主要是把遇到的坑讲下第一步,准备2个U盘(如果不嫌麻烦一个也可以)1.用大白菜或者老毛桃将其中一个做成启动盘2.在window…

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