关于内核进程0和进程1

 

关于内核进程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进程探悉》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值