共享内存是所有IPC方式中最快的一种,原因在于共享内存一旦映射到进程地址空间,进程间数据的传递就不需要涉及内核。

对于管道、FIFO和消息队列,两个进程之间通过这三种方式进行通信,则内核就扮演着“中转站”的角色。

——发送消息一方,通过系统调用(write或msgsnd)将消息从用户层拷贝到内核层,由内核暂时保存这份信息;

——接受消息的一方,通过系统调用(read或msgrcv)将消息从内核层提取到用户层;

android 共享内存 实现对象共享 共享内存的通信过程_共享内存

注意:在使用read/write时,操作系统还会将数据缓存到临时缓冲区内。

我们在来看共享内存:

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_02

内核负责构建出一片内存区域,两个或多个进程可以将这块内存区域映射到自己的虚拟地址空间,从此之后内核不再参与双方通信。

注意:建立共享内存之后,内核并不是完全不参与进程间的通信,因为当进程使用共享内存时,可能会发生缺页,引发缺页中断,这种情况下,内核还是会参与进来的。

一般情况下,允许多个进程同时操作共享内存,就不得不防范竞争条件的出现,比如有两个进程同时执行写操作将会导致数据的不确定性,或者一个进程在执行读取操作时,另外一个进程正在执行更新操作。因此,共享内存这种进程间通信的手段通常不会单独出现,总是和信号量、文件锁等同步的手段配合使用。

共享内存

共享内存空间有自己特定的数据结构,包括访问权限、大小以及最近访问时间等;

它的结构体定义在:/usr /src/kernels/2.6.32-431.el6.i686/include/Linux/shm.h中。如下:

操作系统提供给用户看的结构体:

android 共享内存 实现对象共享 共享内存的通信过程_android 共享内存 实现对象共享_03

内核中的:

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_04

——shm_perm表示kern_ipc_perm数据结构

——shm_file共享段特殊文件

——shm_nattch当前附加的内存区数

——shm_segsz内存区字节数

——shm_atim最后访问时间

——shm_dtim最后分离时间

——shm_ctim最后修改时间

——shm_cprid创建者pid

——shm_lprid最后访问进程的pid

——mlock_user锁定在共享内存RAM中的用户的user_struct描述符指针

由于共享内存会占用大量的内存空间,因此,操作系统对共享内存做了限制:

在/usr/include/linux/shm.h可以看到:

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_05

——SHMMAX表示一个共享内存段的最大字节数 为32MB;

——SHMMIN表示一个共享内存段的最小字节数 为1B;

——SHMMNI表示系统所能创建的共享内存的最大个数 为4096;

——SHMALL表示系统中共享内存的分页总数 为2M个页即可表示所有共享段的总字节数为2M*4KB=8GB;

——SHMSEG表示一个进程允许attach的共享内存段的最大个数,可以看到系统默认和SHMMNI一样;

我们可以通过/proc/sys/kernel/shmmni、cat /proc/sys/kernel/shmmax和/proc/sys/kernel/shmall查看和修改他们:

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_06

共享内存的操作

创建或打开——shmget函数

功能:创建或打开共享内存

原型:int shmget(key_t key, size_t size, int shmflg);

参数:key表示共享内存段的名字;

   

②打开:0

返回值:成功返回一个非负整数,即该共享内存段的标识符;失败返回-1;

共享内存挂载—— shmat函数

功能:将共享内存挂载到进程自己的地址空间下

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

参数:shmid表示共享内存的标识即shmget得到的id;

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_07

但是我们一般将flag设置为0表示具有读写权限;

返回值:成功返回一个指针,指向共享内存段的第一个节;失败返回-1;

共享内存卸载——shmdt函数

功能:卸载但并不删除共享内存段即只是将共享内存段与当前进程脱离;

原型:int shmdt(const void *shmaddr);

参数:shmadder由shmat返回的指针;

返回值:成功返回0;失败返回-1;

