Skip to content

Latest commit

 

History

History
180 lines (125 loc) · 9.27 KB

共享内存.md

File metadata and controls

180 lines (125 loc) · 9.27 KB

共享内存

共享内存是最高效的IPC(进程间通信)机制,因为它不涉及进程之间的任何数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。因此共享内存通常和其他进程间通信方式一起使用。

Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用:shmget、shmat、shmdt和shmctl

shmget系统调用

shmget:创建一段新的共享内存,或者获取一段已经存在的共享内存

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

参数

  • key:一个键值,用来标识一段全局唯一的共享内存
  • size:指定共享内存的大小,单位是字节。如果是创建新的共享内存,则size值必须被指定;如果是获取已经存在的共享内存,则可以把size设置为0
  • shmflg:和semget系统调用的sem_flags参数相同,不过shmget支持两个额外的标志——SHM_HUGETLB和SHM_NORESERVE,具体含义如下:
    • SHM_HUGETLB:类似于mmap的MAP_HUGETLB标志,系统将使用"大页面"来为共享内存分配空间
    • SHM_NORESERVE:类似于mmap的MAP_NORESERVE标志,不为共享内存保留交换分区(swap空间),这样,当物理内存不足的时候,对该共享内存执行写操作将触发SIGSEGV信号

返回值

shmget成功时返回一个正整数值,它是共享内存的标识符。shmget失败时返回-1并设置errno

如果shmget用于创建共享内存,则这段共享内存的所有字节都被初始化为0,与之关联的内核数据结构shmid_ds将被创建并初始化。shmid_ds结构体定义如下:

struct shmid_ds
{
    struct ipc_perm shm_perm;       //共享内存的操作权限
    size_t shm_segsz;               //共享内存大小,单位是字节
    __time_t shm_atime;             //对这段内存最后一次调用shmat的时间
    __time_t shm_dtime;             //对这段内存最后一次调用shmdt的时间
    __time_t shm_ctime;             //对这段内存最后一次调用shmctl的时间
    __pid_t shm_cpid;               //创建者的PID
    __pid_t shm_lpid;               //最后一次执行shmat或shmdt操作的进程的PID
    shmatt_t shm_nattach;           //目前关联到此共享内存的进程数量
    //省略一些填充字段
};

shmget对shmid_ds结构体的初始化包括:

  • 将shm_perm.cuid和shm_perm.uid设置为调用进程的有效用户ID
  • 将shm_perm.cgid和shm_perm.gid设置为调用进程的有效组ID
  • 将shm_perm.mode的最低9位设置为shmflg参数的最低9位
  • 将shm_segsz设置为size
  • 将shm_lpid、shm_nattach、shm_atime、shm_dtime设置为0
  • 将shm_ctime设置为当前的时间

shmat和shmdt系统调用

共享内存被创建/获取之后,我们不能立即访问它,而是需要先将它关联到进程的地址空间中。使用完共享内存后,我们也需要将它从进程地址空间分离。这两项任务分别由如下两个系统调用实现:

#include <sys/shm.h>
void* shmat( int shm_id, const void* shm_addr, int shmflg );

参数

  • shm_id:由shmget调用返回的共享内存标识符

  • shm_addr:指定将共享内存关联到进程的哪块地址空间

    最终的效果要受到shmflg参数的可选标志SHM_RND的影响:

    • 如果shm_addr为NULL,则被关联的地址由操作系统选择,这是推荐的做法,以确保代码的可移植性
    • 如果shm_addr非空,并且SHM_RND标志未被设置,则共享内存被关联到addr指定的地址处
    • 如果shm_addr非空,并且设置了SHM_RND标志,则被关联的地址是[shm_addr-(shm_addr%SHMLBA)]。SHMLBA的含义是"段低端边界地址倍数"(Segment Low Boundary Address Multiple),它必须是内存页面大小(PAGE_SIZE)的整数倍。现在的Linux内核中,它等于一个内存页大小。SHM_RND的含义是圆整(round),即将共享内存被关联的地址向下圆整到离shm_addr最近的SHMLBA的整数倍地址处

    除了SHM_RND标志外,shmflg参数还支持如下标志:

    • SHM_RDONLY:进程仅能读取共享内存中的内容,若没有指定该标志,则进程可同时对共享内存进行读写操作(前提是在创建共享内存的时候指定其读写权限)
    • SHM_REMAP:如果地址shmaddr已经被关联到一段共享内存上,则重新关联
    • SHM_EXEC:指定对共享内存段的执行权限。对共享内存而言,执行权限实际上和读权限一样

返回值

