共享内存 不同于内存映射区 ,它不属于任何进程,并且不受进程生命周期的影响,通过调用 Linux 提供的系统函数可以得到这块共享内存。使用之前需要让进程和共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作;进程也可以和这块共享内存解除关联,解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中,共享内存的效率是最高的。
共享内存操作默认不阻塞,如果多个进程同时读写共享内存,可能出现数据混乱。共享内存需要借助其他机制(比如:信号量)来保证进程间的数据同步,共享内存内部没有提供这种机制。
创建/打开共享内存 在使用共享内存之前必须要先做一些准备工作,如果共享内存不存在就需要先创建出来,如果已经存在就需要先打开这块共享内存。不管是创建还是打开共享内存使用的函数是同一个,函数原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <sys/ipc.h> #include <sys/shm.h> int shmget (key_t key, size_t size, int shmflg) ;
场景:创建一块大小为 4k 的共享内存
1 shmget (100 , 4096 , IPC_CREAT | 0664 );
场景:创建一块大小为 4k 的共享内存,并且检测是否存在
1 2 shmget (100 , 4096 , IPC_CREAT | 0664 | IPC_EXCL);
场景:打开一块已经存在的共享内存
1 2 3 4 shmget (100 , 4096 , IPC_CREAT|0664 );shmget (100 , 0 , 0 );
场景:打开一块共享内存,如果不存在就创建
1 shmget (100 , 4096 , IPC_CREAT | 0664 );
补充:shmget()
函数的第一个参数是一个大于 0 的正整数,如果不想自己指定可以通过 ftok()
函数直接生成这个 key 值。 该函数的函数原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <sys/types.h> #include <sys/ipc.h> key_t ftok (const char *pathname, int proj_id) ;key_t key = ftok ("/home/robin" , 'a' );shmget (key, 4096 , IPC_CREATE | 0664 );
关联和解除关联 创建/打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址,通过内存地址进行数据的读写操作。 函数的原型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void *shmat (int shmid, const void *shmaddr, int shmflg) ;
当进程不需要再操作共享内存,可以让进程和共享内存解除关联。另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联就自动解除了。 函数的原型如下:
1 2 3 4 5 6 7 int shmdt (const void *shmaddr) ;
删除共享内存 shmctl()
函数是一个多功能函数,可以设置、获取共享内存的状态,也可以将共享内存标记为删除状态。当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部和共享内存解除关联,共享内存才会被删除。通过 shmctl()
函数只是能够标记删除共享内存,在程序中多次调用该操作是没有关系的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int shmctl (int shmid, int cmd, struct shmid_ds *buf) ;struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; pid_t shm_cpid; pid_t shm_lpid; shmatt_t shm_nattch; };
相关 shell 命令 使用 ipcs
添加参数 -m
:查看系统中共享内存的详细信息
1 2 3 4 5 6 $ ipcs -m ------------ 共享内存段 -------------- 键 shmid 拥有者 权限 字节 nattch 状态 0x00000000 425984 oracle 600 524288 2 目标 0x00000000 327681 oracle 600 524288 2 目标
使用 ipcrm
命令标记删除某块共享内存
1 2 3 4 5 $ ipcrm -M shmkey $ ipcrm -m shmid
通过 shmctl()
可知:共享内存的信息是存储到一个 struct shmid_ds
的结构体中,其中有一个重要的成员叫做 shm_nattch
,在这个成员变量里边记录着当前共享内存关联的进程的个数,一般将其称之为引用计数。当共享内存被标记为删除状态,并且这个引用计数变为 0 之后共享内存才会被真正的被删除掉。
当共享内存被标记为删除状态之后,共享内存的状态也会发生变化,共享内存内部维护的 key
从一个正整数变为 0,其属性从公共的变为私有的。私有是指只有已经关联成功的进程才允许继续访问共享内存,不再允许新的进程和这块共享内存进行关联了。下图表示了共享内存的状态变化:
进程间通信 使用共享内存实现进程间通信的操作流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. 调用 Linux 的系统 API 创建一块共享内存 这块内存不属于任何进程,默认进程不能对其进行操作。 2. 准备好进程 A 和进程 B,这两个进程需要和创建的共享内存进行关联 关联操作: 调用 Linux 的 api 关联成功之后,得到了这块共享内存的起始地址 3. 在进程 A 或者进程 B 中对共享内存进行读写操作 读内存: printf () 等; 写内存: memcpy () 等;4. 通信完成,可以让进程 A 和 B 和共享内存解除关联 解除成功,进程 A 和 B 不能再操作共享内存 共享内存不受进程生命周期的影响的 5. 共享内存不在使用之后,将其删除 调用 Linux 的 api 函数,删除之后这块内存被内核回收
写共享内存的进程,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdio.h> #include <sys/shm.h> #include <string.h> int main () { int shmid = shmget (1000 , 4096 , IPC_CREAT | 0664 ); if (shmid == -1 ) { perror ("shmget error" ); return -1 ; } void * ptr = shmat (shmid, NULL , 0 ); if (ptr == (void *) -1 ) { perror ("shmat error" ); return -1 ; } const char * p = "写共享内存的进程,写数据..." ; memcpy (ptr, p, strlen (p)+1 ); printf ("按任意键继续,删除共享内存\n" ); getchar (); shmdt (ptr); shmctl (shmid, IPC_RMID, NULL ); printf ("共享内存已经被删除...\n" ); return 0 ; }
读共享内存的进程,示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <sys/shm.h> #include <string.h> int main () { int shmid = shmget (1000 , 0 , 0 ); if (shmid == -1 ) { perror ("shmget error" ); return -1 ; } void * ptr = shmat (shmid, NULL , 0 ); if (ptr == (void *) -1 ) { perror ("shmat error" ); return -1 ; } printf ("读共享内存的进程,数据: %s\n" , (char *)ptr); printf ("按任意键继续,删除共享内存\n" ); getchar (); shmdt (ptr); shmctl (shmid, IPC_RMID, NULL ); printf ("共享内存已经被删除...\n" ); return 0 ; }
shm 和 mmap 的区别 共享内存和内存映射区都可以实现进程间通信,分析二者的区别: (1)实现进程间通信的方式 共享内存:多个进程只需要一块共享内存就够了。共享内存不属于进程,需要和进程关联才能使用。 内存映射区:位于每个进程的虚拟地址空间中,并且需要关联同一个磁盘文件才能实现进程间数据通信。 (2)效率 共享内存:直接对内存操作,效率高。 内存映射区:需要内存和文件之间的数据同步,效率低。 (3)生命周期 共享内存:进程退出对共享内存没有影响,调用相关函数/命令/关机才能删除共享内存。 内存映射区:进程退出,内存映射区也就没有了。 (4)数据的完整性(突发状态下数据能否被保存,比如:突然断电) 共享内存:数据存储在物理内存中,断电之后系统关闭,内存数据也就丢失了。 内存映射区:可以完整的保存数据,内存映射区数据会同步到磁盘文件。
参考资料 https://subingwen.cn/linux/shm