Linux多进程进阶---进程间通信《内存共享》(三)


前言

  共享内存不同于内存映射区,它不属于任何进程,并且不受进程生命周期的影响。通过调用Linux提供的系统函数就可得到这块共享内存。使用之前需要让进程和共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联, 解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中共享内存的效率是最高的。

一、函数介绍

1. shmget(key_t key, size_t size, int shmflg)

功能:创建一个新的共享内存段,或者获取一个已存在的共享内存段的标识符。
参数:
key:用于唯一标识共享内存段的键值。可以使用IPC_PRIVATE创建一个仅对当前进程可见的私有共享内存,或者使用其他通过ftok()生成的键值来创建或访问公共共享内存。
size:共享内存段的大小(字节)如果是打开一块存在的共享内存, size是没有意义的。
shmflg:控制共享内存段的创建和权限。
  IPC_CREAT: 创建新的共享内存,如果创建共享内存, 需要指定对共享内存的操作权限,比如:IPC_CREAT | 0664
  IPC_EXCL: 检测共享内存是否已经存在了,必须和 IPC_CREAT一起使用
返回值:
共享内存创建或者打开成功返回标识共享内存的唯一的ID,失败返回-1

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

参数:
pathname: 当前操作系统中一个存在的路径
proj_id: 这个参数只用到了int中的一个字节, 传参的时候要将其作为 char 进行操作,取值范围: 1-255
返回值:
函数调用成功返回一个可用于创建、打开共享内存的key值,调用失败返回-1

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

参数:
shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
shmaddr: 共享内存的起始地址, 用户不知道, 需要让内核指定, 写NULL
shmflg: 和共享内存关联的对共享内存的操作权限
  SHM_RDONLY: 读权限, 只能读共享内存中的数据
  0: 读写权限,可以读写共享内存数据
返回值:
关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1

4.int shmdt(const void *shmaddr);

参数
shmat() 函数的返回值, 共享内存的起始地址

返回值
关联解除成功返回0,失败返回-1

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

// 参数 struct 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;   /* Last change time */
	pid_t           shm_cpid;    /* PID of creator */
	pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    // 引用计数, 多少个进程和共享内存进行了关联
	shmatt_t        shm_nattch;  /* 记录了有多少个进程和当前共享内存进行了管联 */
	...
};

参数:
shmid: 要操作的共享内存的ID, 是 shmget() 函数的返回值
cmd: 要做的操作
  IPC_STAT: 得到当前共享内存的状态
  IPC_SET: 设置共享内存的状态
  IPC_RMID: 标记共享内存要被删除了
buf:

  cmd==IPC_RMID, buf就没意义了, 这时候buf指定为NULL即可

返回值:函数调用成功返回值大于等于0,调用失败返回-1

二、代码实例

1.写共享内存的进程代码:

代码如下(示例):

#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
    // 1. 创建共享内存, 大小为4k
    int shmid = shmget(1000, 4096, IPC_CREAT|0664);
    if(shmid == -1)
    {
        perror("shmget error");
        return -1;
    }

    // 2. 当前进程和共享内存关联
    void* ptr = shmat(shmid, NULL, 0);
    if(ptr == (void *) -1)
    {
        perror("shmat error");
        return -1;
    }

    // 3. 写共享内存
    const char* p = "hello, world, 共享内存真香...";
    memcpy(ptr, p, strlen(p)+1);

    // 阻塞程序
    printf("按任意键继续, 删除共享内存\n");
    getchar();

    shmdt(ptr);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    printf("共享内存已经被删除...\n");

    return 0;
}

2.读共享内存的进程代码:

代码如下(示例):

#include <stdio.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
    // 1. 创建共享内存, 大小为4k
    int shmid = shmget(1000, 0, 0);
    if(shmid == -1)
    {
        perror("shmget error");
        return -1;
    }

    // 2. 当前进程和共享内存关联
    void* ptr = shmat(shmid, NULL, 0);
    if(ptr == (void *) -1)
    {
        perror("shmat error");
        return -1;
    }

    // 3. 读共享内存
    printf("共享内存数据: %s\n", (char*)ptr);

    // 阻塞程序
    printf("按任意键继续, 删除共享内存\n");
    getchar();

    shmdt(ptr);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    printf("共享内存已经被删除...\n");

    return 0;
}


三、shell相关命令

  通过shmctl()我们可以得知,共享内存的信息是存储到一个叫做struct shmid_ds的结构体中,其中有一个非常重要的成员叫做shm_nattch,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为0之后共享内存才会被真正的被删除掉。

  当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的key从一个正整数变为0,其属性从公共的变为私有的。这里的私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。

$ ipcs -m

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     nattch     状态      
0x00000000 425984     oracle     600        524288     2          目标       
0x00000000 327681     oracle     600        524288     2          目标       
0x00000000 458754     oracle     600        524288     2          目标 	

使用ipcs 添加参数-m可以查看系统中共享内存的详细信息

# key == shmget的第一个参数
$ ipcrm -M shmkey  

# id == shmget的返回值
$ ipcrm -m shmid	

使用 ipcrm 命令可以标记删除某块共享内存

总结

  1. 调用linux的系统API创建一块共享内存

    • 这块内存不属于任何进程, 默认进程不能对其进行操作
  2. 准备好进程A, 和进程B, 这两个进程需要和创建的共享内存进行关联

    • 关联操作: 调用linux的 api
    • 关联成功之后, 得到了这块共享内存的起始地址
  3. 在进程A或者进程B中对共享内存进行读写操作

    • 读内存: printf() 等;
    • 写内存: memcpy() 等;
  4. 通信完成, 可以让进程A和B和共享内存解除关联

    • 解除成功, 进程A和B不能再操作共享内存了
    • 共享内存不受进程生命周期的影响的
  5. 共享内存不在使用之后, 将其删除

    • 调用linux的api函数, 删除之后这块内存被内核回收了

注:本文为学习大丙爱编程笔记

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值