カーネルスレッドとは


Rev.3を表示中。最新版はこちら

カーネルスレッドで代表的ななものとして、プロセスIDが0のすべての親となるinitです。これはカーネル起動時作成されます。他にkeventd(ワークキュ)、kswapd(メモリー回収)、ksoftoirqd(ソフト割り込み)等があり、必要に応じて作成されたりいたします。

カーネルスレッドはカーネルの補助的な処理を行うものだと推測できても、ユーザプロセスとどう違うのでしょうか? 実はカーネルとしては、スケージューリングにおいてユーザプロセスと同じ物だということです。カーネルスレッドの作成は、ユーザプロセス作成と同じようにCLONE_VM属性でdo_fork関数で作成されます。すなわちカーネルとして1プロセスディスクリプターとして、処理しているに過ぎません。そうすることで、スケージューリングの中で、カーネルスレッドが動作することになり、全体的なパフォーマンスの効率化がはかられるわけです。

CLONE_VMは作成するプロセス(親)のメモリー空間を共有するという事ですが、実はここでのCLONE_VMの意味合いは、共有する目的でなく、あえてメモリー空間の実態を作成するのは意味がないゆえ、そのような無駄な処理を避けるということにあるようです。

ユーザプロセスはユーザモードとカーネルモードを行き来します。従ってユーザプロセスに割り当てられるメモリー空間は4G(ただしユーザモードは3G,カーネルモードは1Gとしてしかアクセスできません。)ですが、カーネルスレッドはメモリー空間を有していないのです。カーネルスレッドは、カーネルスレッドに切り替えるプロセスのメモリー空間で動作しています。タスクスイッチングでカーネルスレッドに切り替えられる毎に、その動作メモリー空間は異なる事になってしまいます。しかしカーネルスレッドはユーザメモリー空間(3G空間)を参照しません。参照するのはカーネルメモリー空間だけです。

ユーザプロセスはお互いに独立した異なるメモリー空間を有していますが、3G以降の1Gはカーネル空間として共通となっています。従ってカーネルスレッド自身にメモリー空間を有することなく、他の任意のユーザプロセスのメモリー空間で動作することが可能となっています。

タスク切り替えは、 schedule関数からcontext_switch関数をコールする事で行います。プロセスディスクリプターのtask_structには、メモリディスクリプターとしてmmとactive_mmの2つを有しています。mmはそのプロセスが有しているメモリ空間で、active_mmはそのプロセスが動作しているメモリ空間です。従ってユーザプロセスはmm=active_mmde、カーネルプロセスmm=NULLでmm=active_mmdeは切り替えるプロセスのmmとなります。(プログラム的にはカーネルスレッドからカーネルスレッドへの切り替えがあるわけで、mm=active_mmとしている。)

context_switch関数で上記の処理が行われます。mm = next->mmで次に動作するプロセスが有しているメモリーディスクリプタを取得し、oldmm = prev->active_mmで前の(現動作している。)プロセスが動作しているメモリー空間のメモリーディスクリプタを取得しています。

!mmというのは次に動作するプロセスがカーネルスレッドと言うことです。その場合、next->active_mm = oldmmで、現動作しているメモリー空間でカーネルスレッドが動作するようにしています。そしてそのメモリー空間カウントをインクリメントすることで、そのメモリー空間を有しているプロセスが削除されても、そのメモリー空間が削除されないようにしています。

!prev->mmは現動作プロセスがカーネルスレッドということです。カーネルスレッドはもう動作する必要がないのでprev->active_mm = NULLとしています。そしてカーネルスレッドが動作していたメモリー空間を、CPU変数にrq->prev_mm = oldmmとしています。これはfinish_task_switch関数で、切り替え後処理でその判断をするためです。
static inline void
context_switch(struct rq *rq, struct task_struct *prev,
              struct task_struct *next)
{
       struct mm_struct *mm, *oldmm;

   :
   :
       mm = next->mm;
       oldmm = prev->active_mm;
   :
   :
       if (unlikely(!mm)) {
               next->active_mm = oldmm;
               atomic_inc(&oldmm->mm_count);
       } else
               switch_mm(oldmm, mm, next);

       if (unlikely(!prev->mm)) {
               prev->active_mm = NULL;
               rq->prev_mm = oldmm;
       }
       switch_to(prev, next, prev);
    :
       finish_task_switch(this_rq(), prev);
}
finish_task_switch関数切り替えられたプロセスの後処理を行います。ここではそれがカーネルスレッドということで。rq->prev_mmがNULLでないなら、それはカーネルスレッドが実行権が無くなったことを意味します。そしてmmdrop関数を呼び出して、そのメモリー空間の参照カウントをデクリメントし、(カーネルスレッドが動作する場合インクリメントしていました。)。そしてそれが0(参照されていない)ならメモリー空間そのもの解放します。
static void finish_task_switch(struct rq *rq, struct task_struct *prev)
       __releases(rq->lock)
{
       struct mm_struct *mm = rq->prev_mm;
   :
       rq->prev_mm = NULL;
   :
       if (mm)
               mmdrop(mm);
   :
}

static inline void mmdrop(struct mm_struct * mm)
{
       if (unlikely(atomic_dec_and_test(&mm->mm_count)))
               __mmdrop(mm);
}

nbystさんへ

[root@localhost ~]# ps x | more
 PID TTY      STAT   TIME COMMAND
   1 ?        Ss     0:06 /sbin/init
   2 ?        S      0:00 [kthreadd]
   3 ?        S      0:00 [ksoftirqd/0]
   5 ?        S      0:00 [kworker/u:0]
   6 ?        S      0:00 [migration/0]
   7 ?        S      0:00 [watchdog/0]
   8 ?        S      0:00 [migration/1]
 :
改めてinitのpidが1になる旨の実装を追ってみたいと思います。

最終更新 2013/08/18 14:34:31 - north
(2010/10/12 05:15:09 作成)


検索

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