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

如何通过send_sig_info以信号实现内核到用户态的数据传输?

内核模块通过信号向用户态传输数据的实现方案

核心逻辑说明

要通过信号传递数据,核心是利用send_sig_info()传递siginfo_t结构体,其中si_ptr字段只能存放用户态虚拟地址(不能直接传内核地址,用户态无法访问)。步骤大致是:

  1. 内核用vmalloc()分配可映射的内存存储事件数据;
  2. 将这块内核内存映射到用户态进程的虚拟地址空间;
  3. 把映射后的用户态地址填入si_ptr,通过send_sig_info()发送信号;
  4. 用户态收到信号后从si_ptr指向的地址读取数据,再通过内核接口通知内核释放内存。

示例代码

内核模块代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/workqueue.h>

#define DEVICE_NAME "sig_ipc_dev"
#define IOCTL_REGISTER_PID _IOR('s', 1, pid_t)
#define IOCTL_RELEASE_MEM _IOW('s', 2, unsigned long)
#define DATA_SIZE 256

static pid_t target_pid = 0;
static struct class *sig_class;
static struct device *sig_device;
static dev_t dev_num;
static struct cdev sig_cdev;

// 存储内核内存与用户态地址的映射关系,实际项目可改用哈希表管理多组映射
static char *kernel_mem;
static unsigned long user_vaddr;

static long sig_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    switch (cmd) {
        case IOCTL_REGISTER_PID:
            if (copy_from_user(&target_pid, (pid_t __user *)arg, sizeof(pid_t))) {
                return -EFAULT;
            }
            pr_info("Registered user PID: %d\n", target_pid);
            break;
        case IOCTL_RELEASE_MEM:
            if (kernel_mem && user_vaddr == arg) {
                // 解除用户态地址映射
                struct task_struct *task;
                rcu_read_lock();
                task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
                if (task) {
                    struct mm_struct *mm = get_task_mm(task);
                    if (mm) {
                        down_write(&mm->mmap_sem);
                        zap_page_range(mm, arg, DATA_SIZE);
                        up_write(&mm->mmap_sem);
                        mmput(mm);
                    }
                }
                rcu_read_unlock();
                // 释放内核内存
                vfree(kernel_mem);
                kernel_mem = NULL;
                user_vaddr = 0;
                pr_info("Released kernel memory\n");
            }
            break;
        default:
            return -ENOTTY;
    }
    return 0;
}

static const struct file_operations sig_fops = {
    .unlocked_ioctl = sig_ioctl,
};

static void send_event_work(struct work_struct *work) {
    struct siginfo info;
    struct task_struct *task;
    struct mm_struct *mm;
    struct page *page;

    if (!target_pid) {
        pr_warn("No registered user PID\n");
        return;
    }

    // 分配内核内存(vmalloc分配的内存可映射到用户态)
    kernel_mem = vmalloc(DATA_SIZE);
    if (!kernel_mem) {
        pr_err("vmalloc failed\n");
        return;
    }

    // 填充事件数据
    snprintf(kernel_mem, DATA_SIZE, "Kernel event triggered at jiffies: %lu", jiffies);
    pr_info("Event data: %s\n", kernel_mem);

    // 获取目标进程的task_struct
    rcu_read_lock();
    task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
    if (!task) {
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        pr_err("Target process not found\n");
        return;
    }

    // 获取进程内存描述符
    mm = get_task_mm(task);
    if (!mm) {
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        pr_err("Failed to get mm_struct\n");
        return;
    }

    // 转换vmalloc内存到page结构
    page = vmalloc_to_page(kernel_mem);
    if (!page) {
        mmput(mm);
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        pr_err("vmalloc_to_page failed\n");
        return;
    }

    // 分配用户态空闲虚拟地址
    user_vaddr = get_unmapped_area(NULL, 0, DATA_SIZE, 0, 0);
    if (user_vaddr == (unsigned long)-ENOMEM) {
        mmput(mm);
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        pr_err("get_unmapped_area failed\n");
        return;
    }

    // 将内核page插入到用户态地址空间
    down_write(&mm->mmap_sem);
    if (vm_insert_page(mm, user_vaddr, page) != 0) {
        up_write(&mm->mmap_sem);
        mmput(mm);
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        pr_err("vm_insert_page failed\n");
        return;
    }
    up_write(&mm->mmap_sem);

    // 初始化siginfo结构体
    memset(&info, 0, sizeof(info));
    info.si_signo = SIGUSR1;
    info.si_code = SI_QUEUE; // 必须用SI_QUEUE才能传递完整siginfo
    info.si_ptr = (void __user *)user_vaddr;

    // 发送信号
    if (send_sig_info(SIGUSR1, &info, task) < 0) {
        pr_err("send_sig_info failed\n");
        // 发送失败,清理已分配资源
        down_write(&mm->mmap_sem);
        zap_page_range(mm, user_vaddr, DATA_SIZE);
        up_write(&mm->mmap_sem);
        mmput(mm);
        rcu_read_unlock();
        vfree(kernel_mem);
        kernel_mem = NULL;
        return;
    }

    mmput(mm);
    rcu_read_unlock();
    pr_info("Signal sent, user address: %p\n", (void *)user_vaddr);
}

