本文介绍: 共享内存不同内存映射区,它不属于任何进程,并且不受进程生命周期影响通过调用Linux提供的系统函数就可得到这块共享内存使用之前需要进程共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联, 解除关联之后就不能操作这块共享内存了。在所有进程间通信方式共享内存效率是最高的。共享内存操作默认阻塞,如果多个进程同时读写共享内存可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步比如信号量共享内存内部没有提供这种机制

原文链接

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

共享内存操作默认阻塞,如果多个进程同时读写共享内存,可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步比如信号量,共享内存内部没有提供这种机制。

1、创建/打开共享内存

1.1 shmget

使用共享内存之前必须要先做一些准备工作,如果共享内存不存在需要创建出来,如果已经存在了就需要打开这块共享内存。不管是创建还是打开共享内存使用的函数是同一个,函数原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

场景1:创建一块大小为4k的共享内存

shmget(100, 4096, IPC_CREAT|0664);

场景2:创建一块大小为4k的共享内存, 并且检测是否存在

// 	如果共享内存已经存在, 共享内存创建失败, 返回-1, 可以perror() 打印错误信息
shmget(100, 4096, IPC_CREAT|0664|IPC_EXCL);

场景3:打开一块已经存在的共享内存

// 函数参数虽然指定了大小和IPC_CREAT, 但是都不起作用, 因为共享内存已经存在, 只能打开, 参数4096也没有意义
shmget(100, 4096, IPC_CREAT|0664);
shmget(100, 0, 0);

场景4:打开一块共享内存, 如果不存在就创建

shmget(100, 4096, IPC_CREAT|0664);

1.2 ftok

shmget() 函数的第一个参数一个大于0的正整数,如果不想自己指定可以通过 ftok()函数直接生成这个key值。该函数的函数原型如下

// ftok函数原型
#include <sys/types.h>
#include <sys/ipc.h>

// 将两个参数作为种子, 生成一个 key_t 类型数值
key_t ftok(const char *pathname, int proj_id);

使用举例:

// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);

2、关联和接触关联

2.1 shmat

创建/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址,通过得到的内存地址行数据的读写操作,关联函数的原型如下

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

2.2 shmdt

当进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。

int shmdt(const void *shmaddr);
  • 参数:shmat() 函数的返回值, 共享内存的起始地址

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

3、删除共享内存

3.1 shmctl

shmctl() 函数是一个多功能函数,可以设置获取共享内存的状态也可以将共享内存标记删除状态当共享内存被标记删除状态之后,并不会马上被删除,直到所有的进程全部和共享内存解除关联,共享内存才会被删除。因为通过shmctl()函数只是能够标记删除共享内存,所以在程序多次调用该操作是没有关系的。

// 共享内存控制函数
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;  /* 记录了有多少个进程和当前共享内存进行了管联 */
	...
};

3.2 相关shell命令

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

$ ipcs -m

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

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

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

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

3.3 共享内存状态

// 参数 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;  /* 记录了有多少个进程和当前共享内存进行了管联 */
	...
};

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

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

在这里插入图片描述

4、进程间通信

使用共享内存实现进程间通信的操作流程如下:

1. 调用linux系统API创建一块共享内存
    - 这块内存不属于任何进程, 默认进程不能对其进行操作
    
2. 准备好进程A, 和进程B, 这两个进程需要和创建的共享内存进行关联
    - 关联操作: 调用linuxapi
    - 关联成功之后, 得到了这块共享内存的起始地址
        
3. 在进程A或者进程B中对共享内存进行读写操作
    - 读内存: printf();
	- 写内存: memcpy();

4. 通信完成, 可以让进程A和B和共享内存解除关联
    - 解除成功, 进程A和B不能再操作共享内存了
    - 共享内存不受进程生命周期影响5. 共享内存不在使用之后, 将其删除
    - 调用linuxapi函数, 删除之后这块内存被内核回收

写共享内存的进程代码:

#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;
}

读共享内存的进程代码:

#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("共享内存数据: %sn", (char*)ptr);

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

    shmdt(ptr);

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

    return 0;
}

两个进程之间就是依靠key,实现同一片共享内存的关联,这里key = 1000

5、shm和mmap区别

共享内存内存映射都可以实现进程间通信,下面来分析一下二者区别

原文地址:https://blog.csdn.net/qq_40896190/article/details/134784842

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_42418.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注