注意:shmdt 函数仅仅是使进程和共享内存脱离关系,并未删除共享内存。 shmdt 函数的作用是将共享内存的引用计数减 1 。只有共享内存的引用计数为 0 时,调用 shmctl 函数的 IPC_RMID 命令才会真正地删除共享内存。
进程执行 exec 之后,所有 attach 的共享内存都会被分离。当进程终止之后,共享内存也会自动被分离。

共享内存控制——shmctl函数

功能:用于控制共享内存包括读取状态、设置状态和删除操作;

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

参数:shmid表示共享内存的标识即shmget得到的id;

android 共享内存 实现对象共享 共享内存的通信过程_共享内存_08

对于IPC_STAT,用于获取 shmid 对应的共享内存的信息。所谓信息,就是下面结构体的内容:

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_09

可以看到,在该结构体中有一个shm_perm,我们可以看看该结构体内容:

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_10

在shm_perm中有一个mode字段,该有两个比较特殊的标志位,即 SHM_DEST 和 SHM_LOCKED :

android 共享内存 实现对象共享 共享内存的通信过程_android 共享内存 实现对象共享_11

删除共享内存时,可能由于 attach 它的进程个数不为 0 ,因此只能打上一个标记,表示标记删除,待到所有 attach 该共享内存的进程都执行过分离( detach )操作,共享内存的引用计数变成 0 之后,才执行真正的删除操作。所谓的标记指的就是 SHM_DEST 标志位。
对于已经标记删除的共享内存,可以通过 ipcs-m 命令的 status 栏来查看,其 dest 含义是已经标记删除的意思。

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_12

可以通过 shmctl 的 SHM_LOCK 操作将一个共享内存段锁入内存,这样它就不会被置换出去。这样做的好处是访问共享内存的时候,不会产生缺页中断( page fault )。

通过 ipcs-m 的输出可以查看共享内存是否被锁入内存,注意下面状态中的 locked 字段,该字段表明对应的共享内存已被锁入内存。

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_13

对于IPC_SET:IPC_SET 也只能修改 shm_perm 中的 uid 、 gid 及 mode 。

进行删除操作的话,选用IPC_RMID;但是需要注意的是:如果共享内存的引用计数 shm_nattch 等于 0 ,则可以立即删除共享内存。但是如果仍然存在进程attach 该共享内存,则并不执行真正的删除操作,而仅仅是设置 SHM_DEST 标记。待所有进程都执行过分离操作之后,再执行真正的删除操作。共享内存处于 SHM_DEST 状态的情况下,依然允许新的进程调用 shmat 函数来 attach该共享内存。

当然,如果是超级用户,还可以有两个操作:SHM_LOCK和SHM_UNLOCK

android 共享内存 实现对象共享 共享内存的通信过程_android 共享内存 实现对象共享_14

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_15

返回值:成功返回0;失败返回-1;

我们可以在命令行中使用:

ipcs -m查看共享内存

ipcrm -M key删除共享内存段

测试案例:(没有使用信号量或文件锁等手段进行同步使用,后面的文章中会写一个测试用例,本章着重讲解它的一些基础概念)

①先写一个create.c用来创建共享内存;

②再写一个at.c旨在打开创建的共享内存并向其中写入数据;

③最后写一个read.c用来读取共享内存中的数据;

creat.c:

android 共享内存 实现对象共享 共享内存的通信过程_android 共享内存 实现对象共享_16

at.c:

android 共享内存 实现对象共享 共享内存的通信过程_android 共享内存 实现对象共享_17

read.c:

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_18

测试结果:

当我们执行create.c后,使用ipcs -m可以看到

android 共享内存 实现对象共享 共享内存的通信过程_删除操作_19

bytes表示创建的大小为36字节;

nattch表示连接到共享内存的进程数;

执行read.ch

android 共享内存 实现对象共享 共享内存的通信过程_引用计数_20

    本篇文章只是大体上的简单介绍了共享内存的些许基础概念,并没有深入探讨,文章中如有错误的地方,欢迎大家指正,不胜感激!