在介绍实现原理之前,先看一个简单的例子,在内核中如何使用相关的api调用用户层程序。
1 一个简单例子
内核模块代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kmod.h>
//需要调用的应用层程序
#define APPNAME "/sbin/app"
static int __init test_init(void)
{
int ret;
//初始话传给引用层的参数 就相当于main(argc,argv[])函数中的argv argv[0]要为执行的程序
char *argv[]={APPNAME,"this is a test",NULL};
//开始执行
ret = call_usermodehelper(APPNAME,argv,NULL,UMH_WAIT_PROC);
if(ret != 0)
{
printk("call failed\n");
return -1;
}
return 0;
}
static void __exit test_exit(void)
{
return;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
该例子调用call_usermodehelper api来执行用户层的应用程序,第一个参数APPNAME 就是指定用户层的应用程序路径,第二个参数argv就是用户层应用程序的参数。下面来看一下call_usermodehelper的具体实现。
2 具体实现
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);//利用参数初始化subprocess_info结构
if (info == NULL)
return -ENOMEM;
return call_usermodehelper_exec(info, wait);//调用执行用户层程序的核心函数
}
call_usermodehelper
-------------->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)
{
struct subprocess_info *sub_info;
sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
if (!sub_info)
goto out;
INIT_WORK(&sub_info->work, __call_usermodehelper);//初始化执行用户层程序的work函数
sub_info->path = path;
sub_info->argv = argv;
sub_info->envp = envp;
sub_info->cleanup = cleanup;
sub_info->init = init;
sub_info->data = data;
out:
return sub_info;
}
call_usermodehelper
-------------->call_usermodehelper_exec
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
DECLARE_COMPLETION_ONSTACK(done);//声明一个完成量
int retval = 0;
helper_lock();
if (!sub_info->path) {
retval = -EINVAL;
goto out;
}
if (sub_info->path[0] == '\0')
goto out;
if (!khelper_wq || usermodehelper_disabled) {//如果khelper_wq workqueue没有初始化完或者usermodehelper还没有enable,直接返回
retval = -EBUSY;
goto out;
}
/*
* Worker thread must not wait for khelper thread at below
* wait_for_completion() if the thread was created with CLONE_VFORK
* flag, for khelper thread is already waiting for the thread at
* wait_for_completion() in do_fork().
*/
if (wait != UMH_NO_WAIT && current == kmod_thread_locker) {
retval = -EBUSY;
goto out;
}
sub_info->complete = &done;
sub_info->wait = wait;
queue_work(khelper_wq, &sub_info->work);//把上一个函数声明的work queue如队列
if (wait == UMH_NO_WAIT) /* task has freed sub_info */
goto unlock;
if (wait & UMH_KILLABLE) {
retval = wait_for_completion_killable(&done);
if (!retval)
goto wait_done;
/* umh_complete() will see NULL and free sub_info */
if (xchg(&sub_info->complete, NULL))
goto unlock;
/* fallthrough, umh_complete() was already called */
}
wait_for_completion(&done);//等待完成
wait_done:
retval = sub_info->retval;
out:
call_usermodehelper_freeinfo(sub_info);
unlock:
helper_unlock();
return retval;
}
call_usermodehelper_exec函数就是把之前的work queue入队列,然后等待该work完成,有关work的执行原理,可以参考这篇文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/102962685
看一下具体的work执行函数__call_usermodehelper
static void __call_usermodehelper(struct work_struct *work)
{
struct subprocess_info *sub_info =
container_of(work, struct subprocess_info, work);//获取subprocess_info 结构
int wait = sub_info->wait & ~UMH_KILLABLE;
pid_t pid;
/* CLONE_VFORK: wait until the usermode helper has execve'd
* successfully We need the data structures to stay around
* until that is done. */
if (wait == UMH_WAIT_PROC)
pid = kernel_thread(wait_for_helper, sub_info,
CLONE_FS | CLONE_FILES | SIGCHLD);//等待用户层程序执行完才返回
else {
pid = kernel_thread(call_helper, sub_info,
CLONE_VFORK | SIGCHLD);//等待用户层程序加载完成就返回
/* Worker thread stopped blocking khelper thread. */
kmod_thread_locker = NULL;
}
//调用用户层引用程序的时候,调用者可以选择几种等待方式,这边根据情况分别处理
switch (wait) {
case UMH_NO_WAIT:
call_usermodehelper_freeinfo(sub_info);
break;
case UMH_WAIT_PROC:
if (pid > 0)
break;
/* FALLTHROUGH */
case UMH_WAIT_EXEC:
if (pid < 0)
sub_info->retval = pid;
umh_complete(sub_info);//通知调用者,唤醒调用者
}
}
wait_for_helper和call_helper的核心函数都是____call_usermodehelper
static int ____call_usermodehelper(void *data)
{
struct subprocess_info *sub_info = data;
struct cred *new;
int retval;
spin_lock_irq(¤t->sighand->siglock);
flush_signal_handlers(current, 1);
spin_unlock_irq(¤t->sighand->siglock);
/* We can run anywhere, unlike our parent keventd(). */
set_cpus_allowed_ptr(current, cpu_all_mask);
/*
* Our parent is keventd, which runs with elevated scheduling priority.
* Avoid propagating that into the userspace child.
*/
set_user_nice(current, 0);
retval = -ENOMEM;
new = prepare_kernel_cred(current);
if (!new)
goto fail;
spin_lock(&umh_sysctl_lock);
new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset);
new->cap_inheritable = cap_intersect(usermodehelper_inheritable,
new->cap_inheritable);
spin_unlock(&umh_sysctl_lock);
if (sub_info->init) {
retval = sub_info->init(sub_info, new);
if (retval) {
abort_creds(new);
goto fail;
}
}
commit_creds(new);
retval = do_execve(sub_info->path,
(const char __user *const __user *)sub_info->argv,
(const char __user *const __user *)sub_info->envp);
if (!retval)
return 0;
/* Exec failed? */
fail:
sub_info->retval = retval;
do_exit(0);
}
可以看到上面函数最终调用do_execve执行用户层应用程序。在do_execve程序中,start_thread就是要在进程核心栈中的相应位置填入进程用户态的xss,esp and xcs,eip.最后进程从ret_from_sys_call返回,iret指令从核心栈pop出xcs, eip完成特权及指令的转移, pop出 xss,esp,完成堆栈的切换,进入用户态执行。
总结一下具体流程就是先创建了一个work,然后在该work函数中又新建了一个内核线程,最后在该内核线程中调用do_execve来执行用户层的应用程序。