LoginSignup
17
15

More than 5 years have passed since last update.

カーネル探検(rest_init()からプロセス番号1生成まで)

Last updated at Posted at 2015-12-13

概要

処理概要

  • rest_init() ↓↓ 以下今までのコンテキスト(swapper)でschdule()まで実行される
    • kernel_thread(kernel_init,...)
      • kernel_thread(int (*fn)(void *)...)
      • regs.si = fn //レジスタSIにkernel_initを登録
      • regs.ip = kernel_thread_helper
      • do_fork()
        • p = copy_process()から始まる処理で親のコピーのプロセスディスクリプタ作成。このプロセスディスクリプタからポインタしている各資源は親と共有している
        • 作成された子プロセスは自プロセス(親プロセス)のほぼ完全な複製
        • レジスタの値は複製されたポイントに書き換えている(子プロセスを指す)
        • wake_up_new_task(p) 子プロセスをスケジューラに登録
        • kernel_threadに戻る → rest_init()
    • kernel_thread(kerneladd,...)
      • 要求受付リストを定期的に監視し、リクエストが登録されていればカーネルスレッドを生成し、とうおr区されていなければスケジューラにCPUを明け渡す処理を永久に繰り返す
      • ドライバなどからはkthread_create()を読んでドライバのカーネルスレッドを作成している
    • schdule()
      • 他のプロセスに譲渡。他のプロセスはいまのところ、kernel_initkerneladdのカーネルスレッド。このタイミングでそれぞれが実行される。
    • cpu_idle()
      • swapperプロセスは全てのプロセスがなくなったらここにくる。アイドルするだけ。コード詳細は以下参照
  • kerneladd //こっちの方が先に実行されるみたい
    • 詳細は下記に記載
  • kernel_init
    • do_bask_setup() → populate_rootfs() → upack_to_rootfs()
    • 既にメモリにあるinitramfsを解凍。ramfsを展開
    • init_post()
      • ramfsにアクセス可能であれば、ramfsの/initを実行しようとする
      • 不可能であれば、ディスクをルートファイルシステムとしてマウントする(prepare_namespace()
    • run_init_process()
      • kernel_execve(init_filename,...) //execveシステムコールを呼び出すだけ。アセンブラ
        • 第一引数は/init
        • call sys_execve
      • sys_execve() //execveシステムコール
        • マッピングを更新してテキストコードやスタックを入れ替え。mm_structのポインタなども子プロセスのものに変更する。仮想空間のアドレスも子になる。
      • do_execve() //ただdo_execve_commonを呼び出すだけ
      • do_execve_common()
        • open_exec() //initramfsの/initに対して
          • 実行権限があること
          • 読み込み権限があること
          • マウントオプションnoexecが指定されていること
        • sched_exec()
          • 実行するCPUを決定
        • bprm_mm_init()
          • 新しい仮想アドレス空間の作成。あとでmmapでここに独自のプログラムをマップする
          • スタックに環境変数、コマンドライン引数をコピー。*envp *argcでポインタ
        • search_binary_handler()
          • linux_binfmtのリストを先頭から順にアクセスしてそのメンバーのload_binary()の実行を試みる
        • load_script()
          • /initは#!/bin/shから始まるシェル救いなのでload_scriptが当てはまる
        • load_elf_binary()
          • /bin/shはEFL形式の実行ファイルなので、/bin/shをメモリにロードする。動的リンカ(/lib64/ld-linux-x86-64.so.2)を見つけ、まずは動的リンカをロードしなければならない
          • flsh_old_exec() = 仮想アドレス空間をdo_execve_common()のbprm_mm_init()で作成したものに切り替える(新規ページテーブルをレジスタCR3に再ロード)
          • プログラムファイルのプログラムヘッダを順にアクセスし、テキストデータ領域を仮想アドレス空間にmmapする。プロセスディスクリプタのmmをこのmapした子独自のものに切り替える。
          • 動的リンカもmmpaして仮想アドレスにマップ。ユーザーモードのスタック領域は0x7ffffffff000に割当られる。setup_arg_pages()によってマップされる
          • create_elf_tables() = インタプリタテーブル作成(プログラムのエントリポイントある)スタックに作成したコマンドライン引数、環境変数へのポインタの配列を作成する。
          • start_thread() = 今までのカーネルモードスタックレジスタの値を書き換える
          • IP ← elf_entry //動的リンカ
          • SP ← bprm->p
          • CS ← _USER_CS // ユーザーモード
          • SS ← _USER_DS // ユーザーモード
          • ELFAGS ← x86_EFLAGS_IF
          • ここから戻り、kernel_execve()に戻る。call sys_execve以降から再開する。iret命令実行。レジスタ復帰。ここでカーネルプロセスから初めてのユーザプロセスが誕生する
          • 動的リンカはインタープリタテーブル読み込み、共有ライブラリの再帰的なマップ。最後にELF実行ファイルのエントリポイントに戻り、/bin/sh /initが実行される。
    • /bin/sh /initの実行
      • コマンドラインオプションroot(これがマウントしたいディスクなどにあるLinuxルートファイルシステム)の読み込み
      • Linuxのルートファイルシステムのマウント
      • Linuxのルートファイルシステムへの移行
      • exec /usr/sbin/switch_root /sysroot (/usr/lib/systemd/systemd または /sbin/init)
    • ルートファイルシステムがマウントされ。cent6だと/sbin/initが実行。/etc/inittabをみにいく。

rest_init()からの全体の流れ

start_kernel()はrest_init()を呼び出して終わり。rest_init()の最後はCPUをアイドル状態(休止にする無限ループ)にするためstart_kernel()に戻ることはない。rest_initの最後でここまでのカーネル初期化(swapper)プロセスはただCPUをアイドル状態にするプロセスとなる。以降はスケジューラーによりプロセスが実行されていく。大事なのはkernel_init、kerneladdを生成してスケジューラに登録する。

  1. kelnel_thread()でkernel_initプロセスを生成する。まだこの時点では生成されただけで動かない。スケジューラーに登録されるのみ。(プロセスの生成)。kernel_initが/sbin/initをプロセスとして生成(プロセス番号1)そして起動させる。
  2. 他に実行可能状態のプロセスがあればCPUを譲る(なければアイドル状態にする)。無い場合はswapperプロセス(rest_initまで動いたプロセス)が動いて、CPUをアイドルにする関数を呼ぶ。1でスケジューラーに登録されたkernel_init()があるのでこのkernel_initが初めて動き出す
  3. kernel_init()が動き出す。通常の場合だとinitramfsをルートファイルシステムとしてマウント。initramfsを使用している場合initramfsの/initが呼び出される。/initスクリプトの中でLinuxの/をルートファイルシステムとしてマウント(ルートファイルシステムのマウント)、そしてswitch_rootコマンドの引数に指定されている?/sbin/initなどをプロセスとして生成(プロセス番号1)そして起動させる。

rest_init()のソースコード全体
resut_init()はこの流れで単純に終わり

init/main.c
static noinline void __init_refok rest_init(void) {             
    int pid;                                                             
    rcu_scheduler_starting();                                            
    /*                                                                   
     * We need to spawn init first so that it obtains pid 1, however     
     * the init task will end up wanting to create kthreads, which, if   
     * we schedule it before we create kthreadd, will OOPS.              
     */ 
    //1.kernel_init生成
    //2.kernel_initの中でルートファイルシステムをマウント                                                             
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);          
    numa_default_policy();                                               
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);         
    rcu_read_lock();
    rcu_read_unlock();                                  
    complete(&kthreadd_done);                           

    /*                                                  
     * The boot idle thread must execute schedule()
     * at least once to get things moving:              
     */                                                 
    init_idle_bootup_task(current);                     
    schedule_preempt_disabled();                        
    /* Call into cpu_idle with preempt disabled */ 
    //3.CPUをアイドル状態にする
    cpu_startup_entry(CPUHP_ONLINE);                    
 }

