call_usermodehelper内核中运行用户应用程序
init是用户空间第一个程序,在调用init前程序都运行在内核态,之后运行init时程序运行到用户态。
操作系统上,一些内核线程在内核态运行,它们永远不会进入用户态。它们也根本没有用户态的内存空间。它的线性地址空间就是共享内核的线性地址空间。一些用户进程通常在用户态运行。有时因为系统调用而进入内核态,调用内核提供的系统调用处理函数。
但有时,我们的内核模块或者内核线程希望能够调用用户空间的进程,就像系统启动之初init_post函数做的那样。
如,一个驱动从内核得到了主从设备号,然后需要使用mknod命令创建相应的设备文件,以供用户调用该设备。
如,一个内核线程想神不知鬼不觉地偷偷运行个有特权的后门程序。
等等之类的需求。
linux kernel提供了call_usermodehelper,用于内核中直接新建和运行用户空间程序,并且该程序具有root权限。
函数原型
call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait); enum umh_wait { UMH_NO_WAIT = -1, /* don't wait at all */ UMH_WAIT_EXEC = 0, /* wait for the exec, but not the process */ UMH_WAIT_PROC = 1, /* wait for the process to complete */ };
默认为UMH_WAIT_EXEC,内核exec用户空间进程后就退出;UMH_WAIT_PROC会一直等到用户空间进程结束为止。
call_usermodehelper函数的参数用法和execve函数一致,
argv是字符串数组,是将被传输到新程序的参数。
envp是字符串数组,格式是key=value,是传递给新程序的环境变量。
argv和envp都必须以NULL字符串结束。以此来实现对字符串数组的大小统计。
这就意味着,argv的第一个参数也必须是程序名。也就是说,新程序名要在execve函数的参数中传递两次。
函数原理
call_usermodehelper()执行之后会在工作队列khelper_wq中加入一个工作线程__call_usermodehelper, 这个工作队列上的线程运行之后,会根据wait的类型,调用kernel_thread启用相应类型的线程wait_for_helper()或者 ____call_usermodehelper(),之所以调用kernel_thread生成新的线程,目的在于让并行运行实现最大化,充分利用 cpu.
部分代码如下:
if (wait == UMH_WAIT_PROC || wait == UMH_NO_WAIT)
pid = kernel_thread(wait_for_helper, sub_info,
CLONE_FS | CLONE_FILES | SIGCHLD);
else
pid = kernel_thread(____call_usermodehelper, sub_info,
CLONE_VFORK | SIGCHLD);
线程wait_for_helper()或者____call_usermodehelper()最终调用kernel_execve()启动用户空间的应用程序,并把参数传给该应用程序,如:"/sbin/hotplug",由此可知call_usermodehelper()是内核驱动程序向外界应用程序程序传递内核信息的好手段,但是因为内核驱动会产生相当多的hotplug事件,所以后来就使用"/sbin/udevsend"临时代替,到了2.6.15内核之后,高效的netlink广播接口开始被采用,逐渐取代"/sbin/hotplug"和"/sbin/udevsend"的部分角色,成为一个新亮点,悄悄地登上了历史舞台。
使用示例
驱动中实现调用。
#include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/sched.h> MODULE_LICENSE("DualBSD/GPL"); static __init int hello_init(void) { int result = 0; char cmd_path[] = "/usr/bin/touch"; char *cmd_argv[] = {cmd_path, "/home/yu/test.txt", NULL}; char *cmd_envp[] = {"HOME=/", "PATH=/sbin:/bin:/user/bin", NULL}; result = call_usermodehelper(cmd_path, cmd_argv, cmd_envp, UMH_WAIT_PROC); printk(KERN_DEBUG"THe result of call_usermodehelper is %d\n", result); return result; } static __exit void hello_exit(void) { int result = 0; char cmd_path[] = "/bin/rm"; char *cmd_argv[] = {cmd_path, "/home/yu/test.txt", NULL}; char *cmd_envp[] = {"HOME=/", "PATH=/sbin:/bin:/user/bin", NULL}; result = call_usermodehelper(cmd_path, cmd_argv, cmd_envp, UMH_WAIT_PROC); printk(KERN_DEBUG"THe result of call_usermodehelper is %d\n", result); } module_init(hello_init); module_exit(hello_exit);
obj-m := hello.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.o *.ko *.mod* *.order *.sym*
参考:
1. 使用call_usermodehelper在Linux内核中直接运行用户空间程序