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

在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,并释放已分配的物理页。
  • 更低延迟:内核直接写入用户态地址,无需套接字层的内存拷贝。
  • 内存高效:仅在实际写入时分配物理页,避免预分配浪费。
  • 实时性适配:通过原子分配和预存VMA信息,完全兼容PREEMPT_RT的softirq上下文要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.10 03:13:11