共享内存可以说是Linux下最快速、最有效的进程间通信。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新;反之,进程B也可以即时看到进程A对共享内存中数据的更新。
6.1 共享内存的概念
struct shmid_ds { struct ipc_perm shm_perm; /* 对应于共享内存的ipc_perm结构 */ size_t shm_segsz; /* 以字节表示的共享内存区域的大小 */ pid_t shm_lpid; /* 最近一次调用shmop函数的进行ID */ pid_t shm_cpid; /* 创建该共享内存的进程ID */ unsigned short shm_lkcnt; /* 共享内存区域被锁定的时间数 */ unsigned long shm_nattch; /* 当期使用该共享内存的进程数 */ time_t shm_atime; /* 最近一次附加操作的时间 */ time_t shm_dtime; /* 最近一次分离操作的时间 */ time_t shm_ctime; /* 最近一次修改的时间 */ };结果体shmid_ds会根据不同的系统内核版本而略有不同,并且在不同的系统中会对共享存储段的大小有限制,在应用时请查询相应的系统手册。
6.2 共享内存的相关操作
6.2.1 创建或打开共享内存
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmget (key_t key, size_t size, int shmflg);
- 当key为IPC_PRIVATE时,创建一个新的共享内存,此时参数shmflg的取值无效。
- 当key不为IPC_PRIVATE时,且shmflg设置了IPC_CREAT位,而没有设置IPC_EXCL位,则执行操作由key取值决定。如果key为内核中某个已存在的共享内存的键值,则执行打开这个键的操作;反之,则执行创建共享内存的操作。
- 当key不为IPC_PRIVATE时,且shmflg设置了IPC_CREAT位和IPC_EXCL位,则只执行创建共享内存的操作。参数key的取值应与内核中已存在的任何共享内存的键值都不相同,否则函数调用失败,返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开共享内存的函数就可以了(即将shmflg设置为IPC_CREAT,而不设置IPC_EXCL)。
另外,当调用shmget函数创建一个新的共享内存时,此共享内存的shmid_ds结构被初始化。ipc_perm中的各个域被设置为相应的值,shm_lpid、shm_nattch、shm_atime、shm_dtime被初始化为0,shm_ctime被设置为系统当前时间。
#include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #define BUFSZ 1024 int main() { int shm_id; shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666); if (shm_id < 0) { printf("shmget failed!\n"); exit(EXIT_FAILURE); } printf("create a shared memory segment successfully: %d\n", shm_id); system("ipcs -m"); exit(EXIT_SUCCESS); }
create a shared memory segment successfully: 3997706
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 294912 regan 600 524288 2 目标
0x00000000 3866625 regan 600 33554432 2 目标
0x00000000 3997706 regan 666 1024 0
$
6.2.2 附加
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>void *shmat (int shmid, const void *shmaddr, int shmflg);
6.2.3 分离
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmdt (const viod *shmaddr);
6.2.4 共享内存的控制
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmctl (int shmid, int cmd, struct shmid_ds *buf);
struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
取值 | 含义 |
IPC_STAT | 取shmid所指向内存共享段的shmid_ds结构,对参数buf指向的结构赋值 |
IPC_SET | 使用buf指向的结构对sh_mid段的相关结构赋值,只对以下几个域有作用,shm_perm.uid、shm_perm.gid及shm_perm.mode |
IPC_RMID | 删除shmid所指向的共享内存段,只有当shmid_ds结构的shm_nattch域为零时,才会真正执行删除命令,否则不会删除该段。注意此命令的请求规则与IPC_SET命令相同 |
SHM_LOCK | 锁定共享内存段在内存,此命令只能由超级用户请求 |
SHM_UNLOCK | 对共享内存段解锁,此命令只能由超级用户请求 |
#include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> typedef struct { char name[4]; int age; } people; int main(int argc, char **argv) { int shm_id, i; char tmp; people *map; if (argc != 2) { printf("Usage: atshm <identifier>"); exit(EXIT_FAILURE); } shm_id = atoi(argv[1]); map = (people *)shmat(shm_id, NULL, 0); tmp = 'a'; for (i = 0; i < 10; i++) { tmp += 1; memcpy((*(map + i)).name, &tmp, 1); (*(map + i)).age = 20 + i; } if (shmdt(map) == -1) { perror("detach error!\n"); } exit(EXIT_SUCCESS); }
#include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> typedef struct { char name[4]; int age; } people; int main(int argc, char **argv) { int shm_id, i; people *map; if (argc != 2) { printf("Usage: atshm <identifier>"); exit(EXIT_FAILURE); } shm_id = atoi(argv[1]); map = (people *)shmat(shm_id, NULL, 0); for (i = 0; i < 10; i++) { printf("name: %s\t", (*(map + i)).name); printf("age %d\n", (*(map + i)).age); } if (shmdt(map) == -1) perror("detach error!\n"); exit(EXIT_SUCCESS); }
$ ./shm_read 3997706
name: b age 20
name: c age 21
name: d age 22
name: e age 23
name: f age 24
name: g age 25
name: h age 26
name: i age 27
name: j age 28
name: k age 29
$
#define TEXT_SZ 2048 struct shared_use_st { int written_by_you; char text[TEXT_SZ]; };生产者程序shm_producer.c:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include "shm_com.h" int main() { int running = 1; void *shared_memory = (void *)0; struct shared_use_st *shared_stuff; int shmid; srand((unsigned int)getpid()); shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT); if (shmid == -1) { fprintf(stderr, "shmget failed\n"); exit(EXIT_FAILURE); } shared_memory = shmat(shmid, (void *)0, 0); if (shared_memory == (void *)-1) { fprintf(stderr, "shmat failed\n"); exit(EXIT_FAILURE); } printf("memory attached at %p\n", shared_memory); shared_stuff = (struct shared_use_st *)shared_memory; shared_stuff->written_by_you = 0; while (running) { if (shared_stuff->written_by_you) { printf("You wrote: %s", shared_stuff->text); sleep(rand() % 4); shared_stuff->written_by_you = 0; if (strncmp(shared_stuff->text, "end", 3) == 0) running = 0; } } if (shmdt(shared_memory) == -1) { fprintf(stderr, "shmdt failed\n"); exit(EXIT_FAILURE); } if (shmctl(shmid, IPC_RMID, 0) == -1) { fprintf(stderr, "shmctl(IPC_RMID) failed\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }消费者程序shm_customer.c:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include "shm_com.h" int main() { int running = 1; void *shared_memory = (void *)0; struct shared_use_st *shared_stuff; char buffer[BUFSIZ]; int shmid; shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT); if (shmid == -1) { fprintf(stderr, "shmget failed\n"); exit(EXIT_FAILURE); } shared_memory = shmat(shmid, (void *)0, 0); if (shared_memory == (void *)-1) { fprintf(stderr, "shmat failed\n"); exit(EXIT_FAILURE); } printf("memory attached at %p\n", shared_memory); shared_stuff = (struct shared_use_st *)shared_memory; while (running) { while (shared_stuff->written_by_you == 1) { sleep(1); printf("waiting for client...\n"); } printf("Enter some text: "); fgets(buffer, BUFSIZ, stdin); strncpy(shared_stuff->text, buffer, TEXT_SZ); shared_stuff->written_by_you = 1; if (strncmp(buffer, "end", 3) == 0) running = 0; } if (shmdt (shared_memory) == -1) { fprintf(stderr, "shmdt failed\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
编译并运行:
$ ./shm_producer &
[1] 10229
memory attached at 0x7fc2c3060000
$ ./shm_customer
memory attached at 0x7fdaf18a0000
Enter some text: hello
You wrote: hello
waiting for client...
Enter some text: Welcome to my hometown!
You wrote: Welcome to my hometown!
waiting for client...
Enter some text: end
You wrote: end
$
shm_producer创建共享内存段,然后将它连接到自己的地址空间中,我们在共享内存的开始处使用了一个结构shared_use_st,该结构中有个标志written_by_you,当共享内存中有数据写入时就设置这个标志。这个标志被设置时,程序就从共享内存中读取文本并打印,然后清除这个标志表示已经读完数据,再用一个end特殊字符串来退出循环,接下来程序分离共享内存段并删除它。
shm_customer使用相同的键来取得并连接同一个共享内存段。然后它提示用户输入文本。如果标志written_by_you被设置,程序就知道客户进程还未读完上一次的数据,因此就继续等待。当其它进程清除了这个标志后,程序写入新数据并设置该标志。它也是使用end来终止并分离共享内存段。
注意,这里的同步标志(written_by_you)非常简陋,它包括一个非常缺乏效率的忙等待(不停地循环),实际编程中应该使用信号量或通过传递消息、生成信号的方法来提供应用程序读、写部分之间的一种更有效率的同步机制。
整理自 《Linux程序设计第4版》、《Linux C编程从初学到精通》。