cpu_startup_entry()は下に詳細記載

kernel_thread()

  • do_fork() 実行。こいつ自体はforkの後にexecve()は読んでない。
  • 親プロセスをコピーして子を生成。スケジューラに登録
  • 通常のforkからのプロセス生成などの場合はカーネルスタックにはpt_regs構造体に合わせてレジスタの値が保存されていて、そのレジスタ値へのポインタがdo_forへの引数regsとして渡される。が、カーネルスレッドを生成する場合、pt_regsに値を直接代入している。
  • struct p_regs* regsのポインタがdo_fork()への引数としてわたされる。それがそのままカーネルスタック上のレジスタを参照して、各所で参照される。

レジスタの値を色々入れ替えている。

arch/x86/kernel/process.c
/*
 * This gets run with %si containing the
 * function to call, and %di containing
 * the "args".
 */
extern void kernel_thread_helper(void);

/*
 * Create a kernel thread
 */
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
        struct pt_regs regs;

        memset(&regs, 0, sizeof(regs));

        regs.si = (unsigned long) fn;
        regs.di = (unsigned long) arg;

#ifdef CONFIG_X86_32
        regs.ds = __USER_DS;
        regs.es = __USER_DS;
        regs.fs = __KERNEL_PERCPU;
        regs.gs = __KERNEL_STACK_CANARY;
