运行机制
共享好端端的一词,近些年被玩坏了,共享单车,共享充电宝,共享办公室,共享雨伞… 甚至还有共享女朋友,真是人有多大胆,共享有多大产。但凡事太尽就容易恶心到人,自己也一度被 共享内存 恶心到了,一直不想碰它,拖到了现在才写。
共享内存的原理简单,目的是为了进程间通讯,方法是通过映射到同一块物理内存。它是一种稀缺资源由内核按资源池方式管理,数量有限,默认是 192
个,用资源ID唯一标识,用户进程需要时通过系统调用向内核申请共享内存大小,管理器从资源池中分配一个可用资源ID,并向物理内存申请对应的物理页框。
如何使用共享内存就涉及到了内存模块最重要的概念 映射,不清楚的可以翻看系列相关篇。有共享需求的进程在各自的进程空间中划出一个线性区映射到共享内存段,那如何找到这个共享内存段呢 ? 由系统调用提供操作接口,简单说是先通过参数key
创建共享资源ID(shmid
),再由shmid
来连接/删除/控制 共享内存。详见本篇末尾的4
个系统调用 Shm***
。
如何实现?
这是笔者看完内核共享内存模块画出来的图,尽量用一张图表达一个模块的内容,因为百文是在给源码注释的过程中产生的,所以会画出这种比较怪异的图,有代码,也有模型,姑且称之为 代码模型图:
图分 管理 和 映射使用 两部分解读。 为了精简,代码展示只留下骨干,删除了判断,检查的代码。
管理部分
- 初始化共享内存,共享内存是以资源池的方式管理的,上来就为全局变量
g_shmSegs
向内核堆空间申请了g_shmInfo.shmmni
个struct shmIDSource
#define SHM_MNI 192 //共享内存总数 默认192
// 共享内存模块设置信息
struct shminfo {
unsigned long shmmax, shmmin, shmmni, shmseg, shmall, __unused[4];
};
STATIC struct shminfo g_shmInfo = { //描述共享内存范围的全局变量
.shmmax = SHM_MAX,//共享内存单个上限 4096页 即 16M
.shmmin = SHM_MIN,//共享内存单个下限 1页 即:4K
.shmmni = SHM_MNI,//共享内存总数 默认192
.shmseg = SHM_SEG,//每个用户进程可以使用的最多的共享内存段的数目 128
.shmall = SHM_ALL,//系统范围内共享内存的总页数,4096页
};
//共享内存初始化
UINT32 ShmInit(VOID)
{
// ..
ret = LOS_MuxInit(&g_sysvShmMux, NULL);//初始化互斥
g_shmSegs = LOS_MemAlloc((VOID *)OS_SYS_MEM_ADDR, sizeof(struct shmIDSource) * g_shmInfo.shmmni);//分配shm段数组
(VOID)memset_s(g_shmSegs, (sizeof(struct shmIDSource) * g_shmInfo.shmmni),
0, (sizeof(struct shmIDSource) * g_shmInfo.shmmni));//数组清零
for (i = 0; i < g_shmInfo.shmmni; i++) {
g_shmSegs[i].status = SHM_SEG_FREE;//节点初始状态为空闲
g_shmSegs[i].ds.shm_perm.seq = i + 1;//struct ipc_perm shm_perm;系统为每一个IPC对象保存一个ipc_perm结构体,结构说明了IPC对象的权限和所有者
LOS_ListInit(&g_shmSegs[i].node);//初始化节点
}
g_shmUsedPageCount = 0;
return LOS_OK;
}
- 系列篇多次提过,每个功能模块都至少有一个核心结构体来支撑模块的运行,进程是
PCB
,任务是TCB
,而共享内存就是shmIDSource
struct shmIDSource {//共享内存描述符
struct shmid_ds ds; //是内核为每一个共享内存段维护的数据结构
UINT32 status; //状态 SHM_SEG_FREE ...
LOS_DL_LIST node; //节点,挂VmPage
#ifdef LOSCFG_SHELL
CHAR ownerName[OS_PCB_NAME_LEN];
#endif
};
首先shmid_ds是真正描述共享内存信息的结构体,记录了本次共享内存由谁创建,大小,用户/组,访问时间等等。
//每个共享内存段在内核中维护着一个内部结构shmid_ds
struct shmid_ds {
struct ipc_perm shm_perm;///< 操作许可,里面包含共享内存的用户ID、组ID等信息
size_t shm_segsz; ///< 共享内存段的大小,单位为字节
time_t shm_atime; ///< 最后一个进程访问共享内存的时间
time_t shm_dtime; ///< 最后一个进程离开共享内存的时间
time_t shm_ctime; ///< 创建时间
pid_t shm_cpid; ///< 创建共享内存的进程ID
pid_t shm_lpid; ///< 最后操作共享内存的进程ID
unsigned long shm_nattch; ///< 当前使用该共享内存段的进程数量
unsigned long __pad1; //保留扩展用
unsigned long __pad2;
};
//内核为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者
s