Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9779
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-08 18:47
文章分类
文章存档

2015年(5)

我的朋友
最近访客

分类: C/C++

2015-03-22 08:23:27

0 前言

    本周,我们尝试通过qemu虚拟机启动一个真实的Linux内核,并看一下Linux在启动时到底做了什么事情。

1 实验   

    提示,本节前半部分与作业内容关系较小。。。                                                         

    首先尝试使用自己的环境生成内核版本文件。 在编译内核之前,需要对内核进行配置,这里使用32位x86的默认配置。生成.config文件。

按照课程中的提示,配置内核时,加入内核调试信息。

给虚拟机(我的Linux Mint运行在虚拟机中)多分配了一些资源,增加了CPU的数量和内存,开始编译内核。生成内核版本文件。


后来到这里尴尬了,由于时间关系暂时没有解决(郁闷,每次作业都拖到很晚,这个后续解决后写到评论里)。 编译生成32位可执行文件时,由于一些原因失败,看似是缺少32位的编译环境。顺手编了一个64位的,但此时我忘记我的内核是32位的了。。

使用qemu加载内核时,果然出现了问题,提示体系结构不匹配。有些不明白的是,按说我内核是32位的啊,感觉应该往前走一些再挂啊。。


好吧,由于时间不够,换回到实验楼了。。。(尴尬)
按照实验要求,开启对qemu虚拟机的远程gdb调试。对start_kernel设置断点。

Contitue执行到断点处,准备逐行分析。

通过step(单步执行并进入调用的函数)和next命令进行逐行分析。

2 分析

1) 关于start_kernel

调用者: 对于32位X86系统来说,是i386_start_kernel,从这里我们也可以看出start_kernel是一个体系结构无关的第一个初始化函数,所有类型的CPU都需要逐步执行Start_kernel,完成相同的操作。

函数内容: 确实有点复杂,只能挑几个意义明显的函数进行简单说明,其他具体的分析只能看各种大牛的博客了。
简单分析见双斜线// 后的注释。