#else
        regs.ss = __KERNEL_DS;
#endif

        regs.orig_ax = -1;
        regs.ip = (unsigned long) kernel_thread_helper;
        regs.cs = __KERNEL_CS | get_kernel_rpl();
        regs.flags = X86_EFLAGS_IF | X86_EFLAGS_BIT1;

        /* Ok, create the new process.. */
        return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}
EXPORT_SYMBOL(kernel_thread);
arch/x86/include/asm/ptrace.h
struct pt_regs {
        unsigned long bx;
        unsigned long cx;
        unsigned long dx;
        unsigned long si;
        unsigned long di;
        unsigned long bp;
        unsigned long ax;
        unsigned long ds;
        unsigned long es;
        unsigned long fs;
        unsigned long gs;
        unsigned long orig_ax;
        unsigned long ip;
        unsigned long cs;
        unsigned long flags;
        unsigned long sp;
        unsigned long ss;
};

  • do_fork()
    • → dup_task_struct()
      • 親のtask_structをコピー
    • → copy_thread
      • ここでレジスタの値を複製した箇所を指すようにしている。
arch/x86/kernel/process_64.c
int copy_thread(unsigned long clone_flags, unsigned long sp,
                unsigned long unused,
        struct task_struct *p, struct pt_regs *regs)
{
        int err;
        struct pt_regs *childregs;
        struct task_struct *me = current;

        childregs = ((struct pt_regs *)
                        (THREAD_SIZE + task_stack_page(p))) - 1;
        *childregs = *regs;

        childregs->ax = 0;
        if (user_mode(regs))
                childregs->sp = sp;
        else
                childregs->sp = (unsigned long)childregs;

        p->thread.sp = (unsigned long) childregs;
        p->thread.sp0 = (unsigned long) (childregs+1);
        p->thread.usersp = me->thread.usersp;

        set_tsk_thread_flag(p, TIF_FORK);

        p->fpu_counter = 0;
        p->thread.io_bitmap_ptr = NULL;

        savesegment(gs, p->thread.gsindex);
        p->thread.gs = p->thread.gsindex ? 0 : me->thread.gs;
        savesegment(fs, p->thread.fsindex);
        p->thread.fs = p->thread.fsindex ? 0 : me->thread.fs;
        savesegment(es, p->thread.es);
        savesegment(ds, p->thread.ds);

