You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

共享内存访问冲突防范:加锁时机、方法及死锁问题解决

内核与用户态共享内存访问冲突解决方案

mmap_write_lock死锁根因

  • 该锁的设计用途是保护进程地址空间元数据,包括VMA结构增删改、页表映射关系调整等,完全不承担物理页内容并发访问的同步职责,用来保护页数据读写属于场景误用。
  • 死锁来自锁依赖冲突:kmap、缺页异常等内核流程执行时可能已经持有mm结构的读锁或其他关联锁,此时主动申请写锁会直接形成锁依赖环,触发死锁是必然结果。
  • 内核态的任意锁都无法直接阻断用户态内存访问:用户态对已映射页的读写由CPU硬件直接执行,不会经过内核锁逻辑,仅靠内核内部锁无法拦住用户态的并发写入。

合规同步实现方案

共享内存访问冲突的核心解决思路是访问双方共同遵守同步约定,不存在仅靠内核单侧逻辑就能完全阻断并发的方案,可根据场景选择以下实现:

方案1:共享原子变量同步(推荐,性能最优)

规范要求的信号、信号量机制本质是访问双方约定同步规则,最高效的实现是基于共享页内的原子变量做协同,无额外系统开销:

  • 在共享页起始位置预留固定长度的同步头,存放原子状态变量(锁标志、序列号均可)
  • 内核侧执行写操作前,先通过原子操作将锁标志置为持有状态,插入写内存屏障保证锁状态先于后续数据写入对用户态可见,再执行memcpy拷贝;拷贝完成后再次插入内存屏障,保证所有数据写入完成后再清除锁标志
  • 用户态访问共享内存前,先原子读取锁标志,若处于持有状态则自旋等待或通过futex阻塞,确认锁释放后再执行读写操作
  • 若为内核单向写、用户态只读的场景,可以替换为无锁seqcount机制:写前序列号自增为奇数,写完自增为偶数;用户态读前后两次读取序列号,值相等且为偶数则说明读取过程无并发写,全程无阻塞,性能最优

内核侧参考实现:

// 共享内存布局,内核与用户态共用同一份结构定义
struct shared_region {
    atomic64_t write_busy; // 0=空闲可访问,1=内核正在写入
    u8 payload[PAGE_SIZE - sizeof(atomic64_t)]; // 实际业务数据区
};

kvaddr = kmap(p);
struct shared_region *reg = (struct shared_region *)kvaddr;

// 标记写状态,加内存屏障禁止CPU、编译器乱序
atomic64_set(&reg->write_busy, 1);
smp_mb__after_atomic();

// 执行数据拷贝,避免覆盖同步头
size_t copy_len = strnlen(src, sizeof(reg->payload));
memcpy(reg->payload, src, copy_len);

// 写完成后清除标记,加屏障保证数据先于标记状态对用户态可见
smp_mb__before_atomic();
atomic64_set(&reg->write_busy, 0);

// 若使用futex阻塞机制,此处执行futex唤醒等待的用户态进程
kunmap(p);

方案2:页表权限保护(低频率写场景适配)

如果不方便修改用户态同步逻辑,可以通过页表权限临时阻断用户态写:

  • 内核执行拷贝前,将对应共享页的用户态页表项修改为只读,刷新对应CPU的TLB
  • 此时用户态如果发起写操作会触发写保护缺页,在自定义的缺页处理逻辑中等待内核拷贝完成后,再恢复页表可写权限、返回用户态继续执行
  • 该方案存在TLB刷新开销,仅适合写操作频率极低的场景,不适合高频I/O。

注意事项

当前无同步逻辑的代码运行正常只是未触发并发时序的巧合,一旦用户态写与内核memcpy时序重叠,轻则出现数据撕裂、读到半更新状态的无效值,重则触发用户态进程野指针崩溃,必须按场景添加同步逻辑。

内容的提问来源于stack exchange,提问作者justtoaskaquestion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 16:09:21