首页 > 系统相关 >Linux进程间的通信方式(二)System V 共享内存

Linux进程间的通信方式(二)System V 共享内存

时间:2024-07-04 09:01:36浏览次数:14  
标签:映射 int System 内存 Linux msg 共享内存 shm

文章目录


前言

本文主要探讨 linux 下进程间的通信方式之共享内存,最后通过一个简单的示例基于共享内存实现一个父子进程之间数据的读写功能。

1. 共享内存的概念

1.1 什么是共享内存

所谓共享内存就是两个或多个进程地址通过页表映射到同一片物理内存区域,各个进程间可以往这块共同内存区域读写数据以达到进程间的相互通信。因为都是直接在内存中读写数据,避免了数据在进程之间拷贝,因此通信速度非常快。

1.2 linux的内存管理机制

说起共享内存不得不提一下 linux 的内存管理机制了。linux 内存管理是指对系统内存的分配、释放、映射、回收等一系列操作的管理, 今天主要来讲一下内存映射。

1.3 内存映射

所谓的内存映射其实就是将进程的虚拟逻辑内存地址映射到真实的物理内存地址, 其中涉及到了地址转换(将逻辑地址转换为真实的物理地址)

在linux操作系统中所有的进程都共享同一块物理内存,逻辑上各自都拥有一块连续的虚拟内存。当一个进程要访问某个数据的时候会给出该数据的逻辑地址,系统通过地址映射算法将逻辑地址转化成物理地址,再访问物理内存单元中存储的数据。

为了方便逻辑地址到物理地址的映射,大多数操作系统都采用了分页式的内存管理机制,通过页的映射来实现逻辑地址到物理地址的转换。
linux内存管理概念图
在页式内存管理机制中,页是内存管理的基本单元,linux系统中一页的默认大小通常为4KB。物理内存也以4KB作为基本单元进行存储,称之为页框。操作系统为每一个进程维护一张表,这张表记录了每一个页到页框的映射关系,这张表称之为页表。当一个进程需要访问某个数据的时候就会通过这张表间接地访问到存储在物理内存的数据了。

2. 共享内存的接口分类

linux下的共享内存接口主要分System V、POSIX和共享文件映射(mmap)三类。

对比项System VPOSIX共享文件映射(mmap)
初始化方式需要使用特定的IPC键不需要特定的IPC键基于文件映射
内存管理更底层的控制更现代化的接口依赖于文件系统
性能较高较高中等(受文件系统影响)
移植性可能存在兼容性问题更好的可移植性较好的可移植性
API复杂度较高中等较低
同步机制需要额外实现需要额外实现依赖于文件系统的同步
大小限制可能受系统限制更灵活的大小配置受文件大小限制
适用场景适用于需要复杂控制的共享内存简单共享内存和进程间通信大文件共享和内存映射

本文先来探讨一下System V共享内存。

3. 共享内存的相关操作函数

3.1 ftok函数(获取一个key值)

函数原型key_t ftok(const char *pathname, int proj_id);
功能基于文件路径和proj_id子序号生成一个键值
参数
pathname: 文件路径
proj_id: 子序号
返回值成功返回一个键值,失败返回-1并设置errno指明错误的原因

3.2 shmget函数(创建或获取一个共享内存描述符)

函数原型int shmget(key_t key, size_t size, int shmflg);
功能创建或者获取一个System V共享内存段并返回一个操作描述符
参数
key: 键值,唯一标识一个消息队列,可取由ftok创建的key值或指定的一个非负整数值
size_t: 申请共享内存的大小,一般为页(4k)的整数倍
shmflg: 这是一组标志,用于控制 shmget 函数的行为
返回值成功返回一个共享内存操作符,失败返回-1并设置errno指明错误的原因

关于msgflg我已经在上一章介绍消息队列的时候就详细讲过了,这里就不再讲了,可以参考一下msgget函数介绍那段。

传送门:Linux进程间的通信方式(一)System V 消息队列

3.3 shmat函数(映射共享内存地址空间)

函数原型void *shmat(int shmid, const void *shmaddr, int shmflg);
功能将当前进程的地址空间中的一段地址映射到到共享内存段上
参数
shmid: 由shmget函数返回的共享内存标识符
shmaddr: 指定共享内存映射的地址,一般写NULL表示让系统自己来选
shmflg: 一组标志位,用于设置该段共享内存的属性,一般写0表示该段共享内存可读可写
返回值成功则返回一个共享内存段映射地址(void*类型),
失败返回(void *) -1类型指针并设置errno指明错误原因

