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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM