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內核中直接運行用戶空間程序