shmat成功时返回共享内存被关联到的地址,失败则返回(void*)-1并设置errno。shmat成功时,将修改内核数据结构shmid_ds的部分字段,如下:

  • 将shm_nattach加1
  • 将shm_lpid设置为调用进程的PID
  • 将shm_atime设置为当前时间

shmdt

int shmdt( const void* shm_addr );

shmdt函数将关联到shm_addr处的共享内存从进程中分离。

shmdt成功时返回0,失败则返回-1并设置errno。shmdt在成功调用时将修改内核数据结构shmid_ds的部分字段,如下:

  • 将shm_nattach减1
  • 将shm_lpid设置为调用进程的PID
  • 将shm_dtime设置为当前时间

shmctl系统调用

shmctl系统调用控制共享内存的某些属性,定义如下:

#include <sys/shm.h>
int shmctl( int shm_id, int command, struct shmid_ds* buf );

参数

  • shm_id:由shmget调用返回的共享内存标识符
  • command:指定要执行的命令,shmctl支持的所有命令如下表所示:
命令 含义 shmctl成功时的返回值
IPC_STAT 将共享内存相关的内核数据结构复制到buf中 0
IPC_SET 将buf中的部分成员复制到共享内存相关的内核数据结构中,同时内核数据结构中的shmid_ds.shm_ctime被更新 0
IPC_RMID 将共享内容加上删除的标记。这样当最后一个使用它的进程调用shmdt将它从进程中分离时,该共享内存就被删除了 0
IPC_INFO 获取系统共享内存资源配置信息,将结果存储在buf中。应用程序需要将buf转换成shminfo结构体类型来读取这些系统信息。shminfo结构体与seminfo类似 内核共享内存信息数组中已经被使用的项的最大索引数
SHM_INFO 与IPC_INFO类似,不过返回的是已经分配的共享内存占用的资源信息。应用程序需要将buf转换成shm_info结构体类型来读取这些信息。shminfo结构体与seminfo类似 同IPC_INFO
SHM_STAT 与IPC_STAT类似,不过此时shm_id参数不是用来表示共享内存标识符,而是内核中共享内存信息数组的索引(每个共享内存的信息都是该数组中的一项) 内核共享内存信息数组中索引值为shm_id的共享内存的标识符
SHM_LOCK 禁止共享内存被移动至交换分区 0
SHM_UNLOCK 允许共享内存被移动至交换分区 0

返回值

shmctl成功时的返回值取决于command参数,shmctl失败时返回-1并设置errno

共享内存的posix方法

利用mmap实现进程间内存共享的3种方法:

  • 利用mmap函数的MAP_ANONYMOUS标志可以实现父子进程之间的匿名内存共享
  • 通过打开同一个文件,mmap可以实现无关进程之间的内存共享
  • Linux提供了另外一种利用mmap在无关进程之间共享内存的方式。这种方式无须任何文件的支持,但它需要先使用shm_open函数来创建或打开一个POSIX共享内存对象:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_open( const char* name, int oflag, mode_t mode );

shm_open的使用方法和open系统调用完全相同

  • name:指定要创建/打开的共享内存对象。从可移植的角度考虑,该参数应该使用"/somename"的格式:以"/"开始,后接多个字符,且这些字符都不是"/":以"\0"结尾,长度不超过NAME_MAX(通常是255).

  • oflag:指定创建方式,可以是下列标志的多个按位或:

    • O_RDONLY:以只读方式打开共享内存对象
    • O_RDWR:以可读、可写方式打开共享内存对象
    • O_CREAT:如果共享内存对象不存在,则创建之,此时mode参数的最低9位将指定该共享内存对象的访问权限。共享内存对象被创建的时候,其初始长度为0
    • O_EXCL:(exclusively:排他地)和O_CREAT一起使用,如果由name指定的共享内存对象已经存在,则shm_open调用返回错误,否则就创建一个新的共享内存对象
    • O_TRUNC:(truncate:截断的)如果共享内存已经存在,则把它截断,使其长度为0

返回值

shm_open调用成功时返回一个文件描述符,该文件描述符用于后续的mmap调用,从而将共享内存关联到调用进程。

shm_open失败时返回-1并设置errno

shm_unlink

和打开的文件最后需要关闭一样,由shm_open创建的共享内存对象使用完之后也需要被删除:

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_unlink( const char *name );

shm_unlink函数将name参数指定的共享内存对象标记为等待删除。当所有使用该共享内存对象的进程都使用ummap将它从进程中分离之后,系统将销毁这个共享内存对象所占据的资源

在代码中使用上述POSIX共享内存函数,编译时需要指定链接选项-lrt

共享内存实例

TODO: 共享内容示例