内核线程之User-Mode Helpers
这次学习下如何在Linux内核态执行用户态程序,这就要用到User-Mode Helpers,为什么要这么“逆操作”呢?有些与平常用户态系统调用内核态反着来,其实在U盘热插拔时,就需要用到该功能了,当U盘插入时,驱动识别到U盘设备,最终需要调用用户态的程序和设定好的规则来将其挂载起来,还有其他的应用场景也需要这样的操作,自己好好探索下吧。
接下来说说关于User-Mode Helpers,下面是相关的函数(在kernel/kmod.c文件中有定义,下面只给出函数头和函数体较少的代码):
1.call_usermodehelper_setup
接下来说说关于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文件中有如下定义:
而 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/
├── 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/
评论
发表评论