        err = -ENOMEM;
        memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));

        if (unlikely(test_tsk_thread_flag(me, TIF_IO_BITMAP))) {
                p->thread.io_bitmap_ptr = kmemdup(me->thread.io_bitmap_ptr,
                                                  IO_BITMAP_BYTES, GFP_KERNEL);
                if (!p->thread.io_bitmap_ptr) {
                        p->thread.io_bitmap_max = 0;
                        return -ENOMEM;
                }
                set_tsk_thread_flag(p, TIF_IO_BITMAP);
        }

        /*
         * Set a new TLS for the child thread?
         */
        if (clone_flags & CLONE_SETTLS) {
#ifdef CONFIG_IA32_EMULATION
                if (test_thread_flag(TIF_IA32))
                        err = do_set_thread_area(p, -1,
                                (struct user_desc __user *)childregs->si, 0);
                else
#endif
                        err = do_arch_prctl(p, ARCH_SET_FS, childregs->r8);
                if (err)
                        goto out;
        }
        err = 0;
out:
        if (err && p->thread.io_bitmap_ptr) {
                kfree(p->thread.io_bitmap_ptr);
                p->thread.io_bitmap_max = 0;
        }

        return err;
}


下記箇所で、regsがカーネルスタック上のレジスタ値を指せるような箇所に挿入している。childregsでカーネルスタック上のレジスタ値をregs構造体でアクセスできるようになる。よって直接その値をいじることで復帰時にレジスタ値を変更することができる。

arch/x86/kernel/process_64.c

        childregs = ((struct pt_regs *)
                        (THREAD_SIZE + task_stack_page(p))) - 1;
        *childregs = *regs;

kernel_thread()まとめ

  • カーネルスレッドを生成する
  • ドライバのプロセスも後述するkthread_create()からkernel_thread()使ってカーネルスレッドを生成する。
  • スレッドなので親とプロセス領域をdo_fork()で複製、プロセスディスクリプタからポインタしている各資源を共有する。 しかし、kernel_thread()の最初やcopy_thread()で各レジスタの値を自分独自のものにしている。
  • kernel_initはプロセスになるためにexecve()を呼び、プロセスディスクリプタからポインタされている親と共有している各資源を子プロセス独自のものにする。

kernel_init()

  • Linuxのルートファイルシステムをマウントする
  • sys_execve() //execveシステムコール
  • flsh_old_exec()
    • 仮想アドレス空間をdo_execve_common()のbprm_mm_init()で作成したものに切り替える(新規ページテーブルをレジスタCR3に再ロード)
    • プログラムファイルのプログラムヘッダを順にアクセスし、テキストデータ領域を仮想アドレス空間にmmapする。プロセスディスクリプタのmmを子独自のものに書き換え。これで仮想アドレスが完全に置き換わる。
  • initramfsの/bin/sh /initを実行。この中で/sbin/initがプロセス1として今までのカーネルプロセスから置き換わって、ユーザプロセスとして生成される

mm_structを子独自のものに書き換えている箇所

ここっぽい。

fs/exec.c
int flush_old_exec(struct linux_binprm * bprm)
{
        int retval;

        /*
         * Make sure we have a private signal table and that
         * we are unassociated from the previous thread group.
         */
        retval = de_thread(current);
        if (retval)
                goto out;

        set_mm_exe_file(bprm->mm, bprm->file);

        filename_to_taskname(bprm->tcomm, bprm->filename, sizeof(bprm->tcomm));
        /*
         * Release all of the old mmap stuff
         */
        acct_arg_size(bprm, 0);
        //ここ!!
        retval = exec_mmap(bprm->mm);
        if (retval)
                goto out;

        bprm->mm = NULL;                /* We're using it now */

        set_fs(USER_DS);
        current->flags &= ~(PF_RANDOMIZE | PF_KTHREAD);
        flush_thread();
        current->personality &= ~bprm->per_clear;

        return 0;

out:
        return retval;
}
EXPORT_SYMBOL(flush_old_exec);
fs/exec.c
static int exec_mmap(struct mm_struct *mm)
{
        struct task_struct *tsk;
        struct mm_struct * old_mm, *active_mm;

        /* Notify parent that we're no longer interested in the old VM */
        tsk = current;
        old_mm = current->mm;
        sync_mm_rss(tsk, old_mm);
        mm_release(tsk, old_mm);

        if (old_mm) {
                /*
                 * Make sure that if there is a core dump in progress
                 * for the old mm, we get out and die instead of going
                 * through with the exec.  We must hold mmap_sem around
                 * checking core_state and changing tsk->mm.
                 */
                down_read(&old_mm->mmap_sem);
                if (unlikely(old_mm->core_state)) {
                        up_read(&old_mm->mmap_sem);
                        return -EINTR;
                }
        }
        task_lock(tsk);
        active_mm = tsk->active_mm;
        tsk->mm = mm;
        tsk->active_mm = mm;
        activate_mm(active_mm, mm);
        task_unlock(tsk);
        arch_pick_mmap_layout(mm);
        if (old_mm) {
                up_read(&old_mm->mmap_sem);
                BUG_ON(active_mm != old_mm);
                mm_update_next_owner(old_mm);
                mmput(old_mm);
                return 0;
        }
        mmdrop(active_mm);
        return 0;
}

