前回の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 */
新装改訂版 Linuxのブートプロセスをみる (アスキー書籍)
- 作者: 白崎博生
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/10/02
- メディア: Kindle版
- この商品を含むブログ (2件) を見る