3.4 shmdt函数(解除共享内存映射)

函数原型int shmdt(const void *shmaddr);
功能将共享内存从当前进程中分离出来
参数shmaddr: shmat函数返回的共享内存地址映射地址
返回值成功返回0,失败返回-1并设置errno指明错误的原因

3.5 shmctl函数(共享内存控制)

函数原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能控制共享内存段
参数
shmid: 由shmget函数返回的共享内存标识符
cmd: 控制命令,用于执行指定的操作,比如删除共享内存段、修改权限等
buf: 共享内存管理结构体
返回值成功返回0,失败返回-1并设置errno指明错误的原因

关于cmd控制指令常见有以下几种:

控制指令描述
IPC_STAT获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID删除共享内存段

关于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;   /* Creation time/time of last
                                               modification via shmctl() */
               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 */
               ...
};

关于ipc_perm结构体的定义以及具体含义如下:

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 and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
};

4. 共享内存应用示例

4.1 System V 共享内存应用示例代码

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

#define MSG_SIZE 1024
#define SHM_PAGE_SIZE 1024*4
#define SHM_PATH_NAME "/"
#define SHM_PROJ_ID 99

typedef struct {
    int msg_type;
    char msg[MSG_SIZE];
}MsgFrame;

/* 申请或获取一个共享内存并返回操作标识符 */
int shm_init(const char *path, int proj_id, int shm_pagesize) 
{
    key_t keyval = ftok(path, proj_id);

    int shm_id = shmget(keyval, shm_pagesize, IPC_CREAT | IPC_EXCL | 0666);
    if (shm_id < 0) {
        if (errno == EEXIST) {
            shm_id = shmget(keyval, 0, 0);
            printf("the shared memory segment whose key is %d and shmid is %d is already exist!\n", keyval, shm_id);
            return shm_id;
        } else
            perror("shmget");
    }

    return shm_id;
}

int main(int argc, char *argv[])
{
    int ret, shm_id;
    char msgbuf[MSG_SIZE];

    /*创建或者获取一个共享内存标识符*/
    shm_id = shm_init(SHM_PATH_NAME, SHM_PROJ_ID, SHM_PAGE_SIZE);
    if (shm_id < 0) {
        printf("get share memory fail !\n");
        return -1;
    }

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return -1;
    } else if (pid == 0) { /* child */
        printf("I am child,pid:%d,ppid:%d\n", getpid(), getppid());

         /*将当前进程地址空间映射到共享内存中*/
        MsgFrame *msg_ptr = (MsgFrame *)shmat(shm_id, NULL, 0);
        if (msg_ptr == (MsgFrame *)(-1)) {
            perror("shmat");
            return -1;
        }

        /*从共享内存中读取数据*/
        for (int i = 0; i < 30; i++) {
            printf("read,msg_type:%d,msg:%s\n", msg_ptr->msg_type, msg_ptr->msg);
            sleep(1);
        }

        exit(0);

    } else if (pid > 0) {
        printf("I am father,pid:%d,ppid:%d\n", getpid(), getppid());

        /*将当前进程地址空间映射到共享内存中*/
        MsgFrame *msg_ptr = (MsgFrame *)shmat(shm_id, NULL, 0);
        if (msg_ptr == (MsgFrame *)(-1)) {
            perror("shmat");
            return -1;
        }

        /*写入数据到共享内存去*/
        for (int i = 0; i < 25; i++) {
            msg_ptr->msg_type = i;
            memset(msgbuf, 0, sizeof(msgbuf));
            sprintf(msgbuf, "number:%d hello world !", i);
            memcpy(msg_ptr->msg, msgbuf, strlen(msgbuf));
            printf("write,msg_type:%d,msg:%s\n", msg_ptr->msg_type, msg_ptr->msg);
            sleep(1);
        }

        /*阻塞等待子进程结束*/
        ret = wait(NULL);

        if (ret == -1) {
            perror("wait");
            return -1;
        }

        /*将共享内存从当前进程分离出来*/
        if (shmdt(msg_ptr) == -1)
            perror("shmdt");

        /*删除共享内存段*/
        if (shmctl(shm_id, IPC_RMID, NULL) == -1)
            perror("shmctl");
    }
    
    return 0;
}

4.2 应用示例讲解

程序首先通过 shm_get 函数获取到一个共享内存的操作符,接着使用 fork 函数创建出子进程之后父子进程再调用 shmat 函数各自将各自进程的内存地址空间映射到共享内存中去,然后再分别向共享内存中读写数据以达到父子进程间的通信。最后父子进程各自完成数据读写操作之后调用 shmdt 函数解除共享内存映射并在父进程中调用 shmctl 函数删除共享内存。

