使用call_usermodehelper在Linux內核中直接運行用戶空間程序

        by沈東良/良少http://blog.csdn.net/shendl2011.07.19



系統初始化時kernel_init在內核態創建和運行應用程序以完成系統初始化

          內核剛剛啓動時,只有內核態的代碼,後來在init過程中,在內核態運行了一些初始化系統的程序,才產生了工作在用戶空間的進程。


/* This is a non __init function. Force it to be noinline otherwise gcc
 736 * makes it inline to init() and it becomes part of init.text section
 737 */
 738static noinline int init_post(void)
 739{
 740        /* need to finish all async __init code before freeing the memory */
 741        async_synchronize_full();
 742        free_initmem();
 743        mark_rodata_ro();
 744        system_state = SYSTEM_RUNNING;
 745        numa_default_policy();
 746
 747
 748        current->signal->flags |= SIGNAL_UNKILLABLE;
 749
 750        if (ramdisk_execute_command) {
 751                run_init_process(ramdisk_execute_command);
 752                printk(KERN_WARNING "Failed to execute %s\n",
 753                                ramdisk_execute_command);
 754        }
 755
 756        /*
 757         * We try each of these until one succeeds.
 758         *
 759         * The Bourne shell can be used instead of init if we are
 760         * trying to recover a really broken machine.
從內核裏發起系統調用,執行用戶空間的應用程序。這些程序自動以root權限運行。
 761         */
 762        if (execute_command) {
 763                run_init_process(execute_command);
 764                printk(KERN_WARNING "Failed to execute %s.  Attempting "
 765                                        "defaults...\n", execute_command);
 766        }
 767        run_init_process("/sbin/init");
 768        run_init_process("/etc/init");
 769        run_init_process("/bin/init");
 770        run_init_process("/bin/sh");
 771
 772        panic("No init found.  Try passing init= option to kernel. "
 773              "See Linux Documentation/init.txt for guidance.");
 774}

        這裏,內核以此運行用戶空間程序,從而產生了第一個以及後續的用戶空間程序。
一般用戶空間的init程序,會啓動一個shell,供用戶登錄系統用。這樣,這裏啓動的用戶空間的程序永遠不會返回。
也就是說,正常情況下不會到panic這一步。
系統執行到這裏後,Linux Kernel的初始化就完成了。

此時,中斷和中斷驅動的進程調度機制,調度着各個線程在各個CPU上的運行。
中斷處理程序不時被觸發。
操作系統上,一些內核線程在內核態運行,它們永遠不會進入用戶態。它們也根本沒有用戶態的內存空間。它的線性地址空間就是共享內核的線性地址空間。
一些用戶進程通常在用戶態運行。有時因爲系統調用而進入內核態,調用內核提供的系統調用處理函數。


         但有時,我們的內核模塊或者內核線程希望能夠調用用戶空間的進程,就像系統啓動之初init_post函數做的那樣。

       如,一個驅動從內核得到了主從設備號,然後需要使用mknod命令創建相應的設備文件,以供用戶調用該設備。

       如,一個內核線程想神不知鬼不覺地偷偷運行個有特權的後門程序。

等等之類的需求。

call_usermodehelper函數

          Linux  Kernel提供了call_usermodehelper函數,讓我們能夠異常方便地在內核中直接新建和運行用戶空間程序,並且該程序具有root權限。



call_usermodehelper函數源碼

include/linux/kmod.h頭文件



 105static inline int
 106call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
 107{
 108        return call_usermodehelper_fns(path, argv, envp, wait,
 109                                       NULL, NULL, NULL);
 110}
 111


  50enum umh_wait {
  51        UMH_NO_WAIT = -1,       /* don't wait at all */
  52        UMH_WAIT_EXEC = 0,      /* wait for the exec, but not the process */
  53        UMH_WAIT_PROC = 1,      /* wait for the process to complete */
  54};
  55
  56struct subprocess_info {
  57        struct work_struct work;
  58        struct completion *complete;
  59        char *path;
  60        char **argv;
  61        char **envp;
  62        enum umh_wait wait;
  63        int retval;
  64        int (*init)(struct subprocess_info *info);
  65        void (*cleanup)(struct subprocess_info *info);
  66        void *data;
  67};
  68


kernel/kmod.c實現文件