kerneladd()

永久にリストをチェック。要求が存在したらカーネルスレッドを生成する。

kernel/kthread.c
int kthreadd(void *unused)
{
        struct task_struct *tsk = current;

        /* Setup a clean context for our children to inherit. */
        set_task_comm(tsk, "kthreadd");
        ignore_signals(tsk);
        set_cpus_allowed_ptr(tsk, cpu_all_mask);
        set_mems_allowed(node_states[N_HIGH_MEMORY]);

        current->flags |= PF_NOFREEZE;

        for (;;) {
                set_current_state(TASK_INTERRUPTIBLE);
                if (list_empty(&kthread_create_list))
                        schedule();
                __set_current_state(TASK_RUNNING);

                spin_lock(&kthread_create_lock);
                while (!list_empty(&kthread_create_list)) {
                        struct kthread_create_info *create;

                        create = list_entry(kthread_create_list.next,
                                            struct kthread_create_info, list);
                        list_del_init(&create->list);
                        spin_unlock(&kthread_create_lock);

                        create_kthread(create);

                        spin_lock(&kthread_create_lock);
                }
                spin_unlock(&kthread_create_lock);
        }

        return 0;
}

ドライバ kthread_create() 呼び出し

inclube/linux/kthread.h
#define kthread_create(threadfn, data, namefmt, arg...) \
        kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)


kthread_create_on_node()

ここでリストに登録。kerneladdスレッドが感知してカーネルスレッドとして生成する。

kernel/kthread.c
/**
 * kthread_create_on_node - create a kthread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @node: memory node number.
 * @namefmt: printf-style name for the thread.
 *
 * Description: This helper function creates and names a kernel
 * thread.  The thread will be stopped: use wake_up_process() to start
 * it.  See also kthread_run().
 *
 * If thread is going to be bound on a particular cpu, give its node
 * in @node, to get NUMA affinity for kthread stack, or else give -1.
 * When woken, the thread will run @threadfn() with @data as its
 * argument. @threadfn() can either call do_exit() directly if it is a
 * standalone thread for which no one will call kthread_stop(), or
 * return when 'kthread_should_stop()' is true (which means
 * kthread_stop() has been called).  The return value should be zero
 * or a negative error number; it will be passed to kthread_stop().
 *
 * Returns a task_struct or ERR_PTR(-ENOMEM).
 */
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
                                           void *data,
                                           int node,
                                           const char namefmt[],
                                           ...)
{
        struct kthread_create_info create;

        create.threadfn = threadfn;
        create.data = data;
        create.node = node;
        init_completion(&create.done);

        spin_lock(&kthread_create_lock);
        list_add_tail(&create.list, &kthread_create_list);
        spin_unlock(&kthread_create_lock);

        wake_up_process(kthreadd_task);
        wait_for_completion(&create.done);

        if (!IS_ERR(create.result)) {
                static const struct sched_param param = { .sched_priority = 0 };
                va_list args;

                va_start(args, namefmt);
                vsnprintf(create.result->comm, sizeof(create.result->comm),
                          namefmt, args);
                va_end(args);
                /*
                 * root may have changed our (kthreadd's) priority or CPU mask.
                 * The kernel thread should not inherit these properties.
                 */
                sched_setscheduler_nocheck(create.result, SCHED_NORMAL, &param);
                set_cpus_allowed_ptr(create.result, cpu_all_mask);
        }
        return create.result;
}
EXPORT_SYMBOL(kthread_create_on_node);

