共享内存
效率最高的进程间通信的方式,内存映射虽然也可以在内存中通信但是依旧需要文件作为载体 在映射的内存中(映射后的内存是在用户区的)操作后依旧要将改动同步到文件中 所以效率不是最高的。
共享内存允许两个或者多个进程(需要考虑互斥和同步的问题)共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间的一部分,因此这种IPC(进程通信)机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。
与管道 等要求 发送进程数据 从用户空间的缓冲区复制进内核内存(将进程中用户区的数据拷贝到内核区的管道) 和接收进程将数据从内核复制进用户空间的缓冲区(从内核区的管道拷贝到用户区的变量里) 相比共享内存的速度更快
使用步骤
调用shmget()创建一个新共享内存段或者取得一个既有共享内存段的标识符(由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。(得到一块内存和id 但这款内存还未与当前进程产生关系) 这块内存的存在位置 为共享库和内存映射的那块空间(用户区)
使用shmat()来附上共享内存段,即 使该段成为调用进程的虚拟内存的一部分。(将创建出来的共享内存 附加到当前进程中 即使共享内存成为当前进程的虚拟内存的一部分)
此刻在程序中可以像对待其他可用内存那样对待这个共享内存段,为引用这块共享内存,程序需要使用由shmat()调用返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段起点的指针。
调用shamt()来分离共享内存段,在这个调用后进程就无法再引用这块共享内存了,这一步可选,进程终止时会自动完成这一步。
调用shmctl()来删除共享内存段,只有当前所有附加内存段的进程都与之分离之后内存段才会销毁,所以只需要最后一个与内存段相连的进程来执行这一步就行。
共享内存写进程
/*#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);创建一个新的共享内存段,或获取一个已有的共享内存段的标识新创建的内存段中的数据被初始化为全0,这款内存在用户区的共享空间中(share库,内存映射的那块区域)key_t整型 通过key找到或创建一个共享内存 16进制非0size 共享内存的大小 不是指定多大就为多大 实际为页大小的整数倍shmflg 共享内存的属性 访问权限|创建共享内存|判断共享内存是否存在IPC_CREAT 创建共享内存标识IPC_EXCL 与 IPC_CREAT 一起使用(IPC_CREAT|IPC_EXCL)确保共享内存段(segment)创建成功 如果内存段已存在函数调用失败shmflg 也可直接在后面填加 这块共享内存权限的八进制数 比如IPC_CREAT|0664SHM_HUGETLBSHM_HUGE_2MB, SHM_HUGE_1GB (since Linux 3.8)SHM_NORESERVE返回值 成功返回共享内存的标识ID 失败返回-1int shmctl(int shmid, int cmd, struct shmid_ds *buf);对共享内存做操作,共享内存要手动删除,创建共享内存的进程被销毁了对共享内存是没有任何影响的shmid 共享内存的idcmd 要做的操作IPC_STAT 获取共享内存的信息IPC_SET 设置共享内存 设置的信息通过buf传入IPC_RMID 标记共享内存需要被删除 并不是一标记就能删除 只有在没有任何线程与这块内存有关联后 才能通过这里的标记删除来达到删除成功buf 被设置或读取的属性信息 共享内存对应所有者的权限(组、用户 读写可执行).... shm_nattach当前与此块共享内存关联的进程数...太多了自己去man2看IPC_STAT buf传出信息 IPC_SET buf中需要初始化为你想要设置的信息 IPC_RMID buf无用可传null#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);将共享内存块与当前进程进行关联shmid 共享内存块的ID 由shmget返回得到shmaddr 指定的共享内存的起始地址,一般不特别指定传NULL 由内核指定shmflg 对共享内存的操作权限SHM_EXEC 当前线程可执行共享内存SHM_RDONLY 当前线程只能读共享内存 进程对共享段的读权限是必须的,如果没有指定这个只读则默认是读写权限(传0就是读写权限)SHM_REMAP (Linux-specific)返回值 成功返回共享内存的起始地址(虚拟地址空间) 失败返回(void*)-1int shmdt(const void *shmaddr);解除当前进程和共享内存的关联shmaddr 共享内存的首地址返回值 成功返回0 失败返回-1#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);*/#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <string.h>int main(){//创建一个共享内存int shmid = shmget(0x1234, 4096, IPC_CREAT | 0664); //id号16进制0x1234 大小4096个字节 创建一块共享内存//和当前进程进行关联void *addr = shmat(shmid, NULL, 0);//写数据char *str = "hello world";memcpy(addr, str, strlen(str) + 1);printf("按任意键 写进程解除关联");getchar();//解除关联shmdt(addr);//删除共享内存块shmctl(shmid, IPC_RMID, NULL);return 0;}
共享内存读进程
/*#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);创建一个新的共享内存段,或获取一个已有的共享内存段的标识新创建的内存段中的数据被初始化为全0,这款内存在用户区的共享空间中(share库,内存映射的那块区域)key_t整型 通过key找到或创建一个共享内存 16进制非0size 共享内存的大小 不是指定多大就为多大 实际为页大小的整数倍shmflg 共享内存的属性 访问权限|创建共享内存|判断共享内存是否存在IPC_CREAT 创建共享内存标识IPC_EXCL 与 IPC_CREAT 一起使用(IPC_CREAT|IPC_EXCL)确保共享内存段(segment)创建成功 如果内存段已存在函数调用失败SHM_HUGETLBSHM_HUGE_2MB, SHM_HUGE_1GB (since Linux 3.8)SHM_NORESERVE返回值 成功返回共享内存的标识ID 失败返回-1int shmctl(int shmid, int cmd, struct shmid_ds *buf);对共享内存做操作,共享内存要手动删除,创建共享内存的进程被销毁了对共享内存是没有任何影响的shmid 共享内存的idcmd 要做的操作IPC_STAT 获取共享内存的信息IPC_SET 设置共享内存 设置的信息通过buf传入IPC_RMID 标记共享内存需要被删除 并不是一标记就能删除 只有在没有任何线程与这块内存有关联后 才能通过这里的标记删除来达到删除成功buf 被设置或读取的属性信息 共享内存对应所有者的权限(组、用户 读写可执行).... shm_nattach当前与此块共享内存关联的进程数...太多了自己去man2看IPC_STAT buf传出信息 IPC_SET buf中需要初始化为你想要设置的信息 IPC_RMID buf无用可传null#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);将共享内存块与当前进程进行关联shmid 共享内存块的ID 由shmget返回得到shmaddr 指定的共享内存的起始地址,一般不特别指定传NULL 由内核指定shmflg 对共享内存的操作权限SHM_EXEC 当前线程可执行共享内存SHM_RDONLY 当前线程只能读共享内存 进程对共享段的读权限是必须的,如果没有指定这个只读则默认是读写权限(传0就是读写权限)SHM_REMAP (Linux-specific)返回值 成功返回共享内存的起始地址(虚拟地址空间) 失败返回(void*)-1int shmdt(const void *shmaddr);解除当前进程和共享内存的关联shmaddr 共享内存的首地址返回值 成功返回0 失败返回-1#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);根据指定的路径名 要已存在 且可访问(读) 和一个int表示工程的id编号 生成共享内存的标号key shmgetproj_id 非0 虽然是int 但是系统在使用时只会使用其中的1个字节(8位 0-255)只要pathname和proj_id给的一致则每次返回的key也都一致key_t在Linux中是一个32位的值,它通过取proj_id参数的最低8个有效位、包含pathname指定文件所属的文件系统的设备的次要设备号的最低8个有效位以及pathname所指定文件的i-node号的最低16个有效位组成。实际应 用中,很容易产生的一个理解是,在proj_id相同的情况下,只要文件(或目录)名称不变,就可以确保ftok返回始终一致的键值。然而,这个理解并非 完全正确,有可能给应用开发埋下很隐晦的陷阱。因为ftok的实现存在这样的风险,即在访问同一共享内存的多个进程先后调用ftok函数的时间段中,如果 pathname指定的文件(或目录)被删除且重新创建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能 正常返回,但得到的键值却并不能保证相同。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上 进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目 的将无法实现。*/#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <string.h>int main(){//获取读进程创建的那块共享内存int shmid = shmget(0x1234, 0, IPC_CREAT);//id号16进制0x1234 大小0字节表示获取(写4096也没事但是不能大于创建的4096) 创建(获取)一块共享内存//和当前进程进行关联void *addr = shmat(shmid, NULL, 0);//读数据printf("%s\n", (char *)addr);printf("按任意键 读进程解除关联");getchar();//解除关联shmdt(addr);//删除共享内存块shmctl(shmid, IPC_RMID, NULL);return 0;}
操作系统如何知道一块共享内存被多少个进程关联?
man 2 shmctl 在可获取的共享内存的属性中 有shm_nattach表示当前与此块共享内存关联的进程数
常用共享内存操作命令 ipcs用法
![]() |
|---|
共享内存的key如果为0(用ipcs -m查看 键)则表示这块共享内存被标记删除(被标记但是还没删 因为还有进程与它有关联) ipcrm -m也是标记删除而不是直接删除
能否对共享内存进行过次删除 shmctl
可以,shmctl做的只是标记删除而不是直接删除 只有在与共享内存关联的进程数为0并且被标记删除则自动删除。取消关联后无法再次关联。一旦通过shmctl对共享内存进行了删除操作,则该共享内存将不能再接受任何新的连接,即使它依然存在于系统中!所以,可以确知, 在对共享内存删除之后不可能再有新的连接,则执行删除操作是安全的;否则,在删除操作之后如仍有新的连接发生,则这些连接都将可能失败!
现在的问题是, 如果两个进程以一个相同的key共享一块内存区,但不同的进程调用shmget的size不一样,是否能成功?
已经创建的共享内存的大小是可以调整的,但是已经创建的共享内存的大小只能调小,不能调大。也就是说,如果进程A先调用shmget创建一块size大小为10M的内存,进程B再次调用shmget时的size参数只能比10M小,如果大于10M,程序会报shmget error: Invalid argument错误。
这种关系可能导致的问题:
当多个进程都能创建共享内存的时候,如果key出现相同的情况,并且一个进程需要创建的共享内存的大小要比另外一个进程要创建的共享内存小,共享内存大的进程先创建共享内存,共享内存小的进程后创建共享内存,小共享内存的进程就会获取到大的共享内存进程的共享内存,并修改其共享内存的大小和内容,从而可能导致大的共享内存进程崩溃。
解决方法:
在oflag参数中使用排他性创建,即使用IPC_EXCL标记;
key值使用IPC_PRIVATE,然后将最终返回的共享内存标识符用其他IPC方法通知给相关进程;
下面这段话很重要:
根据pathname指定的文件(或目录)名称,以及id参数指定的数字,ftok函数为IPC对象生成一个唯一性的键值。 在实际应用中,很容易产生的一个理解是,在id相同的情况下,只要文件(或目录)名称不变,就可以确保ftok返回始终一致的键值。然而,这个理解并非完全正确,有可能给应用开发埋下很隐晦的陷阱。 因为ftok的实现存在这样的风险,即在访问同一共享内存的多个进程先后调用ftok函数的时间段中,如果pathname指定的文件(或目录)被删除且重新创建,则文件系统会赋予这个同名文件(或目录)新的i节点信息,于是这些进程所调用的ftok虽然都能正常返回,但得到的键值却并不能保证相同。由此可能造成的后果是,原本这些进程意图访问一个相同的共享内存对象,然而由于它们各自得到的键值不同,实际上进程指向的共享内存不再一致;如果这些共享内存都得到创建,则在整个应用运行的过程中表面上不会报出任何错误,然而通过一个共享内存对象进行数据传输的目的将无法实现。
解决方法:
如果要确保key_t值不变,要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值。
另外, 建议: 创建进程在通知其他进程挂接的时候,建议不使用ftok方式来获取Key,而使用文件或者进程间通信的方式告知。
共享内存和内存映射的区别
共享内存可以直接创建,内存映射需要磁盘文件(匿名映射 亲属间 可不需要)
内存映射需要将内存中的改动与磁盘文件同步 效率更低
共享内存:所有的进程操作的是同一块共享内存
内存映射: 每个进程在自己的虚拟地址空间(进程用户区)中有一个独立的内存
进程退出 内存映射区销毁但是文件中的数据还在 共享内存还在 需要标记删除并且所有与之关联的进程与它断开联系才会销毁(进程退出会自动与共享内存取消关联)

