読者です 読者をやめる 読者になる 読者になる

〆(.. )カリカリッ!! Linuxのidleスレッドめも

kernel linux

ふと気になったのでめも。

実際にidleスレッドを作るのはinit_idle()だけどそこに至までの流れは結構深い。
start_kernel()
 --> sched_init()
   --> init_idle()
 --> rest_init()
   --> kernel_init()
    --> kernel_init_freeable()
      --> smp_init()
        --> idle_threads_init()
           --> idle_init()
             --> fork_idle()
               --> init_idle()

init_idle()が呼ばれるのは見た限りでは2パターンがある模様。

  • start_kernel() -> sched_init() -> init_idle()
  • start_kernel() -> rest_init() -> 色々 -> init_idle()

ただし、sched_init()の場合は現在のcpu用につくるだけっぽい。

6539         /*
6540          * Make us the idle thread. Technically, schedule() should not be
6541          * called from this thread, however somewhere below it might be,
6542          * but because we are the idle thread, we just pick up running again
6543          * when this runqueue becomes "idle".
6544          */
6545         init_idle(current, smp_processor_id());

いずれにせよcpuコア分のidleスレッドは作る必要が有るのでidle_threads_init()でカーネルが認識している数分のループでidleスレッド作成。idle_threads_init()はsmp_init()から呼ばれる。

 64 void __init idle_threads_init(void)
 65 {
 66         unsigned int cpu, boot_cpu;
 67 
 68         boot_cpu = smp_processor_id();
 69 
 70         for_each_possible_cpu(cpu) {
 71                 if (cpu != boot_cpu)
 72                         idle_init(cpu);
 73         }
 74 }

ここの71行目のif文でboot_cpuでない場合ってチェックがあるのはsched_init()ですでにidleスレッドを作っているからかな。。
start_kernel()からsched_init()を呼んだ時点ではsmp_init()も呼ばれていないのでcpuは1個しか使われていないはず。

idle_initではcpu毎のtask構造体にデータを設定する(fork_idle()で)。

 48 static inline void idle_init(unsigned int cpu)
 49 {
 50         struct task_struct *tsk = per_cpu(idle_threads, cpu);
 51 
 52         if (!tsk) {
 53                 tsk = fork_idle(cpu);
 54                 if (IS_ERR(tsk))
 55                         pr_err("SMP: fork_idle() failed for CPU %u\n", cpu);
 56                 else
 57                         per_cpu(idle_threads, cpu) = tsk;
 58         }
 59 }
 60 

idle_threadsはkernel/smpboot.c にて定義。

 20 #ifdef CONFIG_GENERIC_SMP_IDLE_THREAD
 21 /*
 22  * For the hotplug case we keep the task structs around and reuse
 23  * them.
 24  */
 25 static DEFINE_PER_CPU(struct task_struct *, idle_threads);

fork_idle()は名前から予想が付く通りでプロセスをforkする。

1550 struct task_struct *fork_idle(int cpu)
1551 {
1552         struct task_struct *task;
1553         task = copy_process(CLONE_VM, 0, 0, NULL, &init_struct_pid, 0);
1554         if (!IS_ERR(task)) {
1555                 init_idle_pids(task->pids);
1556                 init_idle(task, cpu);
1557         }
1558 
1559         return task;
1560 }

copy_process()のプロトタイプは以下の通り。

1131 static struct task_struct *copy_process(unsigned long clone_flags,
1132                                         unsigned long stack_start,
1133                                         unsigned long stack_size,
1134                                         int __user *child_tidptr,
1135                                         struct pid *pid,
1136                                         int trace)
1137 {

fork_idle()ではcopy_process()のpid引数にinit_struct_pid構造体へのポインタを渡していて、この構造体はkernel/pid.cで初期化されている。

 46 struct pid init_struct_pid = INIT_STRUCT_PID;

INIT_STRUCT_PIDマクロはと言うと、linux/init_task.hにて定義されている構造体を初期化するためのマクロ。

 71 #define INIT_STRUCT_PID {                                               \
 72         .count          = ATOMIC_INIT(1),                               \
 73         .tasks          = {                                             \
 74                 { .first = NULL },                                      \
 75                 { .first = NULL },                                      \
 76                 { .first = NULL },                                      \
 77         },                                                              \
 78         .level          = 0,                                            \
 79         .numbers        = { {                                           \
 80                 .nr             = 0,                                    \
 81                 .ns             = &init_pid_ns,                         \
 82                 .pid_chain      = { .next = NULL, .pprev = NULL },      \
 83         }, }                                                            \
 84 }

copy_process()に戻って、このpid構造体が使われる場所は2ヶ所。
1ヶ所はこのチェック部分で、pidがinit_struct_pid出ない場合にpid構造体のメモリを確保する時。idleスレッド作成時はこのif文内には入りませんが。

