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

大家都在看

  • Ubuntu18.04安装/卸载NVIDIA显卡驱动

    1 显卡驱动下载 官网:NVIDIA 搜索适合本机的驱动 获取最新版本驱动 立即下载 文件 上面,显卡驱动程序下载已完成。 [En] Above, the video card d…

    Linux 2023年5月27日
    0221
  • 用动态端口,增强winrm,open sshd的,服务器安全

    前言 我开发了一套开源,免费,跨平台的devops脚本批量运维工具。【kaiiit家的饭店】是软件的正式名字。【卡死你3000】是第一版开发代号。 想要增强win被控机密码安全。可…

    Linux 2023年6月14日
    075
  • Linux常用扩展

    目录 ~ ? * [] {} 1. ~ 代表当前用户的home目录 pwd ~$ /home/user/ ls ~$ a touch ~/b ls ~$ a b ~ 等于/home…

    Linux 2023年6月7日
    072
  • 机器学习1

    常见的几种假设检验的实例以及对应python代码实现(包括基于图的效果展示 Z检验 t检验 χ2检验 F检验 熟悉scikit-learn及其相关应用 Numpy Numpy 优势…

    Linux 2023年6月6日
    087
  • SSH_远程终端

    SSH 远程服务 目的 Windwos 和 Linux 的终端控制系统or传送传送文件, 当然 Linux和Linux 以及 Windwos 和 Windwos 之间的通信都是OK…

    Linux 2023年6月7日
    083
  • FusionAccess桌面云安装(windows AD方法)

    创建FusionAccess虚拟机 选择自定义 默认兼容 选择稍后安装操作系统 选择Linux SUSE Linux 名字位置自己选择 选择最少4个处理器 选择最少8G内存 选择仅…

    Linux 2023年6月8日
    0103
  • MySQL — 数据控制语言

    DCL 全称 Data Control Language。数据控制语言,用来创建数据库用户、控制数据库的访问权限。 1、用户管理 select * from user; 只能在指定…

    Linux 2023年6月8日
    090
  • Linux 配置Git

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i 一、用git –version命令检查是否已经安装 二、下载git源码并解压 wget https:/…

    Linux 2023年6月14日
    086
  • redis 突然大量逐出导致读写请求block

    redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请求,导致 redis 短时间不可用; redis 中的LRU是如何实现的? 逐出qps…

    Linux 2023年5月28日
    080
  • python小技巧

    关于 ipython 1 Tab补全 从外观上,IPython shell和标准的Python解释器只是看起来不同。IPython shell的 进步之一是具备其它IDE和交互计算…

    Linux 2023年6月8日
    091
  • 自动化集成:Pipeline整合Docker+K8S

    前言:该系列文章,围绕持续集成:Jenkins+Docker+K8S相关组件,实现自动化管理源码编译、打包、镜像构建、部署等操作; 本篇文章主要描述流水线集成K8S用法。 一、背景…

    Linux 2023年5月27日
    0162
  • Kubernetes 容器平台实战

    一、什么是Kubernetes? Kubernetes是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署,自动扩缩容,维护等功能. 通过Kubernetes可以做到…

    Linux 2023年6月14日
    095
  • 防止shell script多次运行

    防止shell script多次运行 一个思路是在script初期检测系统中是否存在同名进程。 if [ ps -ef | grep "test.sh" | g…

    Linux 2023年5月28日
    076
  • Linux命令篇-awk 命令

    gawk – pattern scanning and processing language; awk:gawk是Unix中原始awk程序的GNU版本,强大之处在于可…

    Linux 2023年6月13日
    087
  • SpringBoot入门-Redis(六)

    package com.vast; import com.vast.dao.AccountRepository; import com.vast.dao.IAccountMybat…

    Linux 2023年5月28日
    064
  • Redis (error) NOAUTH Authentication required.

    首先查看redis设置密码没 表示没有设置密码,设置redis密码 这个时候查看密码是会报错的。 需要noauth身份验证。 修改密码 Original: https://www….

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