|  | 
 
| 8.5  共享内存 
 8.5.1  共享内存概述
 
 可以说,共享内存是一种最为高效的进程间通信方式。因为进程可以直接读写内存,不需要任何数据的复制。为了在多个进程间交换信息,内核专门留出了一块内存区。这段内存区可以由需要访问的进程将其映射到自己的私有地址空间。因此,进程就可以直接读写这一内存区而不需要进行数据的复制,从而大大提高了效率。当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等(请参考本章的共享内存实验)。其原理示意图如图8.8所示。
 
 
 
         图8.8  共享内存原理示意图 
 8.5.2  共享内存的应用
 
 1.函数说明
 
 共享内存的实现分为两个步骤,第一步是创建共享内存,这里用到的函数是shmget(),也就是从内存中获得一段共享内存区域,第二步映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是shmat()。到这里,就可以使用这段共享内存了,也就是可以使用不带缓冲的I/O读写命令对其进行操作。除此之外,当然还有撤销映射的操作,其函数为shmdt()。这里就主要介绍这3个函数。
 
 2.函数格式
 
 表8.20列举了shmget()函数的语法要点。
 表8.20 shmget()函数语法要点
 
 | 所需头文件 
 | #include <sys/types.h> #include <sys/ipc.h>
 #include <sys/shm.h>
 
 |  | 函数原型 
 | int shmget(key_t key, int size, int shmflg) 
 |  | 函数传入值 
 | key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中有个特殊值IPC_PRIVATE。它用于创建当前进程的私有共享内存 
 |  | size:共享内存区大小 
 |  | shmflg:同open()函数的权限位,也可以用八进制表示法 
 |  | 函数返回值 
 | 成功:共享内存段标识符 
 |  | 出错:-1 
 | 
 表8.21列举了shmat()函数的语法要点。
 表8.21 shmat()函数语法要点
 
 | 所需头文件 
 | #include <sys/types.h> #include <sys/ipc.h>
 #include <sys/shm.h>
 
 |  | 函数原型 
 | char *shmat(int shmid, const void *shmaddr, int shmflg) 
 |  | 函数传入值 
 | shmid:要映射的共享内存区标识符 
 |  | shmaddr:将共享内存映射到指定地址(若为0则表示系统自动分配地址并把该段共享内存映射到调用进程的地址空间) 
 |  | shmflg 
 | SHM_RDONLY:共享内存只读 
 |  | 默认0:共享内存可读写 
 |  | 函数返回值 
 | 成功:被映射的段地址 
 |  | 出错:-1 
 | 
 表8.22列举了shmdt()函数的语法要点。
 表8.22 shmdt()函数语法要点
 
 | 所需头文件 
 | #include <sys/types.h> #include <sys/ipc.h>
 #include <sys/shm.h>
 
 |  | 函数原型 
 | int shmdt(const void *shmaddr) 
 |  | 函数传入值 
 | shmaddr:被映射的共享内存段地址 
 |  | 函数返回值 
 | 成功:0 
 |  | 出错:-1 
 | 
 3.使用实例
 
 该实例说明如何使用基本的共享内存函数。首先是创建一个共享内存区(采用的共享内存的键值为IPC_PRIVATE,是因为本实例中创建的共享内存是父子进程之间的共用部分),之后创建子进程,在父子两个进程中将共享内存分别映射到各自的进程地址空间之中。
 
 父进程先等待用户输入,然后将用户输入的字符串写入到共享内存,之后往共享内存的头部写入“WROTE”字符串表示父进程已成功写入数据。子进程一直等到共享内存的头部字符串为“WROTE”,然后将共享内存的有效数据(在父进程中用户输入的字符串)在屏幕上打印。父子两个进程在完成以上工作之后,分别解除与共享内存的映射关系。
 
 最后在子进程中删除共享内存。因为共享内存自身并不提供同步机制,所以应该额外实现不同进程之间的同步(例如:信号量)。为了简单起见,在本实例中用标志字符串来实现非常简单的父子进程之间的同步。
 
 这里要介绍的一个命令是ipcs,这是用于报告进程间通信机制状态的命令。它可以查看共享内存、消息队列等各种进程间通信机制的情况,这里使用了system()函数用于调用shell命令“ipcs”。程序源代码如下所示:
 
 /* shmem.c */
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/shm.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #define BUFFER_SIZE 2048
 
 int main()
 {
 pid_t pid;
 int shmid;
 char *shm_addr;
 char flag[] = "WROTE";
 char *buff;
 
 /* 创建共享内存 */
 if ((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0)
 {
 perror("shmget");
 exit(1);
 }
 else
 {
 printf("Create shared-memory: %d\n",shmid);
 }
 
 /* 显示共享内存情况 */
 system("ipcs -m");
 
 pid = fork();
 if (pid == -1)
 {
 perror("fork");
 exit(1);
 }
 else if (pid == 0) /* 子进程处理 */
 {
 /*映射共享内存*/
 if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
 {
 perror("Child: shmat");
 exit(1);
 }
 else
 {
 printf("Child: Attach shared-memory: %p\n", shm_addr);
 }
 system("ipcs -m");
 
 /* 通过检查在共享内存的头部是否标志字符串"WROTE"来确认
 父进程已经向共享内存写入有效数据 */
 while (strncmp(shm_addr, flag, strlen(flag)))
 {
 printf("Child: Wait for enable data...\n");
 sleep(5);
 }
 
 /* 获取共享内存的有效数据并显示 */
 strcpy(buff, shm_addr + strlen(flag));
 printf("Child: Shared-memory :%s\n", buff);
 
 /* 解除共享内存映射 */
 if ((shmdt(shm_addr)) < 0)
 {
 perror("shmdt");
 exit(1);
 }
 else
 {
 printf("Child: Deattach shared-memory\n");
 }
 system("ipcs -m");
 
 /* 删除共享内存 */
 if (shmctl(shmid, IPC_RMID, NULL) == -1)
 {
 perror("Child: shmctl(IPC_RMID)\n");
 exit(1);
 }
 else
 {
 printf("Delete shared-memory\n");
 }
 
 system("ipcs -m");
 }
 else /* 父进程处理 */
 {
 /*映射共享内存*/
 if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
 {
 perror("Parent: shmat");
 exit(1);
 }
 else
 {
 printf("Parent: Attach shared-memory: %p\n", shm_addr);
 }
 
 sleep(1);
 printf("\nInput some string:\n");
 fgets(buff, BUFFER_SIZE, stdin);
 strncpy(shm_addr + strlen(flag), buff, strlen(buff));
 strncpy(shm_addr, flag, strlen(flag));
 
 /* 解除共享内存映射 */
 if ((shmdt(shm_addr)) < 0)
 {
 perror("Parent: shmdt");
 exit(1);
 }
 else
 {
 printf("Parent: Deattach shared-memory\n");
 }
 system("ipcs -m");
 
 waitpid(pid, NULL, 0);
 printf("Finished\n");
 }
 
 exit(0);
 }
 
 下面是运行结果。从该结果可以看出,nattch的值随着共享内存状态的变化而变化,共享内存的值根据不同的系统会有所不同。
 
 $ ./shmem
 Create shared-memory: 753665
 /* 在刚创建共享内存时(尚未有任何地址映射)共享内存的情况 */
 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 0x00000000 753665     david     666        2048       0
 
 Child: Attach shared-memory: 0xb7f59000 /* 共享内存的映射地址 */
 Parent: Attach shared-memory: 0xb7f59000
 /* 在父子进程中进行共享内存的地址映射之后共享内存的情况*/
 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 0x00000000 753665     david     666        2048       2
 
 Child: Wait for enable data...
 
 Input some string:
 Hello /* 用户输入字符串“Hello” */
 Parent: Deattach shared-memory
 /* 在父进程中解除共享内存的映射关系之后共享内存的情况 */
 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 0x00000000 753665     david     666        2048       1
 /*在子进程中读取共享内存的有效数据并打印*/
 Child: Shared-memory :hello
 
 Child: Deattach shared-memory
 /* 在子进程中解除共享内存的映射关系之后共享内存的情况 */
 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 0x00000000 753665     david     666        2048       0
 
 Delete shared-memory
 /* 在删除共享内存之后共享内存的情况 */
 ------ Shared Memory Segments --------
 key        shmid      owner      perms      bytes      nattch     status
 
 Finished
 | 
 |