System V共享内存
- 共享内存的特点:
- 共享内存允许多个进程共享一块内存段
- 共享内存(一块物理内存)会被映射到进程地址空间,成为虚拟内存的一部分
- 该IPC机制无需内核的介入
- 相比于其他IPC机制,该技术没有内核空间和用户空间的数据交换,速度更快
1.1 创建或者打开一个共享内存段
-
函数功能:创建一个共享内存段或者获取一个既有共享内存段的标识符,新创建的共享内存段会被初始化为0
-
返回值:成功返回该共享内存段的标识符,失败返回-1
-
函数原型:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
-
参数:
- key:用来生成标识符的值,通常是
IPC_PRIVATE
或者ftok()
来生成一个key
值 - size:需要分配的共享内存的大小,单位
Bytes
,但是实际上会被分配到系统分页大小的整倍数 - shmflg:施加于共享内存段的权限或者对既有共享内存段的权限检查
- IPC_CREATE:如果不存在指定key值得共享内存段,机会创建一个共享内存
- IPC_EXCL:如果同时指定了IPC_CREATE标记,指定key值的共享内存还存在的话就会返回EEXIST错误
- 等
- key:用来生成标识符的值,通常是
1.2 使用共享内存
-
函数功能:该系统调用将
shmid
标识的共享内存段映射到进程的虚拟地址空间。映射的前提,进程拥有该共享内存的读写权限 -
返回值:成功返回一个虚拟地址表示共享内存段的开始位置,错误返回一个指针-1
-
函数原型:
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
-
参数:
-
shmid:共享内存段的标识符
-
shmaddr:
- 如果为NULL,该共享内存段会被内核分配到一个合适的地址处,最优映射的方法
- 如果不为NULL:非最优映射的方法
- 没有设置SHM_RND:段会被映射到一个shmaddr指定的地址处,必须是系统分页的整数倍,否则发生EINVAL错误
- 设置了SHM_RND:段会被映射到shmaddr指定的地址舍入为最近的SHMLBA的整数倍,SHMLBA是一个常量,他为系统分页的整数倍
- 非最优映射的原因:
- 降低了系统的可移植性能,其它系统中shmaddr可能不再是合法地址
- 该地址在可能被使用或者已经被映射
-
shmflg: 位掩码之间用 | 连接
-
-
函数功能:当不在使用该共享内存段的时候,分离共享内存和虚拟地址
-
返回值:成功返回0,失败返回-1
-
函数原型:
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
-
参数:
- shmaddr:
shmat()
返回的一个值
- shmaddr:
-
注意:
fork()
创建的子进程会继承父进程映射的共享内存段exec
之后所有映射的共享内存都会被分离,进程结束之后也会被分离
1.4 共享内存控制操作
-
函数功能:在
shmid
标识的共享内存段上面执行一组操作 -
返回值:成功返回0,失败返回-1
-
函数原型:
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-
函数参数:
- shmid:既有共享内存段的标识
- cmd:控制操作
- IPC_RMID:标记一个共享内存段及其关联数据结构
shmid_ds
以便删除- 如果没有进程映射该共享内存段,那么立即删除该共享内存段,否则等待所有进程与该共享内存段分离之后在进行删除
- 进程在映射一块共享内存之后,立即使用IPC_RMID标记该段,在进程与之分离之后,可以快速干净的删除该共享内存段
- 在被IPC_RMID标记之后,如果因为还有进程与之关联没有被删除,那么此时也可以在继续被其他进程映射,但是这种做法我们推荐
- IPC_STAT:将该共享内存的一个shmid_ds关联数据结构副本放到buf指向的缓冲区
- IPC_SET:使用buf指向的缓冲区的值来更新shmid_ds关联数据结构中相应指定的值
- IPC_RMID:标记一个共享内存段及其关联数据结构
1.5 共享内存关联数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
};
- shm_segsz:该字段会被设置为共享内存段所需要的字节数,但因为共享内存是按照分页来进行分配的,所以实际大小可能不会等于该值
- shm_nattch:映射到该共享内存的进程数量,一次
shmat()
调用会加1,一次shmdt()
调用会减1
1.6 共享内存在虚拟进程空间中的位置
1.7 共享内存中存储指针
-
在共享内存中存储段其它位置的指针的时候,不能直接存放指针值,因为该共享内存如果被其它进程附加之后,可能会被映射到其它地址处,存储的指针就不再准确了
- 比如在进程A中,
*p = target
,在另外一个进程中,附加该共享内存段之后,那么*p
存储的指针值已经没有任何意义了,所以正确的做法是让*P
存储一个偏移量,只要baseaddr
每次都是起始地址,那么*P = target - baseaddr
的值,在哪个进程中都会表示,target
这个位置的指针