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

initプロセスをstraceする

initプロセス ( pid = 1 ) を strace できることを昨日知りました

下のログは Ubuntu Server、カーネルは 2.6.38 で実行したものです

# strace -p 1 
Process 1 attached - interrupt to quit
select(11, [3 5 6 7 8 9 10], [], [7 8 9 10], NULL) = ? ERESTARTNOHAND (To be restarted)
--- SIGCHLD (Child exited) @ 0 (0) ---
write(4, "\0", 1)                       = 1
rt_sigreturn(0x4)                       = -1 EINTR (Interrupted system call)
read(3, "\0", 1)                        = 1
read(3, 0x7fff61bc5b9f, 1)              = -1 EAGAIN (Resource temporarily unavailable)
waitid(P_ALL, 0, {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3009, si_status=0, si_utime=0, si_stime=0}, WNOHANG|WEXITED|WSTOPPED|WCONTINUED, NULL) = 0
waitid(P_ALL, 0, {}, WNOHANG|WEXITED|WSTOPPED|WCONTINUED, NULL) = 0
select(11, [3 5 6 7 8 9 10], [], [7 8 9 10], NULL

Ubuntuの/sbin/init は upstart なので、select(2)でイベントを待機している様子を観測できますね。あとwaitid(2)ゾンビプロセスを回収する様子なども。

古いカーネルでは strace できない

もう少し深追いして調べてみたら、古めのカーネルだと pid = 1 のプロセスはstraceできなかったようです。CentOS5で試した所 strace内で呼び出す ptrace(2) が EPERMで弾かれます。


手元にあった 2.6.18系の /kernel/ptrace.c のソースを読んでみました。

int ptrace_attach(struct task_struct *task)
{
        int retval;

        retval = -EPERM;
        if (task->pid <= 1)
                goto out;

        /* 略 */

out:
         return retval;
}

pid が1 以下の場合は無条件で EPERM返すようです


strace -p 1 している時に kill -KILL 1 すると カーネルパニックを起こす


initプロセス ( pid = 1 ) のプロセスに SIGKILL を送ると通常は何も起こらないのですが、straceしている時に SIGKILL を送るとカーネルパニックを起こしました

ptrace(2)でアタッチするとシグナル処理の条件が変わって このような挙動になるのでしょうか?


どこでカーネルパニックを起こしているのか 2.6.38のソースを読んでみました

static struct task_struct *find_new_reaper(struct task_struct *father)
        __releases(&tasklist_lock)
        __acquires(&tasklist_lock)
{
        struct pid_namespace *pid_ns = task_active_pid_ns(father);
        struct task_struct *thread;

        thread = father;
        while_each_thread(father, thread) {
                if (thread->flags & PF_EXITING)
                        continue;
                if (unlikely(pid_ns->child_reaper == father))
                        pid_ns->child_reaper = thread;
                return thread;
        }    

        if (unlikely(pid_ns->child_reaper == father)) {
                write_unlock_irq(&tasklist_lock);

                /* ここ */
                if (unlikely(pid_ns == &init_pid_ns))
                        panic("Attempted to kill init!");

                zap_pid_ns_processes(pid_ns);
                write_lock_irq(&tasklist_lock);
                /*   
                 * We can not clear ->child_reaper or leave it alone.
                 * There may by stealth EXIT_DEAD tasks on ->children,
                 * forget_original_parent() must move them somewhere.
                 */
                pid_ns->child_reaper = init_pid_ns.child_reaper;
        }    

        return pid_ns->child_reaper;
}

initプロセス名前空間絡みの処理でpanicを起こすようです。

arch/x86/kernel/signal.c には下記のようなコメントがあります

/*
 * Note that 'init' is a special process: it doesn't get signals it doesn't
 * want to handle. Thus you cannot kill init even with a SIGKILL even by
 * mistake.
 */
static void do_signal(struct pt_regs *regs)

initプロセスをSIGKILLできてしまうのはバグっぽい感じもしますね。

シグナルを処理するコード周りを見てみましたが、力不足でよく分かりませんでした。後日トライ。

感想

LXC ( Linux Containers ) の検証をしていた際に、コンテナ内で pid = 1 となるプロセスはどういう役割を果たせばいいのかが分からないでいたのですが、 /sbin/init を strace してみて理解できた気がします。