创建内核线程并通过内核线程调用用户态程序

创建内核线程并通过内核线程调用用户态程序

这里使用的内核为 linux-4.0 内核。

patch 贴到这里。

diff --git a/./linux-4.0-orig/linux-4.0/init/main.c b/./linux-4.0/init/main.c
index 6f0f1c5..88837d9 100644
--- a/./linux-4.0-orig/linux-4.0/init/main.c
+++ b/./linux-4.0/init/main.c
@@ -379,6 +379,69 @@ static void __init setup_command_line(char *command_line)
 
 static __initdata DECLARE_COMPLETION(kthreadd_done);
 
+
+static int mykthread_run_umode_handler(int event_id)
+{
+    char *argv[3], *envp[4], *buffer = NULL;
+    int i = 0, value;
+
+	 argv[i++] = myevent_handler;
+
+	 if (!(buffer = kmalloc(32, GFP_KERNEL)))
+		return -ENOMEM;
+
+	 sprintf(buffer, "TROUBLED_DS=%d", event_id);
+
+	 if (!argv[0])
+		return -EINVAL;
+
+	 argv[i] = NULL;
+
+	 i = 0;
+
+    /* minimal command environment */
+    envp[i++] = "HOME=/";
+    envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+	 envp[i++] = buffer;
+	 envp[i] = NULL;
+
+    value = call_usermodehelper(argv[0], argv, envp, 0);
+
+	 kfree(buffer);
+    return 0;
+}
+
+static int mykthread(void *unused)
+{
+	 int sigusr_count = 0;
+	
+	/* Setup a clean context for our children to inherit. */
+	set_task_comm(current, "mykthread");
+
+	/* Request delivery of SIGKILL */
+
+	/* The  thread sleeps on this wait queue until it's woken up
+	 * by parts of the kernel in charge of sensing the health of
+ 	  * data structures of i nterest */
+	
+ 	while (1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule();
+
+		if (signal_pending(current)) {
+			flush_signals(current);
+			sigusr_count++;
+	 	}
+		mykthread_run_umode_handler(sigusr_count);
+	} 
+	
+	set_current_state(TASK_RUNNING);	
+	
+	return 0;
+}
+
 static noinline void __init_refok rest_init(void)
 {
 	int pid;
@@ -392,6 +455,7 @@ static noinline void __init_refok rest_init(void)
 	kernel_thread(kernel_init, NULL, CLONE_FS);
 	numa_default_policy();
 	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
+	kernel_thread(mykthread, NULL, CLONE_FS | CLONE_FILES); 
 	rcu_read_lock();
 	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
 	rcu_read_unlock();
diff --git a/./linux-4.0-orig/linux-4.0/include/linux/sysctl.h b/./linux-4.0/include/linux/sysctl.h
index b7361f8..7fc854e 100644
--- a/./linux-4.0-orig/linux-4.0/include/linux/sysctl.h
+++ b/./linux-4.0/include/linux/sysctl.h
@@ -34,6 +34,8 @@ struct ctl_table_root;
 struct ctl_table_header;
 struct ctl_dir;
 
+extern char myevent_handler[];
+
 typedef int proc_handler (struct ctl_table *ctl, int write,
 			  void __user *buffer, size_t *lenp, loff_t *ppos);
 
diff --git a/./linux-4.0-orig/linux-4.0/kernel/sysctl.c b/./linux-4.0/kernel/sysctl.c
index ce410bb..e02e8b0 100644
--- a/./linux-4.0-orig/linux-4.0/kernel/sysctl.c
+++ b/./linux-4.0/kernel/sysctl.c
@@ -95,6 +95,10 @@
 
 #if defined(CONFIG_SYSCTL)
 
+
+char myevent_handler[256];
+EXPORT_SYMBOL(myevent_handler);
+
 /* External variables not in a header file. */
 extern int max_threads;
 extern int suid_dumpable;
@@ -724,6 +728,13 @@ static struct ctl_table kern_table[] = {
 		.mode		= 0555,
 		.child		= usermodehelper_table,
 	},
+        {
+         .procname = "myevent_handler",
+         .data = &myevent_handler,
+         .maxlen = 256,
+         .mode = 0644,
+         .proc_handler = &proc_dostring,
+        },
 	{
 		.procname	= "overflowuid",
 		.data		= &overflowuid,

kern_table 中添加的表项中,.data 指向的是一块内存区域,myevent_handler 是一个静态数组,用来存储 handler 的命令名称。

原书中的代码存在以下几个问题:

  1. waitqueue 暂时不知道咋样测试,就用信号来搞

  2. 接收到信号后要调用 flush_signals() 来清除 task struct 中的标志字段,不然会一直触发

  3. flush_signals 函数调用前获取 sighand 自旋锁会卡死,应该是此时自旋锁已经被占用

  4. 不需要执行 signal_allow 来显示的使能信号,调用 sigismember 宏判断的时候会失败

  5. 当内核线程直接返回时,有如下 oops 信息。

    / # kill -9 3
    task sighand blocked
    task sighand unblocked
    out
    kthread mykthread exited
    Unable to handle kernel NULL pointer dereference at virtual address 00000000
    pgd = 80004000
    [00000000] *pgd=00000000
    Internal error: Oops: 80000017 [#1] SMP ARM
    Modules linked in:
    CPU: 0 PID: 3 Comm: mykthread Not tainted 4.0.0 #30
    Hardware name: ARM-Versatile Express
    task: 8c048980 ti: 8c05e000 task.ti: 8c05e000
    PC is at 0x0
    LR is at 0x0
    pc : [<00000000>]    lr : [<00000000>]    psr: 00000013
    sp : 8c05fff8  ip : 00000000  fp : 00000000
    r10: 00000000  r9 : 00000000  r8 : 00000000
    r7 : 00000000  r6 : 00000000  r5 : 00000000  r4 : 00000000
    r3 : 00000000  r2 : 00000000  r1 : 00000000  r0 : 00000000
    Flags: nzcv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment kernel
    Control: 10c5387d  Table: 6c21806a  DAC: 00000015
    Process mykthread (pid: 3, stack limit = 0x8c05e210)
    Stack: (0x8c05fff8 to 0x8c060000)
    ffe0:                                                       00000000 00000000
    Code: bad PC value
    [ end trace c752e7bdfd2aa50f ]
    

网上搜索的网页描述内核线程直接返回应该是正常的,在内核源码中没有搜索到线程退出的代码。

内核应该是监控某种事件或数据结构,当出现问题时,唤醒 mykthread 内核线程来调用用户态程序通知用户。这里直接以信号的形式触发事件来模拟

原书代码要实现的逻辑如下:

  1. 在 mykthread 中创建一个 waitqueue,挂起此内核线程等待事件发生

  2. 当事件发生后,在另外的地方调用 wake_up_interruptible(&myevent_waitqueue); 来唤醒 mykthread 内核线程以调用用户态程序通知用户。

修改内核线程,在收到信号后主动调用 do_exit(0) 来退出,测试发现这种情况下不会产生 oops 但是线程退出后变为了 Zombie 进程,这应该是没有父进程的原因。在 kthreadd 中没有看到这个 do_exit(0); 的调用,其实 kthreadd 自从创建后就不会退出

内核调用用户态程序示例

网上搜索到,除了修改 sysctl.c 来注册一个用户态 hepler 钩子之外,还可以动态的注册,相关的 demo 程序如下:

#include <linux/wait.h>
#include <linux/kmod.h>
#include <linux/module.h>
#include <linux/slab.h>

#define CMD_LENGTH 256

static char cmd_str[CMD_LENGTH];

static struct ctl_table_header *ctl_table_header;

static struct ctl_table ctl_table[] = {
    {
     .procname = "umode_helper",
     .data = cmd_str,
     .maxlen = sizeof(cmd_str),
     .mode = 0644,
     .proc_handler = proc_dostring,
     },
    {},
};

static int run_umode_handler_example(int arg)
{
    char *argv[3], *envp[4], *buffer = NULL;
    int i = 0, value;
	
	argv[i++] = cmd_str;
	
	if (!(buffer = kmalloc(32, GFP_KERNEL)))
		return -ENOMEM;

	sprintf(buffer, "TEST_VARIABLE=%s", "hello");
	
	if (!argv[0])
		return -EINVAL;

	argv[i] = NULL;

	i = 0;

    /* minimal command environment */
    envp[i++] = "HOME=/";
    envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
	envp[i++] = buffer;
	envp[i] = NULL;

    value = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
	
	kfree(buffer);
    return 0;
}

static int __init umode_handler_demo_init(void)
{
	ctl_table_header = register_sysctl_table(ctl_table);

    if (!ctl_table_header) {
        printk(KERN_ERR "ctl_table_header register failed\n");
		return -EINVAL;
    } 
	
	return 0;
}

static void __exit umode_handler_demo_exit(void)
{
	run_umode_handler_example(-1);

	if (ctl_table_header)
		unregister_sysctl_table(ctl_table_header);
}

module_init(umode_handler_demo_init);
module_exit(umode_handler_demo_exit);

MODULE_DESCRIPTION("umode demo");
MODULE_AUTHOR("longyu");
MODULE_LICENSE("GPL");

Makefile 文件内容如下:

MODULE_FILENAME=usermodehelper_demo

obj-m += ${MODULE_FILENAME}.o
KO_FILE=${MODULE_FILENAME}.ko

KROOT= /home/longyu/arm-linux-kernel/linux-4.0

modules:
	@${MAKE} -C ${KROOT} ARCH=arm CROSS_COMPILE=arm-none-eabi- M=${PWD} modules

clean:
	@${MAKE} -C ${KROOT} M=${PWD} clean
	rm -rf Modules.symvers modules.order

加载测试记录如下:

/tmp/host_files # insmod usermodehelper_demo.ko 
/tmp/host_files # cat /bin/test.sh 
#!/bin/sh

touch /test
echo $TEST_VARIABLE > /test
/tmp/host_files # echo "/bin/test.sh" > /proc/sys/umode_helper 
/tmp/host_files # cat /proc/sys/umode_helper 
/bin/test.sh
/tmp/host_files # 
/tmp/host_files # rmmod usermodehelper_demo.ko 
running here, argv[0] is /bin/test.sh
/tmp/host_files # cat /test
hello
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值