14.System V 共享内存区

1、概述

System V共享内存区在概念上类似于Posix共享内存区。代之以调用shmopen后调用mmap的是,先调用shmget,再调用shmat。

对于每个共享内存区,内核维护如下的信息结构,它定义在<sys/shm.h>头文件中:

1struct shmid_ds 2{ 3 struct ipc_perm shm_perm; /* operation permission struct */ 4 size_t shm_segsz; /* segment size */ 5 pid_t shm_lpid; /* pid of last operation */ 6 pid_t shm_cpid; /* creator pid */ 7 shmatt_t shm_nattch; /* current attached */ 8 shmat_t shm_cnattch; /* in-core # attached */ 9 time_t shm_atime; /* last attach time */ 10 time_t shm_dtime; /* last detach time */ 11 time_t shm_ctime; /* last change time of this structure */ 12}; 13

2、shmget 函数

shmget函数创建一个新的共享内存区,或者访问一个已存在的共享内存区。

1#include <sys/shm.h> 2int shmget(key_t key, size_t size, int oflag); 3 // 返回:若成功则为共享内存区对象,若出错则为-1 4

返回值是一个称为共享内存区标识符(shared memory identifier)的整数,其他三个shmxxX函数就用它来指代这个内存区。

key既可以是ftok的返回值,也可以是IPC_PRIVATE。

size以字节为单位指定内存区的大小。当实际操作为创建一个新的共享内存区时,必须指定一个不为0的size值。如果实际操作为访问一个已存在的共享内存区,那么size应为0。

oflag是读写权限值的组合。可以与IPC_CREAT或IPC_CREAT | IPC_EXCL按位或

当实际操作为创建一个新的共享内存区时,该内存区被初始化为size字节的0。

 

3、shmat 函数

由 shmget创建或打开一个共享内存区后,通过调用shmat把它 附接到调用进程的地址空间。

1#include <sys/shm.h> 2void *shmat(int shmid, const void *shmaddr, int lag); 3 // 返回:若成功则为映射区的起始地址,若出错则为-1 4

其中shmid是由shmget返回的标识符。shmat的返回值是所指定的共享内存区在调用进程内的起始地址。确定这个地址的规则如下。

  • 如果shmaddr是一个空指针,那么系统替调用者选择地址。这是推荐的(也是可移植性最好的)方法。

  • 如果shmaddr一是一个非空指针,那么返回地址取决于调用者是否给fag参数指定了SHM_RND值:

  • 如果没有指定SHM_RND,那么相应的共享内存区附接到由shmaddr参数指定的地址;

    • 如果指定了SHM_RND,那么相应的共享内存区附接到由shmaddr参数指定的地址向下舍入一个SHMLBA常值。LBA代表“低端边界地址(lower boundary address)"。

默认情况下,只要调用进程具有某个共享内存区的读写权限,它附接该内存区后就能够同时读写该内存区。flag参数中也可以指定SHM_RDONLY值,它限定只读访问。

 

4、shmdt 函数

当一个进程完成某个共享内存区的使用时,它可调用shmdt 断接这个内存区。

1#include <sys/shm.h> 2int shmdt(const void *shmaddr); 3// 返回:若成功则为0,若出错则为-1 4

当一个进程终止时,它当前附接着的所有共享内存区都自动断接掉。

注意本函数调用并不删除所指定的共享内存区。这个删除工作通过以IPC_RMID命令调用shmctl完成。

 

5、shmctl 函数

shmctl提供了对一个共享内存区的多种操作。

1#include <sys/shm.h> 2int shnctl (int shmid, int cmd, struct shmid_ds *buf); 3// 返回:若成功则为0,若出错则为-1 4

该函数提供了三个命令。

  1. IPC_RMID    从系统中删除由shmid标识的共享内存区并拆除它。
  2. IPC_SET      给所指定的共享内存区设置其shmid_ds结构的以下三个成员:shm_perm.uid、shm_perm.gid 和                shm_perm.mode,它们的值来自buf参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。
  3. IPC_STAT    (通过buf参数)向调用者返回所指定共享内存区当前的shmid_ds结构。

 

6、简单的程序

6.1 shmget 程序,创建共享内存区

1// shmget.c 2#include "unpipc.h" 3int main(int argc, char **argv) 4{ 5 int c, id, oflag; 6 char *ptr; 7 size_t length; 8 9 oflag = SVSHM_MODE | IPC_CREAT; 10 while ( (c = Getopt(argc, argv, "e")) != -1) 11 { 12 switch (c) 13 { 14 case 'e': 15 oflag |= IPC_EXCL; 16 break; 17 } 18 } 19 if (optind != argc - 2) 20 err_quit("usage: shmget [ -e ] <pathname> <length>"); 21 length = atoi(argv[optind + 1]); 22 23 id = Shmget(Ftok(argv[optind], 0), length, oflag); 24 ptr = Shmat(id, NULL, 0); 25 26 exit(0); 27} 28

6.2 shmrmid 程序,删除一个共享内存区

1// shmrmid.c 2#include "unpipc.h" 3int main(int argc, char **argv) 4{ 5 int id; 6 if (argc != 2) 7 err_quit("usage: shmrmid <pathname>"); 8 9 id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE); 10 Shmctl(id, IPC_RMID, NULL); 11 12 exit(0); 13} 14

6.3 shmwrite 程序

1// shmwrite.c 2#include "unpipc.h" 3int main(int argc, char **argv) 4{ 5 int i, id; 6 struct shmid_ds buff; 7 unsigned char *ptr; 8 9 if (argc != 2) 10 err_quit("usage: shmwrite <pathname>"); 11 12 id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE); 13 ptr = Shmat(id, NULL, 0); 14 Shmctl(id, IPC_STAT, &buff); 15 16 /* set: ptr[0] = 0, ptr[1] = 1, etc. */ 17 for (i = 0; i < buff.shm_segsz; i++) 18 *ptr++ = i % 256; 19 20 exit(0); 21} 22

6.4 shmread 程序

1// shmread.c 2#include "unpipc.h" 3int main(int argc, char **argv) 4{ 5 int i, id; 6 struct shmid_ds buff; 7 unsigned char c, *ptr; 8 9 if (argc != 2) 10 err_quit("usage: shmread <pathname>"); 11 12 id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MODE); 13 ptr = Shmat(id, NULL, 0); 14 Shmctl(id, IPC_STAT, &buff); 15 16 /* check that ptr[0] = 0, ptr[1] = 1, etc. */ 17 for (i = 0; i < buff.shm_segsz; i++) 18 if ( (c = *ptr++) != (i % 256)) 19 err_ret("ptr[%d] = %d", i, c); 20 21 exit(0); 22} 23

6.5 执行

 

7、小结

System V共享内存区在概念上与Posix共享内存区类似。最常用的函数调用有以下几个。

  • shmget:获取一个标识符。
  • shmat:把一个共享内存区附接到调用进程的地址空间。
  • 以一个IPC_STAT命令调用shmctl:获取一个已存在共享内存区的大小。
  • 以一个IPC_RMID命令调用shnctl:删除一个共享内存区对象。

两者的差别之一是,Posix共享内存区对象的大小可在任何时刻通过调用ftruncate修改,而System V共享内存区对象的大小是在调用shnget创建时固定下来的。

 

 

 

代码交流 2021