Chinaunix首页 | 论坛 | 博客
  • 博客访问: 49619
  • 博文数量: 7
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 304
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-08 20:51
个人简介

爱学习不寂寞

文章分类
文章存档

2015年(7)

我的朋友

分类: LINUX

2015-03-22 23:21:28

赵建清+原创作品转载请注明出处+《Linux内核分析》MOOC课程

一 概述

Linux内核启动之初会经历一个加载和初始化的阶段,大致可以分为3个阶段:
  1. 内核载入物理内存,并创建最小化的运行时环境;
  2. 转移到内核中与平台相关的机器码,并初始化基本的系统功能;
  3. 转移到初始化代码中与平台无关的部分,并完成所有子系统的初始化,最后切换到正常运行模式。
本文分析内核版本3.18.6的源代码启动部分,主要分析属于系统启动第三阶段的start_kernel函数以及idle进程和init进程的产生过程。start_kernel函数属于系统启动的第三阶段,此阶段初始化代码与平台无关,主要用C语言实现。

二 start_kernel函数分析

asmlinkage __visible void __init start_kernel(void)
{
    ...
   // lockdep_init()是个宏,定义在kernel/fork.c中第388行,如下:
   // 
# define lockdep_init()             do { } while (0)
   // 可见没做什么事,留作未来扩展之用。

   lockdep_init();
   // init_task的类型为task_struct.task_struct包含了一个进程相关的所有信息,
   // task_struct就是进程描述符(process descriptor)。
   // init_task就是内核中第一个进程,也就是idle进程或0号进程的process descriptor,
   // init_task由INIT_TASK宏完成初始化,
   // 
set_task_stack_end_magic函数定义在kernel/fork.c中第297行到303行,
   // 用于设置进程栈增长的终点。进程描述符和紧挨着的线程描述符thread_info,通常占据内核
   // 分配的8K空间,并占据两个连续的内存页框,堆栈从这8K的高地址开始增长,在thread_info
   // 结构外设置一个魔数,避免栈数据覆盖了thread_info结构。

    set_task_stack_end_magic(&init_task);
   // 以下3个函数定义为空,不做分析

    smp_setup_processor_id();
    debug_objects_early_init();
    boot_init_stack_canary();
      // 函数体为return 0,不做分析
    cgroup_init_early();
      // 关闭当前CPU中断
    local_irq_disable();
    early_boot_irqs_disabled = true;

     // 在多CPU机器上选择CPU
    boot_cpu_init();
    // 定义在mm/highmem.c第479行,用于高端内存初始化
   
page_address_init();
   // 打印linux版本信息

    pr_notice("%s", linux_banner);
   // setup_arch是个特定与体系结构的函数,主要关注内存管理各个方面的初始化。
   // 在IA-32系统上,首先记录下内核在物理和虚拟内存中的位置。
   // 调用setup_memory_map初始化bootmem分配器;
   // 调用parse_early_param对命令行参数进行部分解释,处理与内存管理设置相关的参数;

    setup_arch(&command_line);
    mm_init_cpumask(&init_mm);
   // 对command_line备份与保存

    setup_command_line(command_line);
   // 以下3个函数在IA-32定义为空

    setup_nr_cpu_ids();
    setup_per_cpu_areas();
    smp_prepare_boot_cpu(); 
      // 设置内存的相关节点和其中的内存域数据结构
    build_all_zonelists(NULL, NULL);
   // 初始化page allocation相关数据结构

    page_alloc_init();
      // 打印与解析内核启动相关参数
    pr_notice("Kernel command line: %s\n", boot_command_line);
    parse_early_param();
    after_dashes = parse_args("Booting kernel",
                  static_command_line, __start___param,
                  __stop___param - __start___param,
                  -1, -1, &unknown_bootoption);
    if (!IS_ERR_OR_NULL(after_dashes))
        parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
               set_init_arg);

    jump_label_init();

    // 使用bootmem分配一个启动信息的缓冲区
    setup_log_buf(0);
   // 使用bootmem分配并初始化PID散列表

    pidhash_init();
   // 前期VFS缓存初始化

    vfs_caches_init_early();
   // 对内核异常表进行排序

       sort_main_extable();
   // 对内核陷阱异常经行初始化

    trap_init();
   // 初始化内核内存分配器,启动信息中的内存信息来自此函数中的mem_init函数

    mm_init();

    // 初始化调度器的数据结构,并创建运行队列
    sched_init();
    // 禁用抢占和中断,早期启动时期,调度是极其脆弱的
    preempt_disable();
    if (WARN(!irqs_disabled(),
         "Interrupts were enabled *very* early, fixing it\n"))
        local_irq_disable();
   // 为IDR机制分配缓存

    idr_init_cache();
   // 内核RCU机制初始化

    rcu_init();
    context_tracking_init();
   // 内核radix树算法初始化

    radix_tree_init();
    // 前期外部中断描述符初始化
    early_irq_init();
   // 架构相关中断初始化

    init_IRQ();
   // 初始化内核时钟

    tick_init();
   // 函数定义为空

    rcu_init_nohz();
   // 以下5个函数是软中断和内核时钟机制初始化

    init_timers();
    hrtimers_init();
    softirq_init();
    timekeeping_init();
    time_init();
    sched_clock_postinit();
    perf_event_init();
   // profile子系统初始化,内核的性能调试工具

    profile_init();
    
   call_function_init();

    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
    // 开启总中断
   early_boot_irqs_disabled = false;

    local_irq_enable();
       // slab分配器后期初始化
    kmem_cache_init_late();

    // 初始化控制台
    console_init();
    if (panic_later)
        panic("Too many boot %s vars at `%s'", panic_later,
              panic_param);
       // 打印lockdep调试模块信息
    lockdep_info();

    // 开启总中断后,用于测试lock inversion bugs
   locking_selftest();

      
      ...
      // 以下3个函数定义为空
    page_cgroup_init();
    debug_objects_mem_init();
    kmemleak_init();
   // 设置每个CPU的页组并初始化

    setup_per_cpu_pageset();
    // 分一致性内存访问(NUMA)初始化
   numa_policy_init();

    if (late_time_init)
        late_time_init();
   // 初始化调度时钟

    sched_clock_init();
    calibrate_delay();
    // PID分配映射初始化
   pidmap_init();
   // 匿名虚拟内存域初始化

    anon_vma_init();
    acpi_early_init();
