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

Linux:実行中プロセスの名前空間切り替え

kernel linux

Linux名前空間に関して設定できるのはclone(2)、unshare(2)、setns(2)で、このうち実行中のプロセスの名前空間を変更できるのは後者の2個なんですが、カーネル側ではkernel/nsproxy.cにあるswitch_task_namespaces()が実際の処理を行ってます。そんなわけでこの辺りのコードを見てみます。

まず、switch_task_namespaces()を呼んでいる箇所ですが以下の4つがありました

  1. unshareシステムコールの実行時
  2. setnsシステムコールの実行時
  3. exitシステムコール実行時
  4. copy_process()のエラー処理

このうち1、2は実行中のプロセスの名前空間を切り替えるため、3はプロセスの終了処理の一環で使用していた名前空間をプロセスから参照できなくさせるため、最後は名前空間を設定したけどプロセスの生成に失敗したので後始末というところです。

unshare(2)の場合はunshare()でこのように呼び出しています。

1864         err = unshare_nsproxy_namespaces(unshare_flags, &new_nsproxy,
1865       
〜略〜
1877                 if (new_nsproxy)
1878                         switch_task_namespaces(current, new_nsproxy);
1879 

setns(2)の場合はsetns()でこのように。tskはcurrentです。

247         new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
〜略〜
258         switch_task_namespaces(tsk, new_nsproxy);

さて、switch_task_namespaces()ですがこれは全部で20行ほどの関数です。might_sleep()はデバッグ機能のCONFIG_DEBUG_ATOMIC_SLEEPがyになっていなければ何もしないのでコンパイル時の最適化で消されているはずです。

201 void switch_task_namespaces(struct task_struct *p, struct nsproxy *new)
202 {
203         struct nsproxy *ns;
204 
205         might_sleep();
206 
207         ns = p->nsproxy;
208 
209         rcu_assign_pointer(p->nsproxy, new);
210 
211         if (ns && atomic_dec_and_test(&ns->count)) {
212                 /*
213                  * wait for others to get what they want from this nsproxy.
214                  *
215                  * cannot release this nsproxy via the call_rcu() since
216                  * put_mnt_ns() will want to sleep
217                  */
218                 synchronize_rcu();
219                 free_nsproxy(ns);
220         }
221 }
222 

この関数の内容はrcu_assign_pointer()で現在設定されている名前空間のへのポインタへnewをセット。そして、使用していた名前空間がNULLでなく、リファレンスカウンタをデクリメントした結果countが0になった場合、名前空間が設定されていてかつこのプロセスの名前空間(p->nsproxy)を誰も使っていない、場合に名前空間を解放しています。
free_nsproxy()は特に面白いことはやっていなくて、各名前空間(uts、mount等)の参照カウンタを減らし、最後にnsproxy構造体のインスタンスをkmem_cache_free()で解放するだけです。

unshare()、setns()の場合、switch_task_namespaces()の呼び出し前には新しく作ったstruct nsproxyを渡しています。setns()で使っているcreate_new_namespaces()に関してはcreate_new_namespaces()めもで軽くまとめています。

unshare_nsproxy_namespaces()はというと、こちらもそんなに大したことはやっていないので軽く見てしまいます。

176 int unshare_nsproxy_namespaces(unsigned long unshare_flags,
177         struct nsproxy **new_nsp, struct cred *new_cred, struct fs_struct *new_fs)
178 {
179         struct user_namespace *user_ns;
180         int err = 0;
181 
182         if (!(unshare_flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
183                                CLONE_NEWNET | CLONE_NEWPID)))
184                 return 0;
185 
186         user_ns = new_cred ? new_cred->user_ns : current_user_ns();
187         if (!ns_capable(user_ns, CAP_SYS_ADMIN))
188                 return -EPERM;
189 
190         *new_nsp = create_new_namespaces(unshare_flags, current, user_ns,
191                                          new_fs ? new_fs : current->fs);
192         if (IS_ERR(*new_nsp)) {
193                 err = PTR_ERR(*new_nsp);
194                 goto out;
195         }
196 
197 out:
198         return err;
199 }

最初に各名前空間のフラグをチェックして1つも設定されていなければ0を返して関数を抜けますが、この場合はnew_nspが設定されないのでunshare()内の以下のif文のチェックに引っかかってswitch_task_namespaces()は呼ばれません。

1877                 if (new_nsproxy)
1878                         switch_task_namespaces(current, new_nsproxy);

その後、ケーパビリティのチェックをして問題なければcreate_new_namespaces()で新しい名前空間インスタンスを作成という流れです。

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース