linux 在内核模块调用应用层程序

在介绍实现原理之前,先看一个简单的例子,在内核中如何使用相关的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(&current->sighand->siglock);
	flush_signal_handlers(current, 1);
	spin_unlock_irq(&current->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来执行用户层的应用程序。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值