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内核中直接运行用户空间程序

2. Linux call_usermodehelper()

3. Invoking user-space applications from the kernel(IBM)

posted @ 2017-08-27 11:50  yuxi_o  阅读(6657)  评论(0编辑  收藏  举报