简而言之,该代码实现了一个基于共享内存实现父子进程之间数据的读写功能。

4.3 共享内存的不足之处

共享内存允许多个进程直接访问同一块内存区域,避免了数据在进程之间拷贝,因此通信速度非常快。由于不需要频繁的数据拷贝和上下文切换,共享内存可以支持高带宽的数据传输,对于需要传输大块数据的应用(如多媒体处理、大文件传输等),共享内存是一种非常适合的方式。

虽然共享内存有这么多的优点,但是它也有存在不足之处。共享内存本身并不提供同步机制, 当多个进程同时访问临界资源的时候就有可能会造成数据的读写冲突从而导致读取到不正确的数据。

那有些什么方法可以弥补这种不足呢?答案当然是有的,比如下一章节要讲到的信号量就可以解决这个问题。

总结

共享内存通信具有高效、适合大数据量传输的优点,但也存在同步复杂、安全性和管理方面的挑战。在选择使用共享内存时,需要综合考虑应用场景和需求,合理设计和实现同步机制,确保数据的一致性和安全性。

标签:映射,int,System,内存,Linux,msg,共享内存,shm
From: https://blog.csdn.net/Mr_Jaychong/article/details/139966647

相关文章

  • Linux下编译Azerothcore源码
    前言终于开始介绍Linux下如何编译AzerothCore源码了,本文编译和架设方法较为繁琐和细致,含Ubuntu、Debian和Docker(相较之前的Docker教程来说本文是手动版),且涉及到搭建注册网站和对外开放服务部分,故再次声明:本网站均为技术研究,若参考本网站教程搭建对外服务,我均不负任何责任!系统环......
  • Linux执行./configure时报错
    configure:error:noacceptableCcompilerfoundin$PATH上述错误是未安装合适的编译器所导致的报错。sudoyuminstallgcc-c++(使用sudoyuminstallgcc-c++时会自动安装/升级gcc及其他依赖的包。)安装、升级完毕后重新执行以下命令即可!./configuremakemake......
  • 【粉丝福利社】Linux私教课:技术内核与企业运维篇(文末送书-进行中)
    ......
  • 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式
    运行机制共享好端端的一词,近些年被玩坏了,共享单车,共享充电宝,共享办公室,共享雨伞…甚至还有共享女朋友,真是人有多大胆,共享有多大产。但凡事太尽就容易恶心到人,自己也一度被 共享内存 恶心到了,一直不想碰它,拖到了现在才写。共享内存的原理简单,目的是为了进程间通讯,方法......
  • Linux下rz/sz安装
    一、工具说明一般情况下,我们会使用终端软件,如 XShell、SecureCRT或FinalShell 来连接远程服务器后,使用 rz 命令上传本地文件到远程服务器,再解压发版上线。二、使用yum来安装#yuminstalllrzsz三、使用二进制来安装首先通过sftp工具把安装文件上传到tmp目录下.#......
  • Linux-gdb
    目录1.-g生成含有debug信息的可执行文件2.gdb开始以及gdb中的常用执行指令3.断点的本质用法4.快速跳出函数体5.其他1.-g生成含有debug信息的可执行文件2.gdb开始以及gdb中的常用执行指令3.断点的本质用法断点的本质是帮助我们缩小出问题的范围比如,......
  • linux wifiAP热点设置
    1、配置/etc/udhcpd.confstart192.168.1.20end192.168.1.50interfacewlan0max_leases30optionsubnet255.255.255.0optionrouter192.168.1.1optiondns8.8.8.8option lease 8logfile/var/log/udhcpd.logoptionlease86400 #指定......
  • Linux 操作系统详解
    前言Linux是一个强大且开源的操作系统,以其稳定性、灵活性和安全性广受欢迎。它在个人计算机、服务器、嵌入式系统以及超级计算机等多个领域得到广泛应用。本文将从多个角度深入探讨Linux的历史、架构和主要特性,为初学者和专业人士提供全面的理解和指南。Linux的历史起源......
  • 玄机——第三章 权限维持-linux权限维持-隐藏 wp
    文章目录一、前言二、概览简介三、参考文章四、步骤(解析)准备步骤#1.0步骤#1.1黑客隐藏的隐藏的文件完整路径md5步骤#1.2黑客隐藏的文件反弹shell的ip+端口{ip:port}步骤#1.3黑客提权所用的命令完整路径的md5flag{md5}拓展1.1拓展1.2步骤#1.4黑客尝试注入恶意代......