《Linux内核分析》MOOC课程第三次实验作业

来源:互联网 发布:多重搜索算法 编辑:程序博客网 时间:2024/05/17 22:32

ustc赵凌云  《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

操作系统启动综述

   计算机启动的过程其实在Andrew S.Tanenbaum所著的《现代操作系统》(中文版第18页)中就有大略的描述:

   1.计算机启动时,存储在RAM中的BIOS程序检查计算机的所有设备,包括RAM、键盘、鼠标、ISA及PCI总线上的设备等,这些设备将被记录下来。

   2.BIOS寻找可引导介质,从软盘、CD或硬盘中加载操作系统,将控制权交给操作系统中的引导程序。

   3.操作系统询问BIOS,获得各个设备的配置信息,然后开始进行初始化工作。

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();
}

总结:

 1 )idle和1号进程怎么来的:

函数创建第一个核心进程kernel_init,同时init_task进程继续对Linux系统初始化。在完成初始化后,init_task会退化为cpu_idle进程,当Core0的就绪队列中没有其它进程时,该进程将会获得CPU运行。新创建的1号进程kernel_init将会逐个启动次CPU,并最终创建用户进程!

kernel_init 1号线程初始化

主要包括三方面

第一:引导SMP系统中的其它CPU(AP(Aplication Processor))

第二:调用do_basic_setup()函数,完成Linux系统其它模块的初始化;

第三:更换核心进程kernel_init为普通进程之后,完成对Linux系统的二次引导,即对Linux系统应用程序的引导

 

2)start_kernel如何运行的:

调用load_up_mmu()重新装载MMU相关的寄存器,开启MMU并跳到start_kernel

再次让CPU进入是实地址模式,去运行load_up_mmu()函数。这样做的目的是让core0在实模式下调用load_up_mmu()函数来重新装载MMU相关的寄存器,比如SDR1BAT寄存器等。之所以要重新转载,是因为我们在<11>blinitial_bats,创建的临时BAT块地址映射,只是启动的第一阶段用到的临时映射,现在这个临时地址映射需要舍弃了,我们需要重新初始化MMU,来建立正式的MMU地址映射从__start()start_here()再到调用start_kernel(),主要的工作与当前目标板的硬件结构密切相关,包含对一些底层硬件进行最基本初始化操作等等,从start_kernel()开始的初始化操作与处理器的类型基本无关了。start_kernel本阶段也是有0号线程init_task中调用的,将完成Linux内核核心数据结构的初始化,最终创建1号线程kernel_init,最后由1号内核线程启动1号用户进程。



0 0