#ifdef CONFIG_X86
    if (efi_enabled(EFI_RUNTIME_SERVICES))
        efi_enter_virtual_mode();
#endif
#ifdef CONFIG_X86_ESPFIX64
    /* Should be run before the first non-init thread is created */
    init_espfix_bsp();
#endif
    thread_info_cache_init();
   // 任务信用系统初始化

    cred_init();
    // 进程创建机制初始化
   fork_init(totalram_pages);

    proc_caches_init();
    // 缓存系统初始化,创建缓存头空间,并检查其大小限制
   buffer_init();

    // 内核密钥管理系统初始化
   key_init();

    // 内核安全框架初始化
   security_init();

    // 内核调试系统后期初始化
   dbg_late_init();

    // 虚拟文件系统缓存初始化
   vfs_caches_init(totalram_pages);

    // 信号管理系统初始化
   signals_init();

    // 页回写机制初始化
    page_writeback_init();
    // proc文件系统初始化
   proc_root_init();

    // 定义为空
   cgroup_init();

    // CPUSET初始化
   cpuset_init();

    // 任务状态早期初始化函数,为任务获取高速缓存并初始化互斥机制
   taskstats_init_early();
   // 任务延迟机制初始化

   delayacct_init();

    check_bugs();

    sfi_init_late();

    if (efi_enabled(EFI_RUNTIME_SERVICES)) {
        efi_late_init();
        efi_free_boot_services();
    }

    ftrace_init();

    // 其余部分初始化
    rest_init();
}

使用gdb在实验楼环境下对内核进行跟踪,在start_kernel()函数中一共加了5个断点,分别是在stark_kernel()
函数、set_task_stack_end_magic()函数、page_address_init()函数、sched_init()函数和rest_init()函数:

完成后查看一下断点信息:

使用continue命令运行内核,在第1个断点start_kernel处停止:

继续运行,在第2个断点set_task_stack_end_magic()函数处停止,使用step命令进入函数内部,准备
查看一下idle进程栈空间尽头的地址stackend,发现内核编译过程已经优化过,无法查看地址:


到达最后一个断点rest_init()函数:

idle进程调用schedule()函数激活其他进程:

三 idle进程和init进程

idle进程或0号进程的进程描述符init_task在init/init_task.c中定义并初始化,使用INIT_TASK宏完成初始化。
存放在init_thread_union变量中thread_info描述符和内核堆栈,由INIT_THREAD_INFO宏完成对它们的初始化。

idle进程是内核静态创建出来的,因此是系统中唯一不通过fork产生的进程。

start_kernel()函数初始化内核需要的所有数据结构,激活中断,然后在rest_init()函数中创建init进程:
kernel_thread(kernel_init, NULL, CLONE_FS);

创建init进程后,idle进程执行
cpu_idle_loop()函数,该函数本质上是在开中断的情况下重复执行hlt
汇编语言指令。只有当没有其他进程处于TASK_RUNNING状态时,调度程序才选择idle进程。此前,至少
调用一次schedule()函数,以激活其他进程。


在多处理器系统中,每个CPU都有一个进程0.只有打开机器电源,计算机的BIOS就启动某一个CPU,同时禁用其他CPU。
运行在CPU0上的idle进程初始化内核数据结构,然后激活其他CPU,并通过copy_process()函数创建另外的idle进程,
把0传递给新创建的idle进程作为他们的PID。

由idle进程创建的内核线程执行init()函数,init()依次完成内核初始化。init()调用execve()函数调用装入可执行程序init。
init内核线程变为一个普通进程,切拥有自己的进程内核数据结构。在系统关闭之前,init进程一直存活,以为内它创建
和监控在操作系统外层执行的所有进程的活动。


参考资料:
《深入理解Linux内核》
《深入理解Linux内核架构》
http://blog.chinaunix.net/uid-26765884-id-3179290.html

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

automaton2015-03-27 23:11:46

555applelc:感谢楼主写出这么好的文章,期待多多分享,让大家都能对内核有更深入的了解。

我会努力的,希望有机会可以交流。

回复 | 举报

555applelc2015-03-25 19:14:26

感谢楼主写出这么好的文章,期待多多分享,让大家都能对内核有更深入的了解。