linux笔记—共享内存
共享内存
- 即多进程之间直接对读写同一段内存.相比较管道及消息队列,显而易见的好处是速度快,所有的IPC方法中效率最高.但共享内存并未提供同步机制,需要自行实现.
原理
顾名思义,共享内存就是说两个不同的进程A、B可以共同享有一块内存区域
整个处理流程是
- 进程A第一次访问该页中的数据时, 生成一个缺页中断. 内核读入此页到内存并更新页表使之指向此页.
- 进程B访问同一页面而出现缺页中断,内核只将进程B的页表登记项指向次页即可.
- 进程A.B 即可访问同一段内存.
使用
shmget函数
shmget被用来开辟/初始化一段共享内存.其他进程使用相同的key 通过 shgat 获取同一段共享内存.,只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符.函数原型
1
int shmget(key_t key, size_t size, int shmflg);
key : 与信号量的semget函数一样,使用共享内存key(非0整数)shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于多进程的共享.调用失败返回-1.
size : 开辟的共享内存大小(字节).
shmflg : 权限控制,与IPC_CREAT做或操作,控制其他进程对共享内存权限. 0644即代表其他进程只有读的权限.shmat函数
用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间函数原型
1
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id: 由shmget函数返回的共享内存标识.
shm_addr: 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址.
shm_flg: 标志位,通常为0.shmdt函数
将共享内存从当前进程中分离,使该共享内存对当前进程不再可用.函数原型:
1
int shmdt(const void *shmaddr);
shmaddr: shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.
shmctl函数
控制共享内存.函数原型
1
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shm_id: shmget函数返回的共享内存标识符
command: 要采取的操作,以下三个值 :
- IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值.
- IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
- IPC_RMID:删除共享内存段
buf: 结构指针,指向共享内存模式和访问权限的结构.
1
2
3
4
5
6
7shmid_ds结构至少包括以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
系统限制及规避
大小
linux默认限制共享内存总大小由 SHMMAX 值确定.默认值未32MB
读取
1
cat /proc/sys/kernel/shmmax
当超过系统限制时 提示
unable to attach to shared memory
.规避
直接修改/proc.无需重启
1
echo "2147483648" > /proc/sys/kernel/shmmax
可以将命令写入启动脚本 /etc/rc.local中.保证重启生效
使用 sysctl 命令修改
1
sysctl -w kernel.shmmax=2147483648
可以将此参数插入到 /etc/sysctl.conf 启动文件中
1
echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf
永久生效.
数量
与大小类似的,共享内存创建的总数量由 SHMMNI 参数确定.
读取
1
cat /proc/sys/kernel/shmmni
默认情况下是 4096.
一般不需要修改.
同一进程多次shmat
shmat即挂载共享内存到进程的进程空间.
当同一进程多次调用shmat 挂载同一共享内存时,shamat每次返回的地址都不同,相当于在进程的线性空间中存在多个实际指向同一块共享内存.直到最后进程线性空间消耗殆尽.
解决:
需要在挂载共享内存前,判断申请的共享内存指针是否为空,为 NULL ,则第一次加载此共享内存.否则不再重复加载.1
2
3
4
5
6void* p = NULL;
/*其他操作*/
if (NULL == p)
{
p = shmat(shmid,p,0666);
}
多个进程相同key多次创建共享内存
共享内存创建有大小之分.key相同情况下,容量小的共享内存会获得之前创建的大的共享内存的内容.有可能导致之前创建共享内存的进程崩溃.
解决
- 在shmage使用IPC_EXCL标记.现行判断共享内存是否已创建.如果已创建则挂载,没有创建返回失败后,再创建.
1
2
3
4
5
6Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL);
if (-1 != shmid)
{
/*错误处理*/
Shmid = Shmget(key, size,IPC_CREATE);
}- 不通过key 标记同一块共享内存.
shmget使用 kry = IPC_PRIVATE ,linux会忽略key值,直接新建一块共享内存.返回标识,通过管道/文件方式共享给其他进程使用.
共享内存删除
调用shmctl 删除共享内存后,共享内存并不会立刻被系统清理.
首先共享内存的 shmid_ds结构中的 shm_nattch 减一.该共享内存从调用shmctl的进程剥离.但 shm_nattch 不为 0 的情况下,即仍然有别进程连接的情况下.共享内存并不会立刻清除. 只有在 shm_nattch 为 0 ,没有任何进程连接的情况下,系统才会清理 这段共享内存.
该段共享内存被任何连接的进程执行 shmctl 删除操作后, 新的进程将无法连接到该段共享内存.
与之对应的 Shmdt ,只是将共享内存由 调用的进程空间剥离.不会影响其他进程连接到该段共享内存.
应用
多进程共享数据.以链表为例.
有两种方式:
- 开辟一段共享内存,之后存入整个链表.将共享内存连接到各个进程.
- 优点:
- 对原有代码改动少.
- 缺点:
- 封装链表操作时,各个进程头节点偏移量不同,不能简单的通过
*p->next
访问. - 有共享内存大小限制.
- 封装链表操作时,各个进程头节点偏移量不同,不能简单的通过
- 优点:
- 每一个链表节点对应一个共享内存.开辟同等数量的共享内存.
- 优点
- 可以随用随创,相对节省内存.
- 链表节点next 存储相对链表头节点偏移即可,访问操作相对单一共享内存容易.
- 缺点
- 受限于 linux 默认 4096的总数量限制.且总数不易更改.
- 开辟新链表节点相对复杂.
- 优点
- 开辟一段共享内存,之后存入整个链表.将共享内存连接到各个进程.
实际使用中第一种较为常见.
同步
- 与共享内存类似的信号量,也是跨进程的.信号量是比较一种比较方便配合共享内存同步的方式.