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

fork/clone時に返すpidの設定(2)

linux kernel

前回のfork/clone時に返すpidの設定(1) - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモの続きです。alloc_pid()を見ていきます。

その前に、pid構造体のnumbers変数はサイズ1の配列として宣言されていますが、pidの処理ではindex番号1とかそれ以上の場所にアクセスしてます。

 57 struct pid
 58 {
 59         atomic_t count;
 60         unsigned int level;
 61         /* lists of tasks that use this pid */
 62         struct hlist_head tasks[PIDTYPE_MAX];
 63         struct rcu_head rcu;
 64         struct upid numbers[1];
 65 };

これはnumbers変数が構造体の最後の位置に宣言されているというところが重要です。pid構造体はこのようにメモリ確保をします。

306         pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);

allocするサイズはns->pid_cachepです。このcachepはkernel/pid_namespace.cのcreate_pid_cachep()で設定していて、このようになってます。

 53         cachep = kmem_cache_create(pcache->name,
 54                         sizeof(struct pid) + (nr_ids - 1) * sizeof(struct upid),
 55                         0, SLAB_HWCACHE_ALIGN, NULL);

sizeof(struct pid) + (nr_ids - 1) * sizeof(struct upid)がサイズの部分です。sizeof(struct pid)はそのままですね。これにnr_ids-1で、これはpid名前空間の階層数にで、この階層数分のupid構造体のサイズが足されます。これによって、pid構造体のサイズを動的に(pid名前空間を新規に作成した時)に決めるようになっています。そのため、pid.hで宣言された配列のサイズ以上の場所にアクセスしても問題ないようになっています。

この構造体の最後にサイズ1の配列をおいて宣言時のサイズ(sizeof (struct foobar))よりも大きなメモリをallocして動的な感じで処理するのはカーネルではちょくちょく見る手法です。

それでは、本題のalloc_pid()に進みます。

まず、alloc_pid()はこのように呼びます。引数で渡しているのはプロセスが所属するpid名前空間です。

1454         if (pid != &init_struct_pid) {
1455                 pid = alloc_pid(p->nsproxy->pid_ns_for_children);

で、alloc_pid()を見ていきまして、最初にスラブアロケーター(slub or slab or slob)よりpid構造体のメモリを確保します。確保するサイズは最初に書いたとおりです。

297 struct pid *alloc_pid(struct pid_namespace *ns)
298 {
299         struct pid *pid;
300         enum pid_type type;
301         int i, nr;
302         struct pid_namespace *tmp;
303         struct upid *upid;
304         int retval = -ENOMEM;
305 
306         pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
307         if (!pid)
308                 return ERR_PTR(retval);
309 

ここはpid名前空間の階層が何段階目かを設定していますね。

310         tmp = ns;
311         pid->level = ns->level;

ここがプロセス番号を割り当てるメインな箇所です。最下部の階層(現在のpid名前空間)から上位(init_struct_pidの名前空間)に向かって各階層の名前空間ごとにプロセス番号の割当を行います。

312         for (i = ns->level; i >= 0; i--) {
313                 nr = alloc_pidmap(tmp);
314                 if (IS_ERR_VALUE(nr)) {
315                         retval = nr;
316                         goto out_free;
317                 }
318 
319                 pid->numbers[i].nr = nr;
320                 pid->numbers[i].ns = tmp;
321                 tmp = tmp->parent;
322         }
323

これは、ホストとコンテナの関係で言うと、コンテナ内のプロセスのpidが1だったとしてもホストから見た場合はpid 1000として認識できるようになってます。 例えば、lwn.netにあるns_child_exec.cでpid名前空間を分離すると、新しい名前空間ではbashがpid1になっています。

masami@saga:~$ sudo ./ns_child_exec -p /bin/bash
[root@saga masami]# echo $$
1

でも、ホスト側から見るとpidは20265というのがわかります。

           │               ├─tmux(1554)─┬─bash(1557)───sudo(20263)───ns_child_exec(20264)───bash(20265)
           │               │            └─bash(18839)───pstree(20292)
 

pid名前空間はこのような感じで階層化していて、元の環境から完全に切り離されるわけではないという仕様になっています。

話は戻って、空いているプロセス番号を見つけるのはalloc_pidmap()の仕事です。プロセス番号の管理はビットマップで行っていて、そのビットマップから空いている番号を探します。詳細は省略します。

is_child_reaper()はプロセス番号がこの名前空間において1番か調べて、1ならtrueが返ります。pid_ns_prepare_proc()は/procのマウント処理みたいです。pid_namespace構造体のproc_mntに値が設定されます。

324         if (unlikely(is_child_reaper(pid))) {
325                 if (pid_ns_prepare_proc(ns))
326                         goto out_free;
327         }
328 

ちなみにstapでpid_ns_prepare_proc()に来た時にpid、commとバックトレースを出して確認した結果がこちらです。

comm(ns_child_exec) pid(4771)
 0xffffffff8124af70 : pid_ns_prepare_proc+0x0/0x30 [kernel]
 0xffffffff8109203c : alloc_pid+0x42c/0x470 [kernel]
 0xffffffff81074d25 : copy_process+0x11e5/0x1a70 [kernel]
 0xffffffff81075731 : _do_fork+0x91/0x360 [kernel]
 0xffffffff81075aa9 : SyS_clone+0x19/0x20 [kernel]
 0xffffffff8158f36e : entry_SYSCALL_64_fastpath+0x12/0x71 [kernel]

get_pid_ns()名前空間の参照数を増やします。そして、pid構造体にあるcountを1に初期化します。

329         get_pid_ns(ns);
330         atomic_set(&pid->count, 1);

リストの初期化します。

331         for (type = 0; type < PIDTYPE_MAX; ++type)
332                 INIT_HLIST_HEAD(&pid->tasks[type]);
333 

名前空間の階層にあったupid構造体を取得します。

334         upid = pid->numbers + ns->level;

残りはリストの初期化ですね。最後にpid構造体を返して終了です。

335         spin_lock_irq(&pidmap_lock);
336         if (!(ns->nr_hashed & PIDNS_HASH_ADDING))
337                 goto out_unlock;
338         for ( ; upid >= pid->numbers; --upid) {
339                 hlist_add_head_rcu(&upid->pid_chain,
340                                 &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
341                 upid->ns->nr_hashed++;
342         }
343         spin_unlock_irq(&pidmap_lock);
344 
345         return pid;
346 
347 out_unlock:
348         spin_unlock_irq(&pidmap_lock);
349         put_pid_ns(ns);
350 
351 out_free:
352         while (++i <= ns->level)
353                 free_pidmap(pid->numbers + i);
354 
355         kmem_cache_free(ns->pid_cachep, pid);
356         return ERR_PTR(retval);
357 }

pidは今の名前空間からpid名前空間の階層を登って行ってinit_struct_pidまでの全ての名前空間のpidを設定しつつ、clone/forkで返すのは所属する名前空間のpidということになります。

pidの取得ですがinclude/linux/pid.hに使用する関数の説明があります。

153 /*
154  * the helpers to get the pid's id seen from different namespaces
155  *
156  * pid_nr()    : global id, i.e. the id seen from the init namespace;
157  * pid_vnr()   : virtual id, i.e. the id seen from the pid namespace of
158  *               current.
159  * pid_nr_ns() : id seen from the ns specified.
160  *
161  * see also task_xid_nr() etc in include/linux/sched.h
162  */