kthread_runマクロ
kthread_runマクロはカーネルスレッドを作成し、起動(アクティブキューに接続)に繋ぐ。カーネルスレッドはユーザプロセスのマルチスレッドと同じような感じ。異なるのは、ユーザプロセスの親もプロセスとして動作するが、カーネルスレッドの親は、カーネル本体で、このカーネルはプロセスとして動作しているわけでない。
lkmで/procのコールバック処理を実装した場合、この関数はカーネル上の1パスの関数にしか過ぎない。当然カーネルスレッドからコールされる事もありうる。従ってその処理が終了しても(return)、イメージとしての実態はカーネル内に存在し続ける。しかしカーネルスレッドだと、カーネルパスの延長上で動作するのでなく、カーネルからスケジュールされるプロセスとして動作する。従って、その処理が終了したらプロセスとして存在しなくなる。ただしrmmodしない限り、イメージとしてメモリ上に存在する。なお、再度必要に応じてkthread_runすることも可能。
従って、lkmから作成したカーネルスレッド、lkmをrmmmodする時に、その前にそのカーネルスレッドを削除する必要がある。この処理を忘れると、実在しないカーネルスレッドに起動がかかるって事になる。(たぶん)
カーネルスレッド内の処理は、サービスの間retできない。上記のごとくretするとプロセスが削除されてしまう。カーネルの実装的には、カーネルスレッドとなる関数の復帰後に、do_exit関数がコールされるようになっている。
以下はlkm下のカーネルスレッドでjiffiesを表示させるサンプル。
lkmで/procのコールバック処理を実装した場合、この関数はカーネル上の1パスの関数にしか過ぎない。当然カーネルスレッドからコールされる事もありうる。従ってその処理が終了しても(return)、イメージとしての実態はカーネル内に存在し続ける。しかしカーネルスレッドだと、カーネルパスの延長上で動作するのでなく、カーネルからスケジュールされるプロセスとして動作する。従って、その処理が終了したらプロセスとして存在しなくなる。ただしrmmodしない限り、イメージとしてメモリ上に存在する。なお、再度必要に応じてkthread_runすることも可能。
従って、lkmから作成したカーネルスレッド、lkmをrmmmodする時に、その前にそのカーネルスレッドを削除する必要がある。この処理を忘れると、実在しないカーネルスレッドに起動がかかるって事になる。(たぶん)
カーネルスレッド内の処理は、サービスの間retできない。上記のごとくretするとプロセスが削除されてしまう。カーネルの実装的には、カーネルスレッドとなる関数の復帰後に、do_exit関数がコールされるようになっている。
以下はlkm下のカーネルスレッドでjiffiesを表示させるサンプル。
#include <linux/module.h> #include <linux/kthread.h> #include <linux/sched.h> #include <linux/jiffies.h> MODULE_AUTHOR("y.kitamura"); MODULE_DESCRIPTION("kthread test"); MODULE_LICENSE("GPL"); static struct task_struct *kthread_tsk; static void my_kthread_main(void) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ); printk("kthread:%ld\n", jiffies); } static int my_kthread(void *arg) { printk("HZ:%d\n", HZ); while (!kthread_should_stop()) { my_kthread_main(); } return 0; } static int __init kthread_test_init(void) { kthread_tsk = kthread_run(my_kthread, NULL, "param %s&%d", "test", 123); if (IS_ERR(kthread_tsk)) return -1; printk("pid->%d:prio->%d:comm->%s\n", kthread_tsk->pid, kthread_tsk->static_prio, kthread_tsk->comm); return 0; } static void __exit kthread_test_exit(void) { kthread_stop(kthread_tsk); } module_init(kthread_test_init); module_exit(kthread_test_exit);実行結果
[root@localhost lkm]# dmesg [ 3991.325662] pid->7712:prio->120:comm->param test&123 [ 3991.326134] HZ:1000 [ 3992.326479] kthread:3692326 [ 3993.326179] kthread:3693326 [ 3994.326339] kthread:3694326 [ 3995.328651] kthread:3695328rmmod時に、kthread_should_stopがコールされる様にする必要がある。kthread_should_stopはkthread->should_stop = 1として、カーネルスレッドを起床させ、wait_for_completionでスレッドが削除されるまでウエイトする。そして後、モジュールを削除する事が可能になる。
int kthread_should_stop(void) { return to_kthread(current)->should_stop; } int kthread_stop(struct task_struct *k) { struct kthread *kthread; int ret; trace_sched_kthread_stop(k); get_task_struct(k); kthread = to_kthread(k); barrier(); /* it might have exited */ if (k->vfork_done != NULL) { kthread->should_stop = 1; wake_up_process(k); wait_for_completion(&kthread->exited); } ret = k->exit_code; put_task_struct(k); trace_sched_kthread_stop_ret(ret); return ret; }
実装
kthread_runマクロはkthread_createマクロ展開し、dataを引数として、threadfnの関数をカーネルスレッドとして作成し、wake_up_process()でそのプロセスを起床させた後、タスク構造体を返す。#define kthread_run(threadfn, data, namefmt, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \ wake_up_process(__k); \ __k; \ })kthread_createマクロでkthread_create_on_node()がコールされ、新規作成するカーネルスレッドをkthread_create_listに接続す。このリストはカーネルスレッドkthreaddが参照し、新規スレッドを作成することになる。kthreaddを起床させているのが、wake_up_process(kthreadd_task)。kthreaddにより新規スレッドが起床ができたら、そのタスク構造体に、プライオリティ/コマンド引数/スケジューラクラスを設定し、タスク構造体を返す。なお引数のでnodeは、NUMのケースっぽい。
#define kthread_create(threadfn, data, namefmt, arg...) \ kthread_create_on_node(threadfn, data, -1, namefmt, ##arg) struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, const char namefmt[], ...) { struct kthread_create_info create; create.threadfn = threadfn; create.data = data; create.node = node; init_completion(&create.done); spin_lock(&kthread_create_lock); list_add_tail(&create.list, &kthread_create_list); spin_unlock(&kthread_create_lock); wake_up_process(kthreadd_task); wait_for_completion(&create.done); if (!IS_ERR(create.result)) { static const struct sched_param param = { .sched_priority = 0 }; va_list args; va_start(args, namefmt); vsnprintf(create.result->comm, sizeof(create.result->comm), namefmt, args); va_end(args); sched_setscheduler_nocheck(create.result, SCHED_NORMAL, ¶m); set_cpus_allowed_ptr(create.result, cpu_all_mask); } return create.result; }kthreaddで、kthread_create_listにリストされている新規作成待ちカーネルスレッドを、create_kthread関数で作成し、そのリストからそのノードを削除する。流れから見ると、新規カーネルスレッドの親はkthreaddってことかな?
int kthreadd(void *unused) { struct task_struct *tsk = current; set_task_comm(tsk, "kthreadd"); ignore_signals(tsk); set_cpus_allowed_ptr(tsk, cpu_all_mask); set_mems_allowed(node_states[N_HIGH_MEMORY]); current->flags |= PF_NOFREEZE; for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (list_empty(&kthread_create_list)) schedule(); __set_current_state(TASK_RUNNING); spin_lock(&kthread_create_lock); while (!list_empty(&kthread_create_list)) { struct kthread_create_info *create; create = list_entry(kthread_create_list.next, struct kthread_create_info, list); list_del_init(&create->list); spin_unlock(&kthread_create_lock); create_kthread(create); spin_lock(&kthread_create_lock); } spin_unlock(&kthread_create_lock); } return 0; }create_kthread関数からkernel_thread()をコールする事で、実際のカーネルスレッドが作成される。なお新規作成されるスレッド(create引数)は、kthread関数の引数となる。
static void create_kthread(struct kthread_create_info *create) { int pid; #ifdef CONFIG_NUMA current->pref_node_fork = create->node; #endif pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD); if (pid < 0) { create->result = ERR_PTR(pid); complete(&create->done); } }kernel_thread()で、やっとdo_forkをコールする。fn=kthread、arg=createだ。新規スレッドが作成されると、regs.ip = (unsigned long) kernel_thread_helperから動作することになる。と思う・・・。この関数はkernel_thread_helperってやつ。
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) { struct pt_regs regs; memset(®s, 0, sizeof(regs)); regs.si = (unsigned long) fn; regs.di = (unsigned long) arg; #ifdef CONFIG_X86_32 regs.ds = __USER_DS; regs.es = __USER_DS; regs.fs = __KERNEL_PERCPU; regs.gs = __KERNEL_STACK_CANARY; #else regs.ss = __KERNEL_DS; #endif regs.orig_ax = -1; regs.ip = (unsigned long) kernel_thread_helper; regs.cs = __KERNEL_CS | get_kernel_rpl(); regs.flags = X86_EFLAGS_IF | X86_EFLAGS_BIT1; return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); }kernel_thread_helperのcall *%esiで、kthread関数がコールされ、create->threadfnに設定してある、新規スレッドがコールされてるみたい。で、そこから復帰したらdo_exit()でスレッド終了処理している。
ENTRY(kernel_thread_helper) pushl $0 # fake return address for unwinder CFI_STARTPROC movl %edi,%eax call *%esi call do_exit ud2 # padding for call trace CFI_ENDPROC ENDPROC(kernel_thread_helper) static int kthread(void *_create) { struct kthread_create_info *create = _create; int (*threadfn)(void *data) = create->threadfn; void *data = create->data; struct kthread self; int ret; self.should_stop = 0; self.data = data; init_completion(&self.exited); current->vfork_done = &self.exited; __set_current_state(TASK_UNINTERRUPTIBLE); create->result = current; complete(&create->done); schedule(); ret = -EINTR; if (!self.should_stop) ret = threadfn(data); do_exit(ret); }kthread()から復帰すると、kernel_thread_helpeに戻るけど、その後再度、call do_exitしている。親としているkthreaスレッドも終了させるってことかな? kthread_runマクロ、大体こんな感じみたい。