点击(此处)折叠或打开

  1. asmlinkage void __init start_kernel(void)
  2. {
  3.     char * command_line;  //boot向内核的传参
  4.     extern const struct kernel_param __start___param[], __stop___param[];

  5.     smp_setup_processor_id();

  6.     /*
  7.      * Need to run as early as possible, to initialize the
  8.      * lockdep hash:
  9.      */
  10.     lockdep_init();
  11.     debug_objects_early_init();

  12.     /*
  13.      * Set up the the initial canary ASAP:
  14.      */
  15.     boot_init_stack_canary();

  16.     cgroup_init_early();

  17.     local_irq_disable();  //关掉本cpu全部中断
  18.     early_boot_irqs_disabled = true;

  19. /*
  20.  * Interrupts are still disabled. Do necessary setups, then
  21.  * enable them
  22.  */
  23.     tick_init();      
  24.     boot_cpu_init();
  25.     page_address_init();
  26.     printk(KERN_NOTICE "%s", linux_banner);
  27.     setup_arch(&command_line);
  28.     mm_init_owner(&init_mm, &init_task);
  29.     mm_init_cpumask(&init_mm);
  30.     setup_command_line(command_line);  //处理内核参数
  31.     setup_nr_cpu_ids();
  32.     setup_per_cpu_areas();
  33.     smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */

  34.     build_all_zonelists(NULL);
  35.     page_alloc_init();

  36.     printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
  37.     parse_early_param();
  38.     parse_args("Booting kernel", static_command_line, __start___param,
  39.          __stop___param - __start___param,
  40.          &unknown_bootoption);

  41.     jump_label_init();

  42.     /*
  43.      * These use large bootmem allocations and must precede
  44.      * kmem_cache_init()
  45.      */
  46.     setup_log_buf(0);
  47.     pidhash_init();
  48.     vfs_caches_init_early();   //虚拟文件系统cache
  49.     sort_main_extable();
  50.     trap_init();      
  51.     mm_init();       //内存管理初始化

  52.     /*
  53.      * Set up the scheduler prior starting any interrupts (such as the
  54.      * timer interrupt). Full topology setup happens at smp_init()
  55.      * time - but meanwhile we still have a functioning scheduler.
  56.      */
  57.     sched_init();
  58.     /*
  59.      * Disable preemption - early bootup scheduling is extremely
  60.      * fragile until we cpu_idle() for the first time.
  61.      */
  62.     preempt_disable();
  63.     if (!irqs_disabled()) {
  64.         printk(KERN_WARNING "start_kernel(): bug: interrupts were "
  65.                 "enabled *very* early, fixing it\n");
  66.         local_irq_disable();
  67.     }
  68.     idr_init_cache();
  69.     perf_event_init();
  70.     rcu_init();  //RCU(read copy update)机制初始化
  71.     radix_tree_init();
  72.     /* init some links before init_ISA_irqs() */
  73.     early_irq_init();
  74.     init_IRQ();  //中断初始化
  75.     prio_tree_init();
  76.     init_timers(); //时钟相关初始化
  77.     hrtimers_init();
  78.     softirq_init(); //软中断(Linux对于中断后半部的实现
  79.     timekeeping_init();
  80.     time_init();
  81.     profile_init();
  82.     call_function_init();
  83.     if (!irqs_disabled())
  84.         printk(KERN_CRIT "start_kernel(): bug: interrupts were "
  85.                  "enabled early\n");
  86.     early_boot_irqs_disabled = false;
  87.     local_irq_enable();   //打开中断

  88.     kmem_cache_init_late();

  89.     /*
  90.      * HACK This is early. We're enabling the console before
  91.      * we've done PCI setups etc, and console_init() must be aware of
  92.      * this. But we do want output early, in case something goes wrong.
  93.      */
  94.     console_init();    //串口初始化
  95.     if (panic_later)
  96.         panic(panic_later, panic_param);

  97.     lockdep_info();

  98.     /*
  99.      * Need to run this when irqs are enabled, because it wants
  100.      * to self-test [hard/soft]-irqs on/off lock inversion bugs
  101.      * too:
  102.      */
  103.     locking_selftest();

  104. #ifdef CONFIG_BLK_DEV_INITRD
  105.     if (initrd_start && !initrd_below_start_ok &&
  106.      page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
  107.         printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
  108.          "disabling it.\n",
  109.          page_to_pfn(virt_to_page((void *)initrd_start)),
  110.          min_low_pfn);
  111.         initrd_start = 0;
  112.     }
  113. #endif
  114.     page_cgroup_init();
  115.     enable_debug_pagealloc();
  116.     debug_objects_mem_init();
  117.     kmemleak_init();
  118.     setup_per_cpu_pageset();
  119.     numa_policy_init();   //非一致性内存访问初始化
  120.     if (late_time_init)
  121.         late_time_init();
  122.     sched_clock_init();
  123.     calibrate_delay();
  124.     pidmap_init();
  125.     anon_vma_init();
  126. #ifdef CONFIG_X86
  127.     if (efi_enabled(EFI_RUNTIME_SERVICES))
  128.         efi_enter_virtual_mode();
  129. #endif
  130.     thread_info_cache_init();
  131.     cred_init();
  132.     fork_init(totalram_pages);
  133.     proc_caches_init();
  134.     buffer_init();
  135.     key_init();
  136.     security_init();
  137.     dbg_late_init();
  138.     vfs_caches_init(totalram_pages);
  139.     signals_init();
  140.     /* rootfs populating might need page-writeback */
  141.     page_writeback_init();
  142. #ifdef CONFIG_PROC_FS
  143.     proc_root_init();
  144. #endif
  145.     cgroup_init();
  146.     cpuset_init();
  147.     taskstats_init_early();
  148.     delayacct_init();

  149.     check_bugs();

  150.     acpi_early_init(); /* before LAPIC and SMP init */
  151.     sfi_init_late();

  152.     ftrace_init();

  153. /* Do the rest non-__init'ed, we're now alive */
  154.     rest_init();   //重要,后面分析
  155. }

2) 关于0号进程与1号进程

其实,0号进程我理解就是执行start_kernel->rest_init的这个内核态进程。 而在rest_init中调用kernel_thread中进行任务创建,新任务的入口函数是kernel_init,这次任务创建分配的pid应为1,这就是1号进程的最开始部分。回到0号进程,会最终执行到 cpu_idle,进入一个while(1)的循环中,处理cpu空闲时的工作(?没仔细分析,从名字看大致是这样吧)。

点击(此处)折叠或打开

  1. static noinline void __init_refok rest_init(void)
  2. {
  3.     int pid;

  4.     rcu_scheduler_starting();
  5.     /*
  6.      * We need to spawn init first so that it obtains pid 1, however
  7.      * the init task will end up wanting to create kthreads, which, if
  8.      * we schedule it before we create kthreadd, will OOPS.
  9.      */
  10.     kernel_thread(kernel_init,NULL, CLONE_FS | CLONE_SIGHAND);
  11.     numa_default_policy();
  12.     pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
  13.     rcu_read_lock();
  14.     kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
  15.     rcu_read_unlock();
  16.     complete(&kthreadd_done);

  17.     /*
  18.      * The boot idle thread must execute schedule()
  19.      * at least once to get things moving:
  20.      */
  21.     init_idle_bootup_task(current);
  22.     preempt_enable_no_resched();
  23.     schedule();

  24.     /* Call into cpu_idle with preempt disabled */
  25.     preempt_disable();
  26.     cpu_idle();
  27. }