ルートファイルシステムのマウント

概要

  • kernel_initの中で実行される。
  • do_bask_setup() カーネルに組み込んでコンパイルしたデバイスドライバなどの初期化関数を呼び出し
    • populate_rootfs()
      • upack_to_rootfsを呼び出し
    • upack_to_rootfs()
      • 既にメモリにあるinitramfsを解凍。ramfsを展開。

ルートファイルシステムのマウント

  • initramfsが使えなければ、Linuxのルートファイルシステムを直接マウントして、Linuxのルートファイルシステムにある指定したinitプログラムを実行しようとする。
  • initramfsが使えればinitramfsの中のinitプログラムを実行しようとしてそのinitの中でLinuxのルートファイルシステムをマウントして、initプログラムの中で指定されているkernelのブートオプションで指定されたものか、initramfsに書かれている/sbin/initプログラムなどを実行しようとする。
  • kernelのブートオプションやinitramfsの有無で順番にみて実行するファイルを決定してrun_init_process()の先で実行する。通常はinitramfsの/initか。条件分岐は以下コメント通り。
init/main.c
static int __init kernel_init(void * unused)
{
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);
        /*
         * init can allocate pages on any node
         */
        set_mems_allowed(node_states[N_HIGH_MEMORY]);
        /*
         * init can run on any cpu.
         */
        set_cpus_allowed_ptr(current, cpu_all_mask);

        cad_pid = task_pid(current);

        smp_prepare_cpus(setup_max_cpus);

        do_pre_smp_initcalls();
        lockup_detector_init();

        smp_init();
        sched_init_smp();

        do_basic_setup();

        /* Open the /dev/console on the rootfs, this should never fail */
        if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
                printk(KERN_WARNING "Warning: unable to open an initial console.\n");

        (void) sys_dup(0);
        (void) sys_dup(0);
        /*
         * check if there is an early userspace init.  If yes, let it do all
         * the work
         */

        // カーネルのブートオプションにrdinit=valueがなければ/init
        if (!ramdisk_execute_command)
                ramdisk_execute_command = "/init";

        //do_basic_setup()の中で展開したinitramfsにアクセスできなければprepare_namespace()でディスクの/をマウント。
        //initramfsは使用しない
        if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
                ramdisk_execute_command = NULL;
                prepare_namespace();
        }

        /*
         * Ok, we have completed the initial bootup, and
         * we're essentially up and running. Get rid of the
         * initmem segments and start the user-mode stuff..
         */

        init_post();
        return 0;
}
init/main.c
static noinline int init_post(void)
{
        /* need to finish all async __init code before freeing the memory */
        async_synchronize_full();
        free_initmem();
        mark_rodata_ro();
        system_state = SYSTEM_RUNNING;
        numa_default_policy();


        current->signal->flags |= SIGNAL_UNKILLABLE;

        //上記でinitramfsにアクセスできなければNULLになっているのでfalseに必ずなる
        //initramfsにアクセス可能であればrun_init_process()で/initを実行して、initramfsの中でLinuxのルートファイルシステムをマウントする
        if (ramdisk_execute_command) {
                run_init_process(ramdisk_execute_command);
                printk(KERN_WARNING "Failed to execute %s\n",
                                ramdisk_execute_command);
        }

        /*
         * We try each of these until one succeeds.
         *
         * The Bourne shell can be used instead of init if we are
         * trying to recover a really broken machine.
         */
        // execute_commandはカーネルのinit=valueの値が入っている。rdinitではない。
        // initramfsが使えないで、init=valueが入っている場合、run_init_process()でそれを実行しようとする
        if (execute_command) {
                run_init_process(execute_command);
                printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                        "defaults...\n", execute_command);
        }
        //initramfsも使えない、カーネルブートオプションもない場合、以下の順で実行しようとする。
        run_init_process("/sbin/init");
        run_init_process("/etc/init");
        run_init_process("/bin/init");
        run_init_process("/bin/sh");

        panic("No init found.  Try passing init= option to kernel. "
              "See Linux Documentation/init.txt for guidance.");
}

