进程间通信之——共享内存

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

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

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

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


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

我们在来看共享内存:


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

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

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

共享内存

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

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

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


内核中的:


——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可以看到:


——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查看和修改他们:


共享内存的操作

创建或打开——shmget函数

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

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

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

           size表示共享内存大小;

           shmflg标识共享内存段的创建标识;

            ——一般常用两个:①创建:IPC_CREAT|0644②打开:0

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

共享内存挂载—— shmat函数

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

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

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

          shmaddr表示挂载到虚拟地址空间的位置,一般置NULL,内核会帮我们找到位置;

          shmflg用来指定共享内存段的访问权限和映射条件;

          ——flag有以下几个值:


但是我们一般将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;

           cmd表示将要执行的操作,包括IPC_RMID、IPC_SET、IPC_STAT和IPC_INFO;

   这些操作可以在/usr/include/linux/ipc.h中看到:


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


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


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


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


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

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


对于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


  buf表示指向一个保存着共享内存的模式状态和访问权限的数据结构,结构体如下:


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

我们可以在命令行中使用

ipcs -m查看共享内存

ipcrm -M key删除共享内存段

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

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

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

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

creat.c:


at.c:


read.c:


测试结果:

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


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

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

执行read.ch


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

猜你喜欢

转载自blog.csdn.net/l__xing/article/details/80198088