为了能快速掌握 System V IPC,本篇以最少量的文字让初学者先初步掌握共享内存,后续文章,再慢慢深入。
共享内存只是 System V IPC 中的一种,前面的进程通信总览中我们讲过 System V IPC 包括了共享内存、消息队列和信号量。
1. 共享内存
为了能够快速上手共享内存,下面直接介绍一下大致的方法:
- 根据已知的键(key) 使用 get 函数获取或者创建内核对象,并且返回内核对象的 id 号。
- 根据 id 号获取内存地址。
- 向内存读写数据。
内核对象,可以理解为位于内核空间中的某种类型的结构体。
先简单介绍一下,键值必须是我们事先约定好的,通过 get 函数获取内核对象 id 号这种方式我们以前应该见到过类似的,就好像是一对 <key, value>
。这里的 get 函数,通常都是以 get 为后缀的函数名,在共享内存里,对应的这个 get 函数名字为 shmget,其中 shm 的意思是 share memory。
根据 key 获取的 id 号,它唯一的标识了内核中的一个对象。此时,我们可以根据此 id 获取内核对象的相关信号。对于共享内存,使用此 id 可以将内核对象中的内存挂接到用户空间的线性地址。
接下来看实例。
2. 实例
该实例有两个程序,程序 a 创建一个共享内存的内核对象,获取内存后,向其写入数据。程序 b 获取此内核对象后挂接内存,读取数据然后打印。
详细过程见注释部分,请认真阅读。
2.1 程序 a
// a.c
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
int main() {
// shmget 函数通过事先约定的键值 0x8888 创建(IPC_CREAT)一个内核对象并返回其 id。如果 0x8888 对应的内核对象存在,就失败(IPC_EXCL)。0664 是该内核对象的权限。第二个参数表示创建的共享内存大小。
int id = shmget(0x8888, 4096, IPC_CREAT | IPC_EXCL | 0664);
// 如果失败就退出
if (id < 0) {
perror("shmget");
return -1;
}
// 打印获取到的内核对象 id
printf("id = %d\n", id);
// 使用函数 shmat (share memory attach) 将内核对象维护的内存挂接到指定线性地址(第二个参数)
// 如果第二个参数为 0,则系统帮你选择一个合适的线性地址。
char *buf = shmat(id, NULL, 0);
// 如果挂接失败就退出
if (buf == (char*)-1) {
perror("shmat");
return -1;
}
// 将数据拷贝到共享内存
strcpy(buf, "hello, share memory!\n");
// 使用 shmdt(share memory detach) 将挂接的内存卸载
if (shmdt(buf) < 0) {
perror("shmdt");
return -1;
}
return 0;
}
2.2 程序 b
// b.c
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
int main() {
// 根据事先约定后的键值获取内核对象 id,这时候后面两个参数都可以为 0.
int id = shmget(0x8888, 0, 0);
if (id < 0) {
perror("shmget");
return -1;
}
printf("id = %d\n", id);
// 挂接内存
char *buf = shmat(id, NULL, 0);
if (buf == (char*)-1) {
perror("shmat");
return -1;
}
// 打印数据
printf("%s", buf);
// 卸载
if (shmdt(buf) < 0) {
perror("shmdt");
return -1;
}
// 删除内核对象
if (shmctl(id, IPC_RMID, NULL) < 0) {
perror("shmctl");
return -1;
}
return 0;
}
2.3 编译
$ gcc a.c -o a
$ gcc b.c -o b
2.4 运行
- 运行程序 a
$ ./a
如果你的程序运行成功,会在屏幕打印内核对象的 id 号。另外通过命令ipcs -m
可以看到刚刚创建的共享内存内核对象。
图1 刚刚通过程序 a 创建的内核对象
- 运行程序 b
$ ./b
屏幕打印如下结果:
图2 程序 b 的运行结果
3. 总结
本篇初步展示共享内存的使用方法。同学需要理解向共享内存中写稿数据后,即使进程结束了,共享内存中的数据都还在,因为这些数据是保存在内核空间中的。后面将详细讲解 System V 的相关概念。
本篇需要掌握:
- 初步理解如何获取内核对象
- 知道进程间通信大致的实现原理(利用内核空间)
- 初步了解 ipcs 命令,可以使用 ipcs -h 命令了解更多使用方法
- 知道 System V IPC 的三种方式