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

CLONE_NEWPIDを調べる:getpid()とpid namespace

kernel linux

CLONE_NEWPIDをclone(2)のflagsに付けて新しいプロセスを親プロセスと別のpid namaspaceで動かすことができますね。この時にどうやってpidを正しくプロセスに見せているのか知りたかったのでgetpid()を見てみました。

まずこんなコードを動かしてみます。あ、子プロセス側ではprocfsを別途mountしていないので子プロセスからpsすると親プロセスのpid namespaceが見える状況でやってます。

static int tst_func(void *arg)
{
    show_pid("cloned process");
    show_uid("cloned process");

    mount_proc();

    execlp("bash", "bash", NULL);
    return 0;
}


int main(int argc, char **argv)
{
~略~
    int clone_flags = CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD;

    stack = make_stack();
    if (!stack) {
        fprintf(stderr, "faild to create stack\n");
        return -1;
    }

    show_pid("orig process");
    show_uid("orig process");

    pid = clone(&tst_func, stack, clone_flags, NULL);
    if (pid == -1) {
        perror("clone");
        exit(-1);
    }
    printf("child process id is %d\n", pid);
~略~

実行結果はこうなって子プロセスでは自分のpidは1と見えていますが、親プロセスからはこいつはpid 28615と見えています。

masami@saga:~$ sudo ./a.out
[sudo] password for masami:
orig process's pid is 28614
orig process's uid is 0
child process id is 28615
cloned process's pid is 1
cloned process's uid is 0
[root@saga masami]# echo $$
1

このときに別途pstreeなどをするとこんな感じで見えますね。

           ├─systemd-udevd(199)
           ├─tmux(819)─┬─bash(820)───ssh(26795)
           │           ├─bash(14220)───sudo(28596)───a.out(28614)───bash(28615)
           │           ├─bash(21440)───pstree(28652)
           │           └─bash(28453)
 

ではここからgetpid()を読んでいきましょう。 pidを読みだすと言ってもcurrent->pidをそのまま返すわけではなく、pid namespaceの関係で以下のような流れで処理が行われています。

getpid()
  -> task_tgid_vnr()
    -> task_tgid()
    -> pid_vnr()
        -> task_active_pid_ns()
          -> task_pid()
          -> ns_of_pid()
        -> pid_nr_ns()

getpid()はこのようにtask_tgid_vnr()を呼び出して、その戻り値をそのまま返す。

809 SYSCALL_DEFINE0(getpid)
810 {
811         return task_tgid_vnr(current);
812 }
813 

task_tgid_vnr()はpid_vnr()の戻り値を返す。

1764 static inline pid_t task_tgid_vnr(struct task_struct *tsk)
1765 {
1766         return pid_vnr(task_tgid(tsk));
1767 }

pid_vnr()は引数としてtask_tgid()の戻り値(struct pid *)を渡す。このtask_tgid()はcurrentタスクのgroup_leaderのpidを返す。

1702 static inline struct pid *task_tgid(struct task_struct *task)
1703 {
1704         return task->group_leader->pids[PIDTYPE_PID].pid;
1705 }

pid_vnr()はグループリーダーのpid構造体を受け取り、pid_nr_ns()の戻り値を返す。

509 pid_t pid_vnr(struct pid *pid)
510 {
511         return pid_nr_ns(pid, task_active_pid_ns(current));
512 }

まずはtask_active_pid_ns()を見てみると、ns_of_pid()が呼ばれる。task_pid()もstruct pid *を返す。

540 struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
541 {
542         return ns_of_pid(task_pid(tsk));
543 }

task_pid()を見るとついにcurrentのpidが返る模様。

1697 static inline struct pid *task_pid(struct task_struct *task)
1698 {
1699         return task->pids[PIDTYPE_PID].pid;
1700 }

ns_of_pid()はpid構造体に関連しているpid namespaceを返す。

134 static inline struct pid_namespace *ns_of_pid(struct pid *pid)
135 {
136         struct pid_namespace *ns = NULL;
137         if (pid)
138                 ns = pid->numbers[pid->level].ns;
139         return ns;
140 }

ここまででpid_nr_ns()の2番目の引数のpid namespaceが取得できたのでpid_nr_ns()を見る。ここでlevelの比較があるのはプロセスのpid namespaceに応じたpidを返すためでしょうね。

495 pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
496 {
497         struct upid *upid;
498         pid_t nr = 0;
499 
500         if (pid && ns->level <= pid->level) {
501                 upid = &pid->numbers[ns->level];
502                 if (upid->ns == ns)
503                         nr = upid->nr;
504         }
505         return nr;
506 }