关于内核进程0和进程1

来源:互联网 发布:如何自学日语 知乎 编辑:程序博客网 时间:2024/05/16 08:14

 

关于内核进程0和进程1

关于内核进程0和进程1

 

Kernel Version: Linux 2.6.18_Pro500 (for Arm)

 

Process 0

 

下面这段对进程0的描述引用自《Understanding The Linux Kernel - Third Edtion

The ancestor of all processes, called process 0, the idle process, or, for historical reasons, the swapper process, is a kernel thread created from scratch during the initialization phase of Linux. This ancestor process uses the following statically allocated data structures (data structures for all other processes are dynamically allocated):

 

A process descriptor stored in the init_task variable, which is initialized by the INIT_TASK macro.

A thread_info descriptor and a Kernel Mode stack stored in the init_thread_union variable and initialized by the INIT_THREAD_INFO macro.

 

The following tables, which the process descriptor points to:

-          init_mm

-          init_fs

-          init_files

-          init_signals

-          init_sighand

The tables are initialized, respectively, by the following macros:

-          INIT_MM

-          INIT_FS

-          INIT_FILES

-          INIT_SIGNALS

-          INIT_SIGHAND

 

总结一下有如下几个要点:

1. 进程0是所有其他进程的祖先, 也称作idle进程或swapper进程。

2. 进程0是在系统初始化时由kernel自身从无到有创建。(过程集中在start_kernel函数内)

3. 进程0的数据成员大部分是静态定义的,即由预先定义好的(如上)INIT_TASK, INIT_MM等宏初始化。

 

进程0的描述符init_task定义在arch/arm/kernel/init_task.c,INIT_TASK宏初始化。 init_mm等结构体定义在include/linux/init_task.h内,为init_task成员的初始值,分别由对应的初始化宏如INIT_MM等初始化,

 

 

Process 1

 

 

 

进程0最终会通过调用kernel_thread创建一个内核线程去执行init函数,这个新创建的内核线程即Process 1(这时还是共享着内核线程0的资源属性如地址空间等)init函数继续完成剩余的内核初始化,并在函数的最后调用execve系统调用装入用户空间的可执行程序/sbin/init,这时进程1就拥有了自己的属性资源,成为一个普通进程(init进程)。至此,内核初始化和启动过程结束。下面就进入了用户空间的初始化,最后运行shell登陆界面。

(注:Init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动。)

 

 

进程1的创建过程如下:

start_kernel -> rest_init -> kernel_thread -> init

 

static void noinline rest_init(void)

    __releases(kernel_lock)

{

    system_state = SYSTEM_BOOTING_SCHEDULER_OK;

 

    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); /* 通过kernel_thread创建一个内核线程去执行init函数。这里新创建的内核线程即Process 1*/

    numa_default_policy();

    unlock_kernel();

 

    /*

     * The boot idle thread must execute schedule()

     * at least one to get things moving:

     */

    __preempt_enable_no_resched();

    schedule(); /* 这里调度上面刚创建的Process 1执行。Process1会去执行init函数 */

    preempt_disable();

 

    /* Call into cpu_idle with preempt disabled */

    cpu_idle();

}

执行schedule()启动进程1后,进程0便调用cpu_idle()函数进入睡眠状态。

 

 

下面看一下kernel_thread函数

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

{

    struct pt_regs regs;

    long pid;

 

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

 

    regs.ARM_r1 = (unsigned long)arg;

    regs.ARM_r2 = (unsigned long)fn;

    regs.ARM_r3 = (unsigned long)do_exit;

    regs.ARM_pc = (unsigned long)kernel_thread_helper;

    regs.ARM_cpsr = SVC_MODE;

 

    pid = do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);

 

    MARK(kernel_thread_create, "%ld %p", pid, fn);

    return pid;

}

 

可以发现kernel_thread 最终也是调用的do_fork完成内核线程的创建。

do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, pregs, 0, NULL, NULL);

flag参数标志决定了新创建的进程的行为方式以及父子进程之间共享的资源种类,这里用到的几个flag标志意思如下:

CLONE_FS        父子进程共享文件系统信息

CLONE_SIGHAND   父子进程共享信号处理函数

CLONE_VM        父子进程共享地址空间

CLONE_UNTRACED  防止跟踪进程在子进程上强制执行CLONE_PTRACE

 

 

看看新创建的内核线程1Process 1)做了些什么事: ( init函数)

static int init(void * unused)

{

 

/* 继续进行一些系统初始化 */

    lock_kernel();

    set_cpus_allowed(current, CPU_MASK_ALL);

    child_reaper = current;

    smp_prepare_cpus(max_cpus);

    init_hardirqs();

    do_pre_smp_initcalls();

    smp_init();

    sched_init_smp();

    cpuset_init_smp();

    ……

/* 完成了剩余的内核初始化工作后,通过调用run_init_process函数执行用户空间程序/sbin/init*/

    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.");

}

这里的 run_init_process 实际上就是通过execve()来运行 init 程序。这里首先运行/sbin/init,如果失败再运行/etc/init,然后是/bin/init,然后是/bin/sh(也就是说,init 可执行文件可以放在上面代码中的4 个目录中都可以),如果都失败,则可以通过在系统

启动时在添加的启动参数来指定init,比如init=/ linuxrc

 

run_init_process源码如下,最终通过execve 系统调用执行/sbin/init程序。

static void run_init_process(char *init_filename)

{

    argv_init[0] = init_filename;

    execve(init_filename, argv_init, envp_init);

}

Exec函数族(execve为其中一种)提供了一个在进程中启动另外一个程序执行的方法。它可以根据指定的文件名或目录找到可执行文件,并用它来取代原先调用进程的属性包括数据段,代码段和堆栈段等。在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。这里在执行execve之前,进程1还是共享着内核线程0资源属性的内核线程。执行execve(即执行了用户空间的init程序),此时,进程1就拥有了自己的地址空间等属性,成为一个普通进程。

 
注:如果想继续深入了解进程1的执行过程,可以看下面这份大虾写的文档:
《init进程探悉》
http://blogimg.chinaunix.net/blog/upfile2/080802120641.pdf