Linux进程间通信一 System V 共享内存简介与示例

目录

1. System V共享内存简介

2. API介绍

2.0 key_t和标识符

2.1  创建system v共享内存

2.2 映射共享内存并使用

2.3 取消共享内存映射

2.4 控制共享内存

3. 实例

3.1 共享内存写示例

3.2 共享内存读示例

4. 运行

5. 注意事项

6. 参考资料


1. System V共享内存简介

System V IPC通常指的是以下三种:

  1. System V 消息队列(message queues)
  2. System V 信号量(semaphores)
  3. System V 共享内存(shared memory)

System V IPC最早是在上世纪70年代末由贝尔实验室开发出来,三种IPC在实现架构、使用方式上有很多相同之处,比如内核实现里共用了ipc_perm权限管理结构体,使用方式上都包括基本的xxxget,xxxctl等等。今天主要介绍下System V 共享内存的使用方式。共享内存作为IPC通信中最快的一种,之所以快是因为一旦创建成功,后续操作无需内核拷贝数据以及系统调用,通信双方可以直接操作共享内存来通信。因为开发出来比较早,所以存在一些缺陷,所以就有了Posix IPC的产生,不过这是后话,在此不提。首先看下共享内存的接口。

2. API介绍

2.0 key_t和标识符

System V IPC对象通过标识符来区分,该标志符具有系统唯一性,是操作系统内的全局变量。生成SystemV IPC对象的Get函数需要一个key_t参数,这是一个整型变量,用于对应一个标识符。该标识符通常有三种方法生成:

  1. 使用随机固定值
  2. 使用IPC_PRIVATE,这表示每次生成IPC对象的时候都会创建一个新的标识符,因此不相干的进程之间无法知道标识符ID,因此通常只用于父子进程,子进程继承父进程的IPC标识符,因而可以通信。
  3. 使用ftok函数生成key_t,该函数组合路径的属性和传入的proj_id拼出一个key_t,实际使用的时候路径通常都是可执行文件路径。
#include <sys/types.h>
#include <sys/ipc.h>


/**
* @brief 根据路径属性和proj_id创建key_t
*
* @params pathname 路径名,文件必须存在且可访问
* @params proj_id 自定义ID,只使用低8位(不能为0)
*
* @returns 成功返回key_t,失败返回-1
*/

key_t ftok(const char *pathname, int proj_id);

2.1  创建system v共享内存

可以使用ipcs | ipcrm | ipcmk 查看、删除和创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

/**
* @brief 创建共享内存ID
*
* @params key 与shm_id关联的key,三种生成方式,包括IPC_PRIVATE,注意key和shm_id不是绑定关系,相同的key产生的标识符不一定相同。
* @params size 共享内存的大小,必须是正整数。
* @params shmflg 标志位和权限控制标志位,可以多个用or运算。IPC_CREAT、 IPC_EXCL
*
* @returns 成功返回shmid,失败返回-1
*/

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

根据标志位的不同,有如下几种情况:

oflag参数key不存在key已经存在
无特殊标志位返回-1返回标识符
IPC_CREAT返回新建标识符返回已有标识符
IPC_CREAT|IPC_EXCL返回新建标识符返回-1

2.2 映射共享内存并使用

使用共享内存前需要将其映射到经常自身地址空间,之后就可以像malloc分配的内存一样使用了

#include <sys/types.h>
#include <sys/shm.h>

/**
* @brief 映射共享内存到进程指定地址
*
* @params shmid 共享内存标识符
* @params shmaddr 映射的地址,为NULL则内核自动选择合适的地址,非空并且设置SHM_RND标志位,则自动对齐,如果没有此标志位并且不对齐,报错。
* @params shmflg 可以设置SHM_RND、SHM_REMAP,SHM_RDONLY
* 如果映射的地址已经有了映射,可以设置SHM_REMAP标志位重新映射,否则报错。
* 如果地址不对齐,可以设置SHM_RND标志位,此时内核自动找到符合符合要求的地址,否则报错。
*
* @returns 成功返回映射地址,可以像malloc返回的地址那样操作,失败返回 (void *) -1 
*/

void *shmat(int shmid, const void *shmaddr,int shmflg);

2.3 取消共享内存映射

#include <sys/types.h>
#include <sys/shm.h>

