如何在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
相关产品推荐
相关产品推荐

