Linux 内核中共享内存的实现(基于 2.6.12)
核心文件与路径
ipc/shm.c: System V 共享内存系统调用与核心逻辑include/linux/shm.h: 结构体与常量定义(shmid_kernel在#ifdef __KERNEL__块内, 仅内核可见)ipc/util.c: IPC 公共 ID/权限/锁管理mm/: 通用内存管理接口(页分配、映射)
核心数据结构
shmid_kernel(位于 include/linux/shm.h)
// include/linux/shm.h (在 #ifdef __KERNEL__ 块内, 仅内核可见)
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm; // IPC 权限/键值/锁
struct file *shm_file; // 关联的 shmem 文件, 承载页缓存
int id; // 内部 ID (用于 IPC 表索引)
unsigned long shm_nattch; // 当前附加次数(映射该段的进程数)
unsigned long shm_segsz; // 段大小(字节)
time_t shm_atim; // 最后 attach 时间
time_t shm_dtim; // 最后 detach 时间
time_t shm_ctim; // 最后变更时间
pid_t shm_cprid; // 创建者 PID
pid_t shm_lprid; // 最后操作 PID
struct user_struct *mlock_user; // 锁定内存的用户结构(用于 SHM_LOCK)
};
shmem_inode_info
- 来自 tmpfs/shmem,作为
shm_file的支撑,负责页缓存与换入换出(除非 SHM_LOCK 锁定)。
系统调用路径(x86_64 类推)
sys_shmget→ipc/shm.c:sys_shmget→newseg()创建段(分配 shmid_kernel + shmem 文件)并通过ipc_addid安装到 IPC 表sys_shmat→ipc/shm.c:sys_shmat→do_shmat()检查权限/地址 → 通过do_mmap_pgoff()把shm_file映射到进程用户空间sys_shmdt→ipc/shm.c:sys_shmdt→do_shmdt()卸载映射,递减shm_nattchsys_shmctl→ipc/shm.c:sys_shmctl处理IPC_RMID/IPC_SET/IPC_STAT/SHM_LOCK/SHM_UNLOCK
关键流程
创建段(shmget → newseg)
- 校验大小不超过
shm_ctlmax,对齐页(向上按页或 SHMLBA)。 - 分配
shmid_kernel(ipc_rcu_alloc),初始化shm_perm、大小、时间戳、计数。 - 创建支撑文件:调用
shmem_file_setup()创建匿名 shmem 文件,关联到shm_file,用于后续映射与页缓存管理。 - 通过
ipc_addid(&shm_ids, …)安装到 IPC ID 表,返回 shmid(序号+序列)。
简要实现(ipc/shm.c:sys_shmget/newseg)
asmlinkage long sys_shmget(key_t key, size_t size, int shmflg)
{
struct shmid_kernel *shp;
int err, id;
// 尺寸检查: 不得超过 shm_ctlmax
if (size > shm_ctlmax)
return -EINVAL;
// 对齐到页面/SHMLBA (VIPT/TLB 需求)
size = ALIGN(size, SHMLBA);
// 如果指定 IPC_PRIVATE 或不存在且 IPC_CREAT,则创建
id = newseg(key, shmflg, size);
return id; // 返回 shmid (序号+序列)
}
static int newseg(key_t key, int shmflg, size_t size)
{
struct shmid_kernel *shp;
int id, err;
// 分配内核描述符
shp = ipc_rcu_alloc(sizeof(*shp));
if (!shp)
return -ENOMEM;
shp->shm_perm.key = key;
shp->shm_perm.mode = (shmflg & S_IRWXUGO);
shp->shm_perm.security = NULL;
shp->shm_segsz = size;
shp->shm_atim = shp->shm_dtim = 0; // 初始未映射/未解绑
shp->shm_ctim = get_seconds(); // 创建时间
shp->shm_cprid = shp->shm_lprid = current->pid;
shp->shm_nattch = 0; // 尚无进程映射
// 创建支撑文件 (基于 shmem)
shp->shm_file = shmem_file_setup("SYSV0000", size, 0);
if (IS_ERR(shp->shm_file)) {
err = PTR_ERR(shp->shm_file);
ipc_rcu_putref(shp);
return err;
}
// 安装到 IPC ID 表
id = ipc_addid(&shm_ids, &shp->shm_perm, shmmni);
if (id == -1) {
fput(shp->shm_file);
ipc_rcu_putref(shp);
return -ENOSPC;
}
return shm_buildid(id, shp->shm_perm.seq); // 返回 shmid
}
映射(shmat → do_shmat)
- 权限检查与 ID 校验 (
ipc_lock/ipc_checkid/ipcperms)。 - 解析
shmaddr/shmflg:SHM_RDONLY只读映射SHM_RND地址向下按SHMLBA对齐- 若指定地址且违反对齐/冲突,返回
EINVAL
- 增加
shm_nattch,更新时间shm_atim、shm_lprid。 - 通过
do_mmap_pgoff()将shm_file映射到进程用户空间(用户态虚拟地址因进程不同而异,但指向同一物理页集合)。
简要实现(ipc/shm.c:sys_shmat/do_shmat)
asmlinkage long sys_shmat(int shmid, char __user *shmaddr, int shmflg)
{
unsigned long addr;
int err;
// do_shmat 负责权限/地址检查与实际映射
err = do_shmat(shmid, shmaddr, shmflg, &addr);
if (err)
return err;
return addr; // 用户态虚拟地址
}
static int do_shmat(int shmid, char __user *shmaddr, int shmflg,
unsigned long *raddr)
{
struct shmid_kernel *shp;
struct file *file;
unsigned long addr, flags;
// 获取并锁定段
shp = shm_lock(shmid);
if (!shp)
return -EINVAL;
if (ipc_checkid(&shp->shm_perm, shmid)) {
shm_unlock(shmid);
return -EIDRM;
}
if (ipcperms(&shp->shm_perm, (shmflg & SHM_RDONLY) ? S_IRUGO : S_IRUGO|S_IWUGO)) {
shm_unlock(shmid);
return -EACCES;
}
file = shp->shm_file;
get_file(file); // 引用计数+1, 防止映射期间文件被释放
// 处理地址/对齐
addr = (unsigned long)shmaddr;
if (shmflg & SHM_RND)
addr &= ~(SHMLBA - 1);
flags = MAP_SHARED;
if (shmflg & SHM_RDONLY)
flags |= PROT_READ;
else
flags |= PROT_READ | PROT_WRITE;
// 实际映射: do_mmap_pgoff 返回用户态虚拟地址
addr = do_mmap_pgoff(file, addr, shp->shm_segsz, flags, 0, 0);
if (IS_ERR_VALUE(addr)) {
fput(file);
shm_unlock(shmid);
return addr;
}
// 更新状态
shp->shm_nattch++; // 映射计数+1
shp->shm_atim = get_seconds(); // 记录最后 attach 时间
shp->shm_lprid = current->pid; // 记录最后操作 PID
shm_unlock(shmid);
*raddr = addr;
return 0;
}
解除映射(shmdt → do_shmdt)
- 查找并拆除进程中对应的 VMA 区域(按基地址与长度)。
- 递减
shm_nattch,更新时间shm_dtim、shm_lprid。 - 如果段已被标记删除且
shm_nattch==0,释放资源。
简要实现(ipc/shm.c:sys_shmdt/do_shmdt)
asmlinkage long sys_shmdt(char __user *shmaddr)
{
return do_shmdt((unsigned long)shmaddr);
}
static int do_shmdt(unsigned long addr)
{
struct shmid_kernel *shp;
int retval;
// 通过 VMA 找到对应的 shm 段, 执行 unmap
retval = shm_unmap(addr); // 内部执行 find_vma + do_munmap
if (retval)
return retval;
// shm_unmap 中会递减 shm_nattch,并在必要时触发销毁
return 0;
}
控制(shmctl)
IPC_RMID: 标记段删除,若shm_nattch==0立即释放(shm_destroy),否则等待最后一个 detach 后释放。IPC_SET: 更新权限和shm_perm.uid/gid/mode,更新时间戳。IPC_STAT: 填充shmid_ds返回用户态。SHM_LOCK/SHM_UNLOCK: 锁定/解锁页以防换出(需要 CAP_IPC_LOCK),内部通过shm_lock/shm_unlock配合 shmem。
简要实现(ipc/shm.c:sys_shmctl,聚焦 IPC_RMID)
asmlinkage long sys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf)
{
struct shmid_kernel *shp;
int err;
down(&shm_ids.sem); // IPC 表级锁
shp = shm_lock(shmid); // 对象锁 + 获取
if (!shp) {
err = -EINVAL;
goto out_up;
}
if (ipc_checkid(&shp->shm_perm, shmid)) {
err = -EIDRM;
goto out_unlock_up;
}
switch (cmd) {
case IPC_RMID:
// 从 IPC 表移除,标记删除
shm_destroy(shp);
err = 0;
break;
case IPC_SET:
// 更新权限/属主/模式
err = shmctl_down(shp, cmd, buf);
break;
case IPC_STAT:
// 拷贝状态给用户
err = shmctl_stat(shp, buf);
break;
case SHM_LOCK:
case SHM_UNLOCK:
err = shmctl_do_lock(shp, cmd);
break;
default:
err = -EINVAL;
break;
}
out_unlock_up:
shm_unlock(shmid);
out_up:
up(&shm_ids.sem);
return err;
}
释放与回收
shm_destroy:从 IPC 表移除 (ipc_rmid),关闭shm_file,释放shmid_kernel。- 如果段被 IPC_RMID 标记,最后一个
shmdt完成后触发真正释放。
删除与回收(ipc/shm.c:shm_destroy)
static void shm_destroy(struct shmid_kernel *shp)
{
// 从 IPC ID 表移除并标记删除
ipc_rmid(&shm_ids, &shp->shm_perm);
// 关闭支撑文件,释放引用
fput(shp->shm_file);
// 释放描述符 (RCU)
ipc_rcu_putref(shp);
}
并发与锁
- IPC 表锁:
shm_ids.sem序列化添加/删除。 - 对象锁:
ipc_lock保护单个段的元数据(权限、计数、状态)。 - RCU/引用计数:
ipc_rcu_alloc/putref管理对象生命周期;shm_nattch计数配合删除判定。
相关限制(2.6.12)
shmmax:单段最大字节数 (/proc/sys/kernel/shmmax)shmmni:系统最大段数 (/proc/sys/kernel/shmmni)shmall:可用页框总数上限 (/proc/sys/kernel/shmall)SHMLBA:映射对齐粒度(通常为页或更大,对齐到大页边界以兼容 VIPT/TLB 需求)
重要特性/行为
- 段由 shmem(tmpfs)文件承载页缓存,支持按需分配与换出(除非 SHM_LOCK)。
shmat映射地址位于用户态虚拟地址空间,各进程地址可不同但物理页共享。shmdt仅拆映射并递减计数,不一定释放物理页;IPC_RMID+shm_nattch==0才真正销毁。SEM_UNDO不涉及共享内存;共享内存的同步需用户态自管(信号量/互斥锁等)。
参考函数/符号(2.6.12)
sys_shmget/sys_shmat/sys_shmdt/sys_shmctlnewseg/do_shmat/do_shmdt/shm_destroyshmem_file_setup(支撑文件)/do_mmap_pgoff(映射)ipc_addid/ipc_rmid/ipc_lock/ipc_checkid


815

被折叠的 条评论
为什么被折叠?