/**
* @brief 取消共享内存映射
*
* @params shmaddr shmat返回的共享内存地址
*
* 只是减少共享内存的引用计数并未实际删除共享内存
*
* @returns 成功返回0,失败返回 -1 
*/

int shmdt(const void *shmaddr);

2.4 控制共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

struct shmid_ds {
    struct ipc_perm shm_perm;    /* 所有权及权限 */
    size_t          shm_segsz;   /* 共享内存大小 (bytes) */
    time_t          shm_atime;   /* 上次shmat时间 */
    time_t          shm_dtime;   /* 上次shmdt时间 */
    time_t          shm_ctime;   /* 上次修改时间 */
    pid_t           shm_cpid;    /* 创建进程皮带 */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* 当前shmat进程数量 */
    ...
};

/**
* @brief 取消共享内存映射
*
* @params shmid 共享内存标识符
* @params cmd 控制命令,IPC_STAT, IPC_SET, IPC_RMID, SHM_LOCK, SHM_UNLOCK
* @params buf 缓存
* IPC_STAT 获取对应共享内存信息,存到buf缓存里。
* IPC_SET 只能修改shm_perm里面的uid,gid及mode
* IPC_RMID 删除共享内存,只有引用计数为0才真正删除
* SHM_LOCK 阻止共享内存被替换出去
* SHM_UNLOCK 和SHM_LOCK相反
*
* @returns 成功返回0,失败返回 -1
*/

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

3. 实例

3.1 共享内存写示例

/*
**  Name: shm_write.c
**  Desc: 共享内存写端
**  Author: masonf
**  Date: 2020-06-02
*/


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

// 消息结构体
typedef struct{
    char content[128];
} msg;

main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    msg *pMsg;

    if (argc < 2)
    {
        printf("invalid argument\n");
        return ;
    }

    // key简单写死
    key = 9999;

    // 创建共享内存
    shm_id=shmget(key,4096,IPC_CREAT);
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }

    // 映射共享内存到进程地址空间
    pMsg=(msg*)shmat(shm_id, NULL, 0);
    if (pMsg == (void *)-1)
    {
        perror("shmat error");
        return ;
    }

    // 写数据到共享内存
    memcpy(pMsg->content, argv[1], strlen(argv[1]));

    // 取消共享内存映射,并没有删除共享内存
    if(shmdt(pMsg) == -1)
    {
        perror(" detach error ");
    }

    return ;
}

3.2 共享内存读示例

/*
**  Name: shm_read.c
**  Desc: 共享内存读端
**  Author: masonf
**  Date: 2020-06-02
*/

#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{
    char content[128];
} msg;

void main(int argc, char** argv)
{
    int shm_id,i;
    key_t key;
    msg *pMsg = NULL;

    // 使用固定值
    key = 9999;

    // 存在则直接返回共享内存
    shm_id = shmget(key, 4096, IPC_CREAT);    
    if(shm_id == -1)
    {
        perror("shmget error");
        return;
    }

    // 映射共享内存到进程自身地址空间
    pMsg = (msg*)shmat(shm_id,NULL,0);
    if (pMsg == NULL)
    {
        printf("shmat error, errno:%d", errno);
        return ;
    }

    printf("Get Msg from shm_id:%s\n", pMsg->content);

    // 取消共享内存映射
    if(shmdt(pMsg) == -1)
    {
        perror(" detach error ");
    }

    return;
}

4. 运行

5. 注意事项

  1. 当多个进程同时读写共享内存存在竞态冲突,需要用户自己做好互斥,可以使用信号量等机制。
  2. 共享内存具有内核持久性,除非手动调用shmctl删除,否则会一直存在,直到系统重启。
  3. fork后执行exec,则共享内存会自动解除映射,detach
  4. 创建共享内存有一些限制。
  • shmmax  单个共享内存块最大字节数
  • shmmnb  共享内存空间最小字节数
  • shmmni  系统最多可以创建的共享内存数量
  • shmseg  单个进程最多可以映射的共享内存数量
  • shmall   系统可供使用的共享内存大小

6. 参考资料

1. https://linux.die.net/man/2/shmctl

https://linux.die.net/man/2/shmget

2. 《Linux环境编程 从应用到内核》

3. 《Unix网络编程 卷二 进程间通信》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值