1353         if (pid != &init_struct_pid) {
1354                 retval = -ENOMEM;
1355                 pid = alloc_pid(p->nsproxy->pid_ns_for_children);
1356                 if (!pid)
1357                         goto bad_fork_cleanup_io;
1358         }

2ヶ所目はここでidleスレッドのpidを決定するのと、このスレッドのスレッドグループIDを決定する部分。

1394         /* ok, now we should be set up.. */
1395         p->pid = pid_nr(pid);
1396         if (clone_flags & CLONE_THREAD) {
1397                 p->exit_signal = -1;
1398                 p->group_leader = current->group_leader;
1399                 p->tgid = current->tgid;
1400         } else {
1401                 if (clone_flags & CLONE_PARENT)
1402                         p->exit_signal = current->group_leader->exit_signal;
1403                 else
1404                         p->exit_signal = (clone_flags & CSIGNAL);
1405                 p->group_leader = p;
1406                 p->tgid = p->pid;
1407         }

pid_nr()はinclude/linux/pid.hで定義されていて

164 static inline pid_t pid_nr(struct pid *pid)
165 {
166         pid_t nr = 0;
167         if (pid)
168                 nr = pid->numbers[0].nr;
169         return nr;
170 }

pid構造体のnumbers配列の0番目の要素のnrの値を返しています。今使っているpid構造体はINIT_STRUCT_PIDで初期化されているのでこれを見ると、

 78         .level          = 0,                                            \
 79         .numbers        = { {                                           \
 80                 .nr             = 0,                                    \
 81                 .ns             = &init_pid_ns,                         \
 82                 .pid_chain      = { .next = NULL, .pprev = NULL },      \
 83         }, }                           

nrは0となりますね。ということでpidは0。pidが決まったら次は以下の分岐ですが、fork_idle()ではclone_flagsにCLONE_VMしか設定していないのでelse文が実行されます。

1396         if (clone_flags & CLONE_THREAD) {
1397                 p->exit_signal = -1;
1398                 p->group_leader = current->group_leader;
1399                 p->tgid = current->tgid;
1400         } else {
1401                 if (clone_flags & CLONE_PARENT)
1402                         p->exit_signal = current->group_leader->exit_signal;
1403                 else
1404                         p->exit_signal = (clone_flags & CSIGNAL);
1405                 p->group_leader = p;
1406                 p->tgid = p->pid;
1407         }

よってtgidも0ですね。

copy_processが成功すればfork_idle()はinit_idle_pids()とinit_idle()を実行する。

1554         if (!IS_ERR(task)) {
1555                 init_idle_pids(task->pids);
1556                 init_idle(task, cpu);
1557         }

init_idle_pidsはこのような関数。

1540 static inline void init_idle_pids(struct pid_link *links)
1541 {
1542         enum pid_type type;
1543 
1544         for (type = PIDTYPE_PID; type < PIDTYPE_MAX; ++type) {
1545                 INIT_HLIST_NODE(&links[type].node); /* not really needed */
1546                 links[type].pid = &init_struct_pid;
1547         }
1548 }

PIDTYPE_PIDやPIDTYPE_MAXはinclude/linux/pid.hで定義されているenumの定数

  6 enum pid_type
  7 {
  8         PIDTYPE_PID,
  9         PIDTYPE_PGID,
 10         PIDTYPE_SID,
 11         PIDTYPE_MAX
 12 };

init_idle()に。
この関数でタスクの状態などが設定されたり、

4189         __sched_fork(idle);
4190         idle->state = TASK_RUNNING;
4191         idle->se.exec_start = sched_clock();

ランキューの設定が有ったり、

4208         rq->curr = rq->idle = idle;

スケジューラーの設定があったり、

4217         /*
4218          * The idle tasks have their own, simple scheduling class:
4219          */
4220         idle->sched_class = &idle_sched_class;

プロセス名の設定をするなどが行われる。

4223 #if defined(CONFIG_SMP)
4224         sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu);
4225 #endif

idle->commで設定された名前は例えばkmeleakで見た場合のcommで確認できますね。

unreferenced object 0xffff8808529e4960 (size 32):
  2   comm "swapper/0", pid 1, jiffies 4294877548 (age 8061.956s)
  3   hex dump (first 32 bytes):
  4     00 01 10 00 00 00 ad de 00 02 20 00 00 00 ad de  .......... .....
  5     00 14 8a 4e 08 88 ff ff 01 00 00 00 00 00 00 00  ...N............
  6   backtrace:
  7     [<ffffffff814db54e>] kmemleak_alloc+0x4e/0xb0
  8  

φ(・・*)ゞ ウーン init_idle()の中でスケジューラ関連の部分はよく分からないのでまた調べよう