カーネル2.6系のinitramfsの/initシェルスクリプト

カーネル2.6系のinitramfsの/initシェルスクリプト

# by the time we get here, the root filesystem should be mounted.
# Try to find init.
for i in "$(getarg init=)" /sbin/init /etc/init /init /bin/sh; do
    [ -f "$NEWROOT$i" -a -x "$NEWROOT$i" ] && { INIT="$i"; break; }
done

switchするとこ



info "Switching root"

wait_for_loginit

if [ -f /etc/capsdrop ]; then
    . /etc/capsdrop
    info "Calling $INIT with capabilities $CAPS_INIT_DROP dropped."
    exec capsh --drop="$CAPS_INIT_DROP" -- -c "exec switch_root \"$NEWROOT\" \"$INIT\" $initargs" || {
        warn "Command:"
        warn capsh --drop=$CAPS_INIT_DROP -- -c "'"exec switch_root "$NEWROOT" "$INIT" $initargs"'"
        warn "failed."
        emergency_shell
    }
else
    exec switch_root "$NEWROOT" "$INIT" $initargs || {
        warn "Something went very badly wrong in the initramfs.  Please "
        warn "file a bug against dracut."
        emergency_shell
    }
fi

kernel_thread_helper

カーネルスレッドはここから始まりcall kernel_threadの第一引数のそれぞれのカーネル関数を実行。実行終えるとここに戻ってきてcall do_exitして処理を終える。

arch/x86/kernel/entry_64.S
ENTRY(kernel_thread_helper)
        pushq $0                # fake return address
        CFI_STARTPROC
        /*
         * Here we are in the child and the registers are set as they were
         * at kernel_thread() invocation in the parent.
         */
        call *%rsi
        # exit
        mov %eax, %edi
        call do_exit
        ud2                     # padding for call trace
        CFI_ENDPROC
END(kernel_thread_helper)

swapperの最後の仕事。CPUをアイドル状態に移行させる。

  • カーネル環境を整え終えると、CPUをアイドル状態に移行させるだけのプロセスになる。
  • rest_init()の最後にcpu_startup_entry()がよばれる。
kernel/cpu/idle.c
void cpu_startup_entry(enum cpuhp_state state) 


//最後に呼ばれる
cpu_idle_loop()
}
kernel/sched/idle.c

static void cpu_idle_loop(void)
188 {
189         while (1) {
190                 /*
191                  * If the arch has a polling bit, we maintain an invariant:
192                  *
193                  * Our polling bit is clear if we're not scheduled (i.e. if
194                  * rq->curr != rq->idle).  This means that, if rq->idle has
195                  * the polling bit set, then setting need_resched is
196                  * guaranteed to cause the cpu to reschedule.
197                  */
198 
199                 __current_set_polling();
200                 tick_nohz_idle_enter();
201 
202                 while (!need_resched()) {
203                         check_pgt_cache();
204                         rmb();
205 
206                         if (cpu_is_offline(smp_processor_id()))
207                                 arch_cpu_idle_dead();
208 
209                         local_irq_disable();
210                         arch_cpu_idle_enter();

247                 smp_mb__after_atomic();
248 
249                 sched_ttwu_pending();

                    //この中でschedule()が呼ばれてる。
250                 schedule_preempt_disabled();
251         }

while (1)なのでswapperプロセスはここを永久にまわる。実行可能状態のプロセスが現れるまでCPUを停止状態にする。CPUの動作が再会されるとschedule()を呼び出して、他のプロセスにCPUを讓る。その後実行可能なプロセスがなくなるとここに
(while (1)の中)制御が戻ってくるので再び実行可能状態のプロセスが現われるまでCPUをHALTする(arch_cpu_idle_dead)。よってswapperプロセスはwhile (1)なので以上の動作を繰り返すプロセスになる。

17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15