创建内核线程并通过内核线程调用用户态程序
这里使用的内核为 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 的命令名称。
原书中的代码存在以下几个问题:
-
waitqueue 暂时不知道咋样测试,就用信号来搞
-
接收到信号后要调用 flush_signals() 来清除 task struct 中的标志字段,不然会一直触发
-
flush_signals 函数调用前获取 sighand 自旋锁会卡死,应该是此时自旋锁已经被占用
-
不需要执行 signal_allow 来显示的使能信号,调用 sigismember 宏判断的时候会失败
-
当内核线程直接返回时,有如下 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 内核线程来调用用户态程序通知用户。这里直接以信号的形式触发事件来模拟。
原书代码要实现的逻辑如下:
-
在 mykthread 中创建一个 waitqueue,挂起此内核线程等待事件发生
-
当事件发生后,在另外的地方调用 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