文章目录
  1. shmget系统调用
  2. shmat与shmdt系统调用
  3. shmctl系统调用
  4. 共享内存的POSIX方法

共享内存是最高效的IPC机制,因为它不涉及进程之间任何的数据传输。但是这种高效率必须依靠其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。因此,共享内存退出需要与其他进程间通信方式一起使用。下面介绍共享内存的4个系统调用。

shmget系统调用

shmget系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。其定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
/*
引用方式: #include <sys/shm.h>
key: 键值, 标志一段全局唯一的共享内存
size: 指定共享内存的大小; 创建新的共享内存: size必须指定 || 获取已有的共享内存: size可设置为0
flags: 指定一组标志, 其低端的9比特为该信号量的权限, 格式含义均与系统调用open的mode参数相同, 可与下面标志按位或
SHM_CREAT: 创建新的共享内存
SHM_EXCL: 确保创建的共享内存唯一(若共享内存已经存在, 则semget返回-1并设置errno为EEXIST)
SHM_HUGETAB: 系统将使用大页面来为共享内存分配空间
SHM_NORESERVE: 不为共享内存保留交换分区(此时在共享内存不足时, 对该共享内存的写操作将触发SIGSEGV信号)
返回共享内存的标识符(正整数): 成功 || 返回-1并设置errno: 失败
*/
int shmget(key_t key, size_t size, int shmflg);

使用shmget创建共享内存之后,这段共享内存的所以字节均被初始化为0,与之关联的内核数据结构shmid_ds将被创建并初始化。结构体shmid_ds定义如下。

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/shm.h>
shm_perm: 共享内存的操作权限
shm_segsz: 共享内存大小
shm_atime: 对这段共享内存最后一次调用shmat的时间
shm_dtime: 对这段共享内存最后一次调用shmdt的时间
shm_ctime: 对这段共享内存最后一次调用shmctl的时间
shm_cpid: 创建者的PID
shm_lpid: 最后一次执行shmat或shmda操作的进程的PID
shm_nattch: 目前关联到此共享内存的进程数量
*/
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;
__syscall_ulong_t __unused4;
__syscall_ulong_t __unused5;
};

shmget对shmid_ds结构体的初始化如下。

  • shm_perm.cuid与shm_perm.uid设置为调用进程的有效用户ID;
  • shm_perm.cgid与shm_perm.gid设置为调用进程的有效组ID;
  • shm_perm.mode低9位设置为flags参数的最低9位;
  • shm_segsz设置为size;
  • shm_atime、shm_dtime、shm_lpid、shm_nattch设置为0;
  • shm_ctime设置为当前系统时间;

shmat与shmdt系统调用

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

1
2
3
4
5
6
7
8
9
10
11
/*
引用方式: #include <sys/shm.h>
shm_id: 指定的共享内存标识符(由shmget系统调用返回)
shm_addr: 指定共享内存关联的地址; 推荐为NULL, 此时关联的地址由操作系统选择, 可使得代码具有良好的移植性; 对于指定了具体地址的情况, 关联的地址还会受到shmflg的影响
shmflg: 可选标志, 详见下文
SHM_RDONLY: 进程只能读取共享内存的内容; 若未指定此标志,在共享内存的创建时指定了可写的情况下, 进程可对共享内存进行写操作
SHM_REMAP: 若地址shm_addr已被关联到一段共享内存上,则将其重新关联到shm_id指定的共享内存
SHM_EXEC: 指定对共享内存的执行权限(等价于读权限)
返回共享内存被关联到的地址: 成功 || 返回(void *)(-1)并设置errno: 失败
*/
void shmat(int shm_id, const void * shm_addr, int shmflg);
  • 若shm_addr非空,并且未设置SHM_SND标志,则共享内存被关联到addr指定地址;
  • 若shm_addr非空,并且设置了SHM_SND标志,则被关联的地址为shm_addr-(shm_addr%SHMLBA),其中SHMLBA为”段低端边界地址倍数”。它必须是内存页面大小的整数倍;

shmat成功返回将修改内核数据结构shmid_ds的部分字段。

  • shm_nattch置1;
  • shm_lpid置为调用进程的PID;
  • shm_atime置为当前时间;
1
2
3
4
5
/*
引用方式: #include <sys/shm.h>
返回0: 成功 || 返回-1并设置errno: 失败
*/
int shmdt(const void * shm_addr);

shmdt系统调用将关联到shm_addr处的共享内存从进程中分离。shmdt成功返回时将修改内核数据结构shmid_ds的部分字段。

  • shm_nattch减1;
  • shm_lpid置为调用进程的PID;
  • shm_dtime置为当前时间;

shmctl系统调用

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

1
2
3
4
5
6
7
/*
引用方式: #include <sys/shm.h>
shm_id: 指定的共享内存标识符(由shmget系统调用返回)
command: 指定执行的命令, 详见下表
返回值取决于command参数 || 返回-1并设置errno: 失败
*/
int shmctl(int shm_id, int command, struct shmid_ds * buf);

其中,shmctl支持的命令如下表。

 

共享内存的POSIX方法

Linux提供另一种利用mmap在无关内存之间共享内存的方式,这种方式无须任何文件的支持,但它需要使用如下函数创建/打开一个POSIX共享内存对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
引用方式: #include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
name: 指定要创建/打开的共享内存对象(从可移植性的角度考虑, name参数应该是/[a-z][A-Z]{1,255}形式)
oflag: 指定创建方式, 如下标志的按位或
O_RDONLY: 以只读方式打开共享内存对象
O_RDWR: 以可读可写的方式打开共享内存对象
O_CREAT: 若共享内存对象不存在,则创建它; 此时mode参数的低9位指定该共享内存对象的访问权限(共享内存对象被创建时, 其初始长度为0)
O_EXCL: 与O_CREAT一起使用, 若由name指定的共享内存对象已经存在, 则shm_open调用返回错误, 否则创建一个新的共享内存对象
O_TRUNC: 若共享内存对象已存在, 则将其截断, 使其长度为0
返回一个文件描述符: 成功 || 返回-1并设置errno: 失败
*/
int shm_open(const char * name, int oflag, mode_t mode);

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

一般,shm_open返回的文件描述符会配合ftruncate函数使用,ftruncate函数设置指定文件的大小。其定义如下。

1
2
3
4
5
6
/*
引用方式: #include <unistd.h>
fd: 指定的文件描述符(该文件必须以写入模式打开)
size: 指定的大小(若原来的文件大小比参数size大,则超过的部分会被截断)
*/
int ftruncate(int fd, off_t size);

同理,shm_open创建的共享内存对象也需要在使用完释放,使用shm_unlink函数将共享内存对象标记为”等待删除”,当所有关联了此共享内存对象的进程都使用munmap函数将共享内存对象从进程的地址空间中分离之后,系统将销毁这个共享内存对象所占据的资源。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
name: 指定共享内存对象
返回0: 成功 || 返回-1并设置errno: 失败
*/
int shm_unlink(const char * name);

注:使用gcc编译调用了POSIX共享内存函数的代码时,需要加上链接选项-lrt。