首页 > 系统服务 > 详细

Linux---从start_kernel到init进程启动

时间:2015-03-22 22:15:21      阅读:399      评论:0      收藏:0      [点我收藏+]

“平安的祝福 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

init process是Linux系统的第一个用户态进程,那自然没有父亲。它是由Linux内核直接启动的。该进程读取系统的初始化脚本并执行其他的相关的程序,最终完成系统启动的整个过程、

start_kernel()是内核的汇编与C语言的交接点,在该函数以前,内核的代码都是用汇编写的,完成一些最基本的初始化与环境设置工作,比如内核代码载入内存并解压缩(现在的内核一般都经过压缩),CPU 的最基本初始化,为C代码的运行设置环境(C代码的运行是有一定环境要求的,比如stack的设置等)。这里一个不太确切的比喻是start_kernel()就像是C代码中的main()。我们知道对应用程序员而言,main()是他的入口,但实际上程序的入口是被包在了C库中,在链接阶段,linker会把它链接入你的程序中。main()中的argc,argv 等都不是平白无故来的,而它的任务中有一项就是为main()准备运行环境。

在start_kernel()中Linux将完成整个系统的内核初始化。内核初始化的最后一步rest_init函数就是启动init进程这个所有进程的祖先。

 一、首个用户级线程pid=1

{
…
printk(KERN_NOTICE"%s", linux_banner);  //输出linux版本信息
setup_arch(&command_line);   //设置与初始化硬件体系相关的环境并调用
                           //pageint_inig() ->devicemaps_init(mdesc);初始化异常向量表,初始化zone memory maps,初始化结构体:MACHINE_START等
sched_init()                 //初始化调度器…先于中断开始前
printk(boot_command_line);   //提取分析核心启动参数过程(从bootloader 中传递)
parse_early_param();
parse_args
trap_init();                  //自陷入口函数初始化,针对此版本arm中直接return
early_irq_init();              //中断初始化过程….
init_IRQ();          
init_timers();                //初始化定时器,开启定时器软中断服务以及注册服务程序以及初始化各CPU中的tev_base等init_timers()->run_timer_softirq()->__run_timers().. 
timekeeping_init(); 
time_init(); //设置定时器及返回当前时间
console_init() //初步的初始化控制台,此控制台只能打印出一些简单//的启动信息…
vmalloc_init(); //
vfs_caches_init_early(); //
mem_init(); //初始化内存并计算可用内存大小
kmem_cache_init(); // 初始化SLAB缓存分配器
calibrate_delay(); //延迟校准,jiffy,记录系统的定时器的节拍数,每变化一次代表了系统定时器2个连续节拍时间的间隔。
fork_init(num_physpages); //初始化max_threads,init_task参数为fork()提供参考
buffer_init(); //初始化块设备读写缓冲区
vfs_caches_init(num_physpages);   //初始化虚拟文件系统 inode_init() ->files_init() ->mnt_init()...
signals_init(); //初始化内核信号队列….
rest_init(); //最后实际进入reset_init()函数,包括所有剩下的硬件//驱动,线程初始化等过程…这也最终完成//start_kernel//的启动过程。
}

其中rest_init()函数如下:

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.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS); //创建一个内核线程,实际上是一个内核进程。其中kernel_init只是一个函数
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//CLONE_FS | CLONE_SIGHANDdo_fork产生线程时的标志,表示进程间的fs信息共享,信号处理和块信号共享,
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    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 */
    cpu_startup_entry(CPUHP_ONLINE);
}

kernel_thread(kernel_init, NULL, CLONE_FS); //创建一个内核线程,实际上是一个内核进程,其中pid=1。其中kernel_init只是一个函数
 
static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();
    /* 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();

    flush_delayed_fput();

    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
               ramdisk_execute_command, ret);
    }

    /*
     * 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.
     */
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
#ifndef CONFIG_INIT_FALLBACK
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
#else
        pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",
               execute_command, ret);
#endif
    }
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
return 0;
panic(
"No working init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance.");
}

 

 

try_to_run_init_process()其参数就是要执行的可执行文件名,也就是这里的init process在磁盘上的文件。

static int try_to_run_init_process(const char *init_filename)
{
    int ret;

    ret = run_init_process(init_filename);

    if (ret && ret != -ENOENT) {
        pr_err("Starting init: %s exists but couldn‘t execute it (error %d)\n",
               init_filename, ret);
    }

    return ret;
}
//try_to_run_init_process()实际上调用run_init_process();
static
int run_init_process(const char *init_filename) { argv_init[0] = init_filename; return do_execve(getname_kernel(init_filename), (const char __user *const __user *)argv_init, (const char __user *const __user *)envp_init); }
//run_init_process()实际上调用do_execve();
int
do_execve(struct filename *filename, const char __user *const __user *__argv, const char __user *const __user *__envp) { struct user_arg_ptr argv = { .ptr.native = __argv }; struct user_arg_ptr envp = { .ptr.native = __envp }; return do_execveat_common(AT_FDCWD, filename, argv, envp, 0); }

//do_execve()实际上调用do_execveat_common(),执行sched_exec(),然后返回一个值
static
int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, int flags) { char *pathbuf = NULL; struct linux_binprm *bprm; struct file *file; struct files_struct *displaced; int retval; if (IS_ERR(filename)) return PTR_ERR(filename);   /********
  *********
  *******
  /
sched_exec();   /********
  *********
  *******
  /

  return reval;
}

二、idle进程

在执行完成rest_init()之后,此时init_task的任务基本上已经完全结束了,它将 沦落为一个idle task。事实上在更早前的start kernel()函数中的sched_init(),在sched_init()函数中, 通过init_idle(current, smp_processor_id())函数的调用就已经把init_task初始化成了一个idle task,init_idle函数的第一个参数current就是&init_task,在init_idle中将会把init_task加入到 cpu的运行队列中,这样当运行队列中没有别的就绪进程时,init_task(也就是idle task)将会被调用,它的核心是一个while(1)循环,在循环中它将会调用schedule函数以便在运行队列中有新进程加入时切换到该新进程上。

void __init sched_init(void)
{
    /*
     *
     *
     */

    /*
     * Make us the idle thread. Technically, schedule() should not be
     * called from this thread, however somewhere below it might be,
     * but because we are the idle thread, we just pick up running again
     * when this runqueue becomes "idle".
     */
    init_idle(current, smp_processor_id());
    /*
     *
     *
     */
    scheduler_running = 1;
}

 

三、实验截图

断点的设置

技术分享

 

 

运行到start_kernel处的情况

 

技术分享

 

运行到sched_init()处的图象

技术分享

 

运行到rest_init()处的图象

技术分享

 

参考文献:

http://blog.csdn.net/yyplc/article/details/7030983

http://blog.csdn.net/hardy_2009/article/details/7383815

 

Linux---从start_kernel到init进程启动

原文:http://www.cnblogs.com/pingandezhufu/p/4356648.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!