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マクロ、大体こんな感じみたい。