static DECLARE_DELAYED_WORK(event_work, send_event_work);

static int __init sig_ipc_init(void) {
    int ret;

    // 分配字符设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        pr_err("alloc_chrdev_region failed\n");
        return ret;
    }

    // 创建设备类和设备节点
    sig_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(sig_class)) {
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(sig_class);
    }

    sig_device = device_create(sig_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(sig_device)) {
        class_destroy(sig_class);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(sig_device);
    }

    // 注册字符设备
    cdev_init(&sig_cdev, &sig_fops);
    ret = cdev_add(&sig_cdev, dev_num, 1);
    if (ret < 0) {
        device_destroy(sig_class, dev_num);
        class_destroy(sig_class);
        unregister_chrdev_region(dev_num, 1);
        return ret;
    }

    pr_info("Signal IPC module loaded\n");

    // 2秒后触发模拟事件
    schedule_delayed_work(&event_work, msecs_to_jiffies(2000));

    return 0;
}

static void __exit sig_ipc_exit(void) {
    cancel_delayed_work_sync(&event_work);
    if (kernel_mem) {
        vfree(kernel_mem);
    }
    cdev_del(&sig_cdev);
    device_destroy(sig_class, dev_num);
    class_destroy(sig_class);
    unregister_chrdev_region(dev_num, 1);
    pr_info("Signal IPC module unloaded\n");
}

module_init(sig_ipc_init);
module_exit(sig_ipc_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kernel to user IPC via signal");

用户态应用代码

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#define DEVICE_PATH "/dev/sig_ipc_dev"
#define IOCTL_REGISTER_PID _IOR('s', 1, pid_t)
#define IOCTL_RELEASE_MEM _IOW('s', 2, unsigned long)
#define DATA_SIZE 256

static int dev_fd;

static void signal_handler(int signo, siginfo_t *info, void *context) {
    if (signo != SIGUSR1 || info->si_code != SI_QUEUE) {
        return;
    }

    char *event_data = (char *)info->si_ptr;
    printf("Received kernel event: %s\n", event_data);

    // 解除用户态地址映射
    munmap(event_data, DATA_SIZE);

    // 通知内核释放对应内存
    if (ioctl(dev_fd, IOCTL_RELEASE_MEM, (unsigned long)event_data) < 0) {
        perror("ioctl release mem failed");
    }
}

int main() {
    struct sigaction sa;
    pid_t pid = getpid();

    // 打开设备节点
    dev_fd = open(DEVICE_PATH, O_RDWR);
    if (dev_fd < 0) {
        perror("open device failed");
        exit(EXIT_FAILURE);
    }

    // 向内核注册当前进程PID
    if (ioctl(dev_fd, IOCTL_REGISTER_PID, &pid) < 0) {
        perror("ioctl register pid failed");
        close(dev_fd);
        exit(EXIT_FAILURE);
    }

    // 设置信号处理函数(必须启用SA_SIGINFO才能获取siginfo结构体)
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler;
    sa.sa_flags = SA_SIGINFO;
    if (sigaction(SIGUSR1, &sa, NULL) < 0) {
        perror("sigaction failed");
        close(dev_fd);
        exit(EXIT_FAILURE);
    }

    printf("Waiting for kernel events... (PID: %d)\n", pid);
    while (1) {
        pause();
    }

    close(dev_fd);
    return EXIT_SUCCESS;
}

内存释放的关键说明

  1. 用户态不能直接释放内核内存:内核内存的生命周期由内核管理,用户态没有权限直接操作内核地址空间,强行操作会导致崩溃或系统异常。
  2. 正确的释放流程:
    • 用户态收到数据后,先调用munmap()解除自身地址空间中对内核内存的映射,避免后续非法访问;
    • 通过自定义的ioctl命令(示例中的IOCTL_RELEASE_MEM)将用户态地址传递给内核;
    • 内核根据用户态地址找到对应的内核内存块,先解除用户态的页映射(zap_page_range()),再调用vfree()释放内核内存。
  3. 异常情况处理:如果用户态进程意外崩溃,内核需要监听进程退出事件(比如注册mm_notifier),自动清理未释放的内核内存,避免内存泄漏。

注意事项

  • 必须使用SI_QUEUE作为si_code:只有这个代码能保证用户态收到完整的siginfo_t结构体,否则si_ptr字段可能无法正确传递。
  • 内核内存必须用vmalloc()分配:kmalloc()分配的内存物理地址不连续,无法直接映射到用户态;vmalloc()分配的是连续虚拟地址,可通过vmalloc_to_page()获取对应的页结构。
  • 用户态必须设置SA_SIGINFO标志:否则信号处理函数无法获取siginfo_t结构体,也就无法读取si_ptr中的数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:46:04