再来看1号进程后续做了什么。 初始化了用户接口,最后调用init_post。(其他的暂时不关注。。不关注)

点击(此处)折叠或打开

  1. static int __init kernel_init(void * unused)
  2. {
  3.     /*
  4.      * Wait until kthreadd is all set-up.
  5.      */
  6.     wait_for_completion(&kthreadd_done);

  7.     /* Now the scheduler is fully set up and can do blocking allocations */
  8.     gfp_allowed_mask = __GFP_BITS_MASK;

  9.     /*
  10.      * init can allocate pages on any node
  11.      */
  12.     set_mems_allowed(node_states[N_HIGH_MEMORY]);
  13.     /*
  14.      * init can run on any cpu.
  15.      */
  16.     set_cpus_allowed_ptr(current, cpu_all_mask);

  17.     cad_pid = task_pid(current);

  18.     smp_prepare_cpus(setup_max_cpus);

  19.     do_pre_smp_initcalls();
  20.     lockup_detector_init();

  21.     smp_init();
  22.     sched_init_smp();

  23.     do_basic_setup();

  24.     /* Open the /dev/console on the rootfs, this should never fail */ 
  25.     if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)   //打开用户可以输入命令的console口
  26.         printk(KERN_WARNING "Warning: unable to open an initial console.\n");

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

  33.     if (!ramdisk_execute_command)
  34.         ramdisk_execute_command = "/init";

  35.     if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
  36.         ramdisk_execute_command = NULL;
  37.         prepare_namespace();
  38.     }

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

  44.     init_post();
  45.     return 0;
  46. }

点击(此处)折叠或打开

  1. static noinline int init_post(void)
  2. {
  3.     /* need to finish all async __init code before freeing the memory */
  4.     async_synchronize_full();
  5.     free_initmem();
  6.     mark_rodata_ro();
  7.     system_state = SYSTEM_RUNNING;
  8.     numa_default_policy();


  9.     current->signal->flags |= SIGNAL_UNKILLABLE;

  10.     if (ramdisk_execute_command) {
  11.         run_init_process(ramdisk_execute_command);
  12.         printk(KERN_WARNING "Failed to execute %s\n",
  13.                 ramdisk_execute_command);
  14.     }

  15.     /*
  16.      * We try each of these until one succeeds.
  17.      *
  18.      * The Bourne shell can be used instead of init if we are
  19.      * trying to recover a really broken machine.
  20.      */
  21.     if (execute_command) {
  22.         run_init_process(execute_command);  //如果内核命令行中给出了到init进程的直接路径(或者别的可替代的程序),这里就试图执行init。
  23.         printk(KERN_WARNING "Failed to execute %s. Attempting "
  24.                     "defaults...\n", execute_command);
  25.     }
  26.     run_init_process("/sbin/init");  //如果前面执行失败,就按照下述4行的顺序,寻找执行init,如果都没有,就以shell当作init来执行。
  27.     run_init_process("/etc/init");
  28.     run_init_process("/bin/init");
  29.     run_init_process("/bin/sh");

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

点击(此处)折叠或打开

  1. static void run_init_process(const char *init_filename)
  2. {
  3.     argv_init[0] = init_filename;
  4.     kernel_execve(init_filename, argv_init, envp_init); // 不会再返回内核态啦~~内核态88啦
  5. }

4 总结

简单来说,
1) start_kernel是第一个结构无关的内核初始化程序,所有cpu都要进行相同的步骤,start_kernel本身就是0号进程。0号进程最终会进入死循环,处理空闲事件。
2) 1号进程由0号进程创建,最终会进入用户态进行相关初始化工作,是第一个用户态进程。

作者:胡川         
原创作品转载请注明出处 
《Linux内核分析》MOOC课程




参考:

《创建一号进程》 http://blog.csdn.net/yunsongice/article/details/6171336

《0号进程与1号进程的区别》 http://blog.csdn.net/yjzl1911/article/details/5613569
《start_kernel》 http://www.cnitblog.com/zouzheng/archive/2008/08/04/47574.html
《init_post》函数

阅读(639) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~