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

nsproxyとfork()周りのめも

kernel linux

軽くnsproxyと初期化周りのめもを。

Linuxでは名前空間の管理にはnsproxy構造体が使われていて定義はinclude/linux/nsproxy.hにある。 中身はこのような形で5つの名前空間が管理されている。

 29 struct nsproxy {
 30         atomic_t count;
 31         struct uts_namespace *uts_ns;
 32         struct ipc_namespace *ipc_ns;
 33         struct mnt_namespace *mnt_ns;
 34         struct pid_namespace *pid_ns_for_children;
 35         struct net           *net_ns;
 36 };

これらの名前空間は基本的にはdo_fork()の時に親プロセスのものがコピーされる。基本的にはと言ったので基本的じゃない場合としてはclone(2)で明示的に名前空間を分ける場合、例えばCLONE_NEWPIDをフラグに指定してpid名前空間を親プロセスと分けるということもあるし、unshare(2)で後からmount名前空間を分けるということも可能。

ここでfork(2)でコピーされる名前空間の大元はinclude/linux/nsproxy.hにあるinit_nsproxy。

 37 extern struct nsproxy init_nsproxy;

この変数はkernel/nsproxy.cでこのように設定される。mnt_nsがnullなのは静的に決められないからしょうがないところですかね。

 31 struct nsproxy init_nsproxy = {
 32         .count                  = ATOMIC_INIT(1),
 33         .uts_ns                 = &init_uts_ns,
 34 #if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
 35         .ipc_ns                 = &init_ipc_ns,
 36 #endif
 37         .mnt_ns                 = NULL,
 38         .pid_ns_for_children    = &init_pid_ns,
 39 #ifdef CONFIG_NET
 40         .net_ns                 = &init_net,
 41 #endif
 42 };

init_nsproxyはまずinit_taskへ設定される。

 17 /* Initial task structure */
 18 struct task_struct init_task = INIT_TASK(init_task);
 19 EXPORT_SYMBOL(init_task);

INIT_TASKマクロによってこのように展開される。

210         .nsproxy        = &init_nsproxy,   

fork()系の処理で親プロセスのnsproxyを子プロセスへコピーするのはarch_dup_task_struct()で実施。x86_64の場合はarch/x86/kernel/process.cのarch_dup_task_struct()でsrcをdstにコピーしている部分が該当。

 65 int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
 66 {
 67         int ret;
 68 
 69         *dst = *src;
 70         if (fpu_allocated(&src->thread.fpu)) {
 71                 memset(&dst->thread.fpu, 0, sizeof(dst->thread.fpu));
 72                 ret = fpu_alloc(&dst->thread.fpu);
 73                 if (ret)
 74                         return ret;
 75                 fpu_copy(dst, src);
 76         }
 77         return 0;
 78 }
 79 

流れとしてはこのような形。

copy_process()
  -> dup_task_struct()
    -> arch_dup_task_struct()

さて、arch_dup_task_struct()で親プロセスの名前空間が子プロセスにコピーされたけどまだ処理としてhこれだけではなくて、copy_namespaces()による処理がある。
この処理はcopy_process()から呼び出される。

1349         retval = copy_namespaces(clone_flags, p);

copy_namespaces()はkernel/nsproxy.cにあり、比較的短い処理。
最初にflags(do_fock()での引数名だとclone_flags)のチェックで、新規に名前空間を作る必要がない場合はget_nsproxy()でinit_nsproxy構造体のcount変数をインクリメントして参照数を増やして終了。

124 int copy_namespaces(unsigned long flags, struct task_struct *tsk)
125 {
126         struct nsproxy *old_ns = tsk->nsproxy;
127         struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
128         struct nsproxy *new_ns;
129 
130         if (likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
131                               CLONE_NEWPID | CLONE_NEWNET)))) {
132                 get_nsproxy(old_ns);
133                 return 0;
134         }
135 

次はケーパビリティのチェック。

136         if (!ns_capable(user_ns, CAP_SYS_ADMIN))
137                 return -EPERM;
138 

flagsでCLONE_NEWIPCがセットされていた場合のチェック。

139         /*
140          * CLONE_NEWIPC must detach from the undolist: after switching
141          * to a new ipc namespace, the semaphore arrays from the old
142          * namespace are unreachable.  In clone parlance, CLONE_SYSVSEM
143          * means share undolist with parent, so we must forbid using
144          * it along with CLONE_NEWIPC.
145          */
146         if ((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
147                 (CLONE_NEWIPC | CLONE_SYSVSEM)) 
148                 return -EINVAL;
149

ここまでで特に問題が内容なら新規に名前空間を作るためにcreate_new_namespaces()を呼び出す。

150         new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);
151         if (IS_ERR(new_ns))
152                 return  PTR_ERR(new_ns);
153 
154         tsk->nsproxy = new_ns;
155         return 0;
156 }

ここまでがfork()系の関数で名前空間に関する設定をしている部分の大枠。