在PREEMPT_RT内核softirq上下文实现按需分页式内核用户共享内存
实现PREEMPT_RT内核softirq上下文的按需分页共享内存方案
核心思路概述
要实现内核作为写入方、用户态作为读取方的按需分页共享内存,核心是利用用户态虚拟地址预留+内核自定义缺页处理的机制,替代未导出的get_vm_area(),同时适配PREEMPT_RT的softirq上下文约束——避免睡眠操作、使用原子内存分配。
具体实现步骤
1. 用户态预留虚拟内存区域
用户态通过mmap()创建匿名或tmpfs映射,仅预留虚拟地址空间,不立即分配物理页:
// 用户态代码示例(tmpfs方式推荐) #define SHMEM_SIZE (1024 * 1024) // 1MB共享区域 int fd = open("/dev/shm/irq_log_shm", O_RDWR | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open tmpfs file failed"); exit(EXIT_FAILURE); } if (ftruncate(fd, SHMEM_SIZE) < 0) { perror("ftruncate failed"); close(fd); exit(EXIT_FAILURE); } void *shared_addr = mmap(NULL, SHMEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shared_addr == MAP_FAILED) { perror("mmap failed"); close(fd); exit(EXIT_FAILURE); }
tmpfs映射的优势是内核可通过文件描述符直接关联内存区域,无需遍历进程VMA链表。
2. 内核模块关联用户态虚拟区域
在进程上下文(如系统调用、ioctl处理函数)中,完成用户态VMA的绑定和自定义缺页函数的注册:
// 内核代码,进程上下文执行 #include <linux/mm.h> #include <linux/fs.h> static struct vm_area_struct *saved_vma; static unsigned long vma_start, vma_end; // 自定义缺页处理函数 static int shm_vm_fault(struct vm_fault *vmf) { struct page *page; gfp_t gfp_mask = GFP_KERNEL | __GFP_ZERO; // PREEMPT_RT softirq上下文不能睡眠,切换为原子分配掩码 if (in_serving_softirq()) { gfp_mask = GFP_ATOMIC | __GFP_ZERO; } page = alloc_page(gfp_mask); if (!page) { return VM_FAULT_OOM; } vmf->page = page; return 0; } static const struct vm_operations_struct shm_vm_ops = { .fault = shm_vm_fault, }; // 处理用户态传入的文件描述符,绑定VMA int bind_shm_fd(int fd) { struct file *file = fdget(fd); struct mm_struct *mm = current->mm; struct vm_area_struct *vma; if (!file) return -EBADF; down_read(&mm->mmap_sem); // 遍历进程VMA找到对应tmpfs映射的区域 for (vma = mm->mmap; vma; vma = vma->vm_next) { if (vma->vm_file == file) { vma->vm_ops = &shm_vm_ops; vma->vm_flags |= VM_IO | VM_PFNMAP; saved_vma = vma; vma_start = vma->vm_start; vma_end = vma->vm_end; up_read(&mm->mmap_sem); fdput(file); return 0; } } up_read(&mm->mmap_sem); fdput(file); return -EINVAL; }
注意:所有涉及mmap_sem的操作必须在进程上下文完成,softirq上下文不能持有可睡眠锁。
3. Softirq上下文的内核写入实现
在softirq中直接访问用户态虚拟地址,缺页时会自动触发自定义shm_vm_fault分配物理页:
// 内核softirq代码 #include <linux/interrupt.h> #include <linux/spinlock.h> static DEFINE_SPINLOCK(log_write_lock); // PREEMPT_RT自旋锁支持抢占 void softirq_log_handler(struct softirq_action *h) { char log_buf[] = "Softirq log message"; size_t log_len = sizeof(log_buf); // 假设用户态共享区域起始地址已保存为user_shared_addr void *user_addr = (void *)vma_start; // 检查地址合法性 if ((unsigned long)user_addr + log_len > vma_end) { return; } // 自旋锁保证多softirq写入的原子性 spin_lock(&log_write_lock); // 直接写入用户态地址,缺页时触发shm_vm_fault分配物理页 memcpy(user_addr, log_buf, log_len); spin_unlock(&log_write_lock); }
PREEMPT_RT内核中,GFP_ATOMIC分配不会阻塞,因此softirq上下文的缺页处理可安全执行。
关键注意事项
- 内存分配掩码:softirq上下文必须使用
GFP_ATOMIC,进程上下文可使用GFP_KERNEL,避免睡眠导致实时性下降。 - 地址有效性:内核写入前必须验证地址是否在预留的VMA范围内,防止越界访问。
- 同步机制:多softirq或多CPU写入时,需使用PREEMPT_RT兼容的自旋锁保证原子性。
- 资源清理:模块卸载时需遍历进程VMA恢复默认
vm_ops,并释放已分配的物理页。
方案优势对比Netlink
- 更低延迟:内核直接写入用户态地址,无需套接字层的内存拷贝。
- 内存高效:仅在实际写入时分配物理页,避免预分配浪费。
- 实时性适配:通过原子分配和预存VMA信息,完全兼容PREEMPT_RT的softirq上下文要求。
内容的提问来源于stack exchange,提问作者Programmer_2147483647
相关产品推荐
相关产品推荐