377/**
 378 * call_usermodehelper_exec - start a usermode application
 379 * @sub_info: information about the subprocessa  子進程的信息
 380 * @wait: wait for the application to finish and return status.等待用戶空間子進程的完成,並返回結果。
 381 *        when -1 don't wait at all, but you get no useful error back when
 382 *        the program couldn't be exec'ed. This makes it safe to call
 383 *        from interrupt context.
-1表示根本不等待子進程的結束。 但這樣你就無法對程序出錯進行處理。
如果使用中斷上下文,那麼應該使用-1。
 384 *
 385 * Runs a user-space application.  The application is started
 386 * asynchronously if wait is not set, and runs as a child of keventd.
 387 * (ie. it runs with full root capabilities).
call_usermodehelper_exec函數,啓動一個用戶模式應用程序。
如果不設置wait,那麼用戶空間應用程序會被異步啓動。  它在root權限下運行。是keventd進程的子進程。


 388 */
 389int call_usermodehelper_exec(struct subprocess_info *sub_info,
 390                             enum umh_wait wait)
 391{
 392        DECLARE_COMPLETION_ONSTACK(done);
 393        int retval = 0;
 394
 395        helper_lock();
 396        if (sub_info->path[0] == '\0')
 397                goto out;
 398
 399        if (!khelper_wq || usermodehelper_disabled) {
 400                retval = -EBUSY;
 401                goto out;
 402        }
 403
 404        sub_info->complete = &done;
 405        sub_info->wait = wait;
 406把用戶空間進程掛到一個內核工作隊列。
 407        queue_work(khelper_wq, &sub_info->work);
 408        if (wait == UMH_NO_WAIT)        /* task has freed sub_info */
 409                goto unlock;
如果等待子進程完成,那麼執行等待完成的  事件通知和喚醒。就是說當前進程sleep。
 410        wait_for_completion(&done);
 411        retval = sub_info->retval;
 412
 413out:
 414        call_usermodehelper_freeinfo(sub_info);
 415unlock:
 416        helper_unlock();
 417        return retval;
 418}
 419EXPORT_SYMBOL(call_usermodehelper_exec);
 420
 421void __init usermodehelper_init(void)
 422{
 423        khelper_wq = create_singlethread_workqueue("khelper");
 424        BUG_ON(!khelper_wq);
 425}



     call_usermodeheler函數創建的新程序實際上作爲keventd內核線程的子進程運行,因此具有root權限。

      新程序被扔到內核工作隊列“khelper”中進行執行。

      如果使用UMH_NO_WAIT,那麼因爲沒有在事件隊列上等待和喚醒的過程,因此可以在中斷上下文中使用。

     它的返回值是新程序的返回值。


call_usermodeheler函數的參數用法和execve函數一致

#include<unistd.h>

intexecve(const char *filename, char *const argv[],
char*const
 envp[]);

execve函數使用sys_execve系統調用,創建並運行一個程序。

argv是字符串數組,是將被傳輸到新程序的參數。

envp是字符串數組,格式是key=value,是傳遞給新程序的環境變量。

argvenvp都必須以NULL字符串結束。以此來實現對字符串數組的大小統計。



     這就意味着,argv的第一個參數也必須是程序名。也就是說,新程序名要在execve函數的參數中傳遞兩次。



     這和main函數傳入的參數格式也是一致的。



使用call_usermodehelper在內核態創建和運行用戶空間程序的示例



/*

============================================================================

Name : testDriver1.c

Author :Edward Shen[email protected]

Version :

Copyright : Your copyrightnotice

Description : Hello World in C,Ansi-style

============================================================================

*/


#include<linux/init.h>

#include<linux/module.h>

#include<linux/moduleparam.h>

//#include<linux/config.h>


#include<linux/kernel.h>/*printk()*/

#include<linux/sched.h>

MODULE_LICENSE("DualBSD/GPL");



static__initinttestDriver1_init(void){

     intresult=0;

     char cmdPath[]="/usr/bin/touch";

     char* cmdArgv[]={cmdPath,"/touchX.txt",NULL};

     char* cmdEnvp[]={"HOME=/",

"PATH=/sbin:/bin:/usr/bin",NULL};

      result=call_usermodehelper(cmdPath,cmdArgv,cmdEnvp,UMH_WAIT_PROC);

      printk(KERN_DEBUG"testDriver1_initexec!Theresult of call_usermodehelper is %d\n",result);

      printk(KERN_DEBUG"testDriver1_initexec!Theprocess is \"%s\",pidis %d ,sys_getpid is %d \n",current->comm,current->pid);

      returnresult;

}



static__exitvoidtestDriver1_exit(void){

      intresult=0;

      char cmdPath[]="/bin/rm";

      char* cmdArgv[]={cmdPath,"/touchX.txt",NULL};

      char* cmdEnvp[]={"HOME=/",

"PATH=/sbin:/bin:/usr/bin",NULL};

      result=call_usermodehelper(cmdPath,cmdArgv,cmdEnvp,UMH_WAIT_PROC);

      printk(KERN_DEBUG"testDriver1_exitexec!Theresult of call_usermodehelper is %d\n",result);

      printk(KERN_DEBUG"testDriver1_exitexec!Theprocess is \"%s\",pidis %d \n",current->comm,current->pid);

}


module_init(testDriver1_init);

module_exit(testDriver1_exit);



編譯上述模塊的命令

make-C /lib/modules/`uname -r`/build M=`pwd` modules


然後使用insmodrmmod命令加載和卸載驅動。可以看到上述命令創建了文件和刪除了創建的文件。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章