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

如何在Linux内核模块的中断服务程序中调用外部可执行文件?

在Linux内核模块中断服务程序中调用可执行文件的方法

核心原则:绝对不能在ISR中直接执行用户态程序

中断服务程序(ISR)运行在原子上下文,无法进行进程调度、不能执行任何可能阻塞的操作(如创建用户态进程、文件IO等)。直接在ISR中调用用户态程序会导致系统死锁或崩溃,必须将调用逻辑转移到进程上下文中执行,推荐使用工作队列(workqueue)实现。


具体实现步骤

1. 添加必要的头文件

在模块代码顶部添加:

#include <linux/workqueue.h>
#include <linux/kmod.h>  // 提供call_usermodehelper接口

2. 定义全局工作队列和工作项

在模块全局变量区域添加:

static struct workqueue_struct *gpio_workqueue;
static struct work_struct gpio_work;

3. 实现工作处理函数(进程上下文)

该函数运行在进程上下文,可安全调用用户态程序:

static void gpio_work_handler(struct work_struct *work) {
    // 要调用的用户态可执行文件路径
    const char *argv[] = {"/home/user_name/Documents/your_executable", NULL};
    // 必要的环境变量,确保程序能正常运行
    const char *envp[] = {"HOME=/", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL};

    // 调用用户态程序,UMH_WAIT_EXEC表示等待程序启动(不等待退出)
    int ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
    if (ret != 0) {
        printk(KERN_ERR "gpio_irq: Failed to execute user program, ret=%d\n", ret);
    }
}

4. 修改ISR,调度工作队列

将原ISR中调用用户态程序的逻辑替换为调度工作队列:

static irq_handler_t gpio_irq_handler(unsigned int irq, void *dev_id, struct pt_regs *regs) {
    printk(KERN_INFO "gpio_irq: Interrupt was triggered and ISR was called!\n");
    
    // 保留原GPIO切换逻辑
    if (toggle_value == 0) {
        toggle_value = 1;
        if(gpio_direction_output(498, 1)) {
            printk(KERN_ERR "gpio_irq: Can not set GPIO 498 output value to 1!\n");
            gpio_free(498);
        }
    } else {
        toggle_value = 0;
        if(gpio_direction_output(498, 0)) {
            printk(KERN_ERR "gpio_irq: Can not set GPIO 498 output value to 0!\n");
            gpio_free(498);
        }
    }

    // 调度工作队列,将用户态程序调用逻辑放到进程上下文执行
    queue_work(gpio_workqueue, &gpio_work);

    return (irq_handler_t) IRQ_HANDLED; 
}

5. 模块初始化时创建工作队列

修改ModuleInit函数,添加工作队列初始化逻辑:

static int __init ModuleInit(void) {
    printk(KERN_INFO "gpio_irq: Loading module... ");

    // 保留原GPIO和中断申请逻辑
    if(gpio_request(499, "rpi-gpio-499")) {
        printk(KERN_ERR "gpio_irq: Can not allocate GPIO 499\n");
        return -1;
    }
    if(gpio_request(498, "rpi-gpio-498")) {
        printk(KERN_ERR "gpio_irq: Can not allocate GPIO 498\n");
        return -1;
    }

    if(gpio_direction_input(499)) {
        printk(KERN_ERR "gpio_irq: Can not set GPIO 499 to input!\n");
        gpio_free(499);
        return -1;
    }

    irq_number = gpio_to_irq(499);

    if(request_irq(irq_number, (irq_handler_t) gpio_irq_handler, IRQF_TRIGGER_RISING, "my_gpio_irq", NULL) != 0){
        printk(KERN_ERR "gpio_irq: Can not request interrupt nr.: %d\n", irq_number);
        gpio_free(499);
        return -1;
    }

    // 创建单线程工作队列,确保任务顺序执行
    gpio_workqueue = create_singlethread_workqueue("gpio_workqueue");
    if (!gpio_workqueue) {
        printk(KERN_ERR "gpio_irq: Failed to create workqueue\n");
        free_irq(irq_number, NULL);
        gpio_free(499);
        gpio_free(498);
        return -1;
    }
    // 初始化工作项,绑定处理函数
    INIT_WORK(&gpio_work, gpio_work_handler);

    printk(KERN_INFO "gpio_irq: Done!\n");
    printk(KERN_INFO "gpio_irq: GPIO 499 is mapped to IRQ Nr.: %d\n", irq_number);
    return 0;
}

6. 模块退出时清理工作队列

修改ModuleExit函数,确保工作队列资源被正确释放:

static void __exit ModuleExit(void) {
    printk(KERN_INFO "gpio_irq: Unloading module... ");

    // 刷新工作队列,确保所有未完成的任务执行完毕
    flush_workqueue(gpio_workqueue);
    // 销毁工作队列
    destroy_workqueue(gpio_workqueue);

    free_irq(irq_number, NULL);
    gpio_free(499);
    gpio_free(498);

    printk(KERN_INFO "gpio_irq: Done!\n");
}

关键注意事项

  • call_usermodehelper是内核提供的安全调用用户态程序的接口,替代用户态的system()函数,支持多种等待模式(如UMH_WAIT_PROC表示等待程序退出)。
  • 如果不需要自定义工作队列,也可以使用系统默认工作队列system_wq,但自定义队列更可控,避免与其他系统任务冲突。
  • 环境变量必须设置必要的路径(如PATH),否则用户态程序可能无法找到依赖库或其他命令。

内容的提问来源于stack exchange,提问作者Aravind D. Chakravarti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:07:15