kthread_runマクロ


kthread_runマクロはカーネルスレッドを作成し、起動(アクティブキューに接続)に繋ぐ。カーネルスレッドはユーザプロセスのマルチスレッドと同じような感じ。異なるのは、ユーザプロセスの親もプロセスとして動作するが、カーネルスレッドの親は、カーネル本体で、このカーネルはプロセスとして動作しているわけでない。

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:3695328
rmmod時に、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, &param);
               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(&regs, 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, &regs, 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マクロ、大体こんな感じみたい。

最終更新 2012/07/25 20:06:35 - north
(2012/07/24 17:03:22 作成)


検索

アクセス数
3693018
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。