内核线程之User-Mode Helpers

这次学习下如何在Linux内核态执行用户态程序,这就要用到User-Mode Helpers,为什么要这么“逆操作”呢?有些与平常用户态系统调用内核态反着来,其实在U盘热插拔时,就需要用到该功能了,当U盘插入时,驱动识别到U盘设备,最终需要调用用户态的程序和设定好的规则来将其挂载起来,还有其他的应用场景也需要这样的操作,自己好好探索下吧。
接下来说说关于User-Mode Helpers,下面是相关的函数(在kernel/kmod.c文件中有定义,下面只给出函数头和函数体较少的代码):
1.call_usermodehelper_setup
struct subprocess_info *call_usermodehelper_setup(char *path, char **argv,
        char **envp, gfp_t gfp_mask,
        int (*init)(struct subprocess_info *info, struct cred *new),
        void (*cleanup)(struct subprocess_info *info),
        void *data) ;
2.call_usermodehelper_exec
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) ;
3.call_usermodehelper
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
        struct subprocess_info *info;
        gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;

        info = call_usermodehelper_setup(path, argv, envp, gfp_mask,
                NULL, NULL, NULL);
        if (info == NULL)
                return -ENOMEM;

        return call_usermodehelper_exec(info, wait);
}
从上面这3个函数可以看到,第3个最终是调用第1和第2这两个函数的,故而一般直接使用第3个函数就可以了,从上面的代码中有 UMH_NO_WAIT宏,其在include/linux/kmod.h文件中有如下定义:
#define UMH_NO_WAIT 0 /* don't wait at all */
#define UMH_WAIT_EXEC 1 /* wait for the exec, but not the process */
#define UMH_WAIT_PROC 2 /* wait for the process to complete */
#define UMH_KILLABLE 4 /* wait for EXEC/PROC killable */
即最终调用 call_usermodehelper_exec函数时所采用的等待策略,宏后面均有注释。
而 GFP_ATOMIC和 GFP_KERNEL则是在 调用call_usermodehelper_setup函数时所采取的内存分配策略,GFP_ATOMIC是原子性的,不可等待,而GFP_KERNEL则可等待,其在include/linux/gfp.h文件中有相应的宏定义。
在上面3个函数中,我们看到使用了 subprocess_info结构体进行牵线搭桥,该结构体在include/linux/kmod.h文件中有如下定义:
struct subprocess_info {
        struct work_struct work;
        struct completion *complete;
        char *path;
        char **argv;
        char **envp;
        int wait;
        int retval;
        int (*init)(struct subprocess_info *info, struct cred *new);
        void (*cleanup)(struct subprocess_info *info);
        void *data;
};
接下来上实例源码吧,本次为了图方便,还是在内核线程的基础上使用UMH(User-Mode Helpers),包含参数的组成形式等,以及完善《内核线程同步之signal》一文中如何在module_exit退出时发信号让线程结束的处理方法:
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>

static struct task_struct * slam_thread = NULL;

static int run_umh_app(void)
{
        char *argv[] = {"/bin/touch", "/home/xinu/slam.txt", NULL};
        static char *envp[] = {
                "HOME=/",
                "TERM=linux",
                "PATH=/sbin:/bin:/usr/bin:/usr/sbin",
                NULL
        };

        return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
}

static int slam_func(void *data)
{
        printk("<xinu>%s()!\n", __func__);
        allow_signal(SIGKILL);
        mdelay(1000);

        while(!signal_pending(current))
        {
                /* run user-mode helpers */
                run_umh_app();

                printk("<xinu>jiffies(%lu)\n", jiffies);
                set_current_state(TASK_INTERRUPTIBLE);
                schedule_timeout(msecs_to_jiffies(5000));
        }

        printk("<xinu>leave slam_func!\n");

        return 0;
}

static __init int kthread_signal_example_init(void)
{
        slam_thread = kthread_run(slam_func, NULL, "slam");
        printk("<xinu>kthread_signal_example_init()!\n");

        return 0;
}

static __exit void kthread_signal_example_exit(void)
{
        if(!IS_ERR(slam_thread))
        {
                send_sig(SIGKILL, slam_thread, 1);
        }
        printk("<xinu>%s()!\n", __FUNCTION__);
}

module_init(kthread_signal_example_init);
module_exit(kthread_signal_example_exit);
相应的Makefile文件内容如下:
obj-m += kthread_umh_example.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
        make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
对应的源码文件目录树如下: /home/xinu/xinu/linux_kernel_driver_l1/kthread_user_mode_helpers_example/
├── kthread_umh_example.c
└── Makefile
看到源码里作为argv和envp的最后一个元素必须为NULL,还有用到send_sig函数(在kernel/signal.c文件里有定义)去发SIGKILL信号给对应的线程吧,还有注意 run_umh_app函数的调用位置,现在是放在循环体首(“{”之后),如果是在尾(“}”之前),则在rmmod时会RIP错误。
加载模块后,我们会看到/home/xinu目录下slam.txt文件的修改时间不断刷新(如果不存在该文件则会自动创建,调用了touch命令)。
至此,对内核信号机制的使用作了补充,了解了如何在内核里发结束信号给指定线程,还有学习了如何在内核态执行用户态程序。
参考网址:
http://www.linux-mag.com/id/2195/
http://reneeciou.blogspot.com/2013/08/linux-kernel-threads.html
http://www.ibm.com/developerworks/cn/linux/l-user-space-apps/

评论

此博客中的热门博文

I/O映射之I/O端口

通过Netlink检测网线插拔

使用seq_file