Linux 2.6启动流程分析(结合开机log分析)

关于Linux启动流程的分析,以kernel 2.6.35.7为背景进行分析。

〇、head.S (arch/arm/boot/compressed/)

start:
  1. 待补充…

一、head.S (arch/arm/kernel/)

ENTRY(stext):
  1. 设置CPSR:CPU为SVC模式,禁止IRQ和FIQ。

  2. 从cp15读得cpuid

  3. __lookup_processor_type:.proc.info.init段中搜索与cpuid一致的proc_info_list.cpu_val,对应结构体的地址保存到r5.proc.info.init段的内容来自arch/arm/mm/proc-*.S,该文件对应不同版本指令集的CPU架构。

  4. 若操作3中未在cpu支持表中找到本cpuid,且开CONFIG_DEBUG_LL的情况下,在控制台打印错误提示“Error: unrecognized/unsupported processor variant (0x”及进死循环。

  5. __lookup_machine_type:*(.arch.info.init)段中匹配uboot通过r1传递过来的机器id。*(.arch.info.init)段的内容来自MACHINE_START宏定义的内容。

  6. 若操作5中未在机器支持表中匹配到目标MACHINE,且开CONFIG_DEBUG_LL的情况下,在控制台打印错误提示“Error: unrecognized/unsupported machine ID (r1 = 0x”及进死循环。

  7. __vet_atags: 验证u-boot通过r2传递过来的atags指针的有效性。

  • ① atag起始地址4字节对齐;
  • ② 第一个标签为ATAG_CORE(通过验证struct tag的成员hdr.taghdr.size)。

  8. __create_page_tables: 在kernel代码前建立大小为16KB的1级页表:

  • ①清零所有页表项;
  • ②映射kernel所处内存起始地址对应的一个段;
  • ③映射整个kernel所覆盖到的内存地址;
  • ④映射DDR在内存地址中头1MB内存(为了正常使用启动参数)。

  9. 通过proc_info_list.__cpu_flush成员间接调用__v7_setup:arch/arm/mm/proc-v7.S)初始化CPU级资源:

  • ①冲刷整个D-cache;
  • ②失效指令和数据TLB,设置TTB1寄存器,设置domain access寄存器,修改PRRR和NMRR以设置内存区属性;
  • ③设置系统控制寄存器SCTLR以开启D-cache、I-cache、MMU等。

  10. __enable_mmu:设置区域访问寄存器和加载页表指针具体如下:

  • ①设置domain access寄存器以划分kernel、user、table、io区域;
  • ②加载页表内存地址到TTBR0;
  • ③根据用户配置再次设置系统控制寄存器SCTLR(如若未定义CONFIG_ALIGNMENT_TRAP则会清除#CR_A位)。

以上代码由于未开启MMU所以均为位置无关码,而在操作9中已经开启了MMU,所以以下流程是位置有关码而使用虚拟地址。

  11. 通过__switch_data间接调用__mmap_switched:arch/arm/kernel/head-common.S)建立C语言执行环境和保存好处理器id机器idatags地址SCTLR的值,跳转到start_kernel。具体步骤如下:

  • ①若__data_loc_data的地址值不同,则需要将从__data_loc开始的data段移动到_data地址上;
  • ②清零bss段;
  • ③设置栈指针为sp = init_thread_union + THREAD_START_SP;即sp设置到了init_task(arch/arm/kernel/init_task.c)的栈顶,从而静态地构建了init_task进程(后续衰退为idle进程)的上下文;
  • ④将处理器ID、机器ID、atags指针、SCTLR的值和清除A位的SCTLR值,保存到.text段(位于arch/arm/kernel/head-common.S首);
  • ⑤直接跳转到start_kernelC语言函数。

二、main.c (init/)

start_kernel():
  1. smp_setup_processor_id():空。

尽早执行代码2、3以初始化lockdep hash

  2. lockdep_init():需定义CONFIG_LOCKDEP,死锁检测模块lockdep的初始化。

  3. debug_objects_early_init():依赖定义CONFIG_DEBUG_OBJECTS,初始化哈希buckets,将静态对象池对象链接到轮询列表中,在这个调用之后对象跟踪器是完全可操作的。

  4. boot_init_stack_canary() :空。

  5. cgroup_init_early():cgroup及其子系统的初始化,打印如下:

[    0.000000] Initializing cgroup subsys cpu

  6. local_irq_disable():禁止IRQ,关闭irqs追踪器(依赖CONFIG_TRACE_IRQFLAGS)。

  7. early_boot_irqs_off()early_init_irq_lock_class():空。

  8. lock_kernel():用大内核锁(Linux 2.6.39 彻底踢出内核) 锁定整个内核,确保没有其他处理器在核心态并行运行。

  9. tick_init():依赖CONFIG_GENERIC_CLOCKEVENTS,初始化滴答时钟控制器。注册了时钟事件的监听器tick_notifier

  10. boot_cpu_init():激活第一个处理器。将当前处理器标记为onlinepresent等。

  11. page_address_init():依赖CONFIG_HIGHMEM(默认未定义),初始化highmem页表。

  12. printk(KERN_NOTICE "%s", linux_banner);打印如下:

[    0.000000] Linux version 2.6.35.7zxd_GEC210-gcd52db5-dirty (gec@ubuntu) (gcc version 4.4.1 (Sourcer++ Lite 2009q3-67) ) #87 PREEMPT Sat May 26 18:13:59 HKT 2018

  13. setup_arch(&command_line)

  • ①通过lookup_processor_type()获得处理器信息表,进而适配处理器的各种控制方法:CPU名、页表缓存TLB管理方法、用户highpage管理方法、cache管理方法;
  • setup_processor():初始化CPU级别相关的变量。

      (1) 打印CPU id信息:

[    0.000000] CPU: ARMv7 Processor [412fc082] revision 2 (ARMv7), cr=10c53c7f

      (2) 初始化init_uts_ns.name为架构名,初始化elf_platformelf_hwcap(elf文件执行环境变量);
      (3) cacheid_init():读取协处理器cp15的寄存器获得cacheid,识别并打印其类型如下:

[    0.000000] CPU: VIPT nonaliasing data cache, VIPT nonaliasing instruction cache

      (4) cpu_proc_init():间接调用了arch/arm/mm/proc-v7.S中的cpu_v7_proc_init函数,空。

  • mdesc = setup_machine(machine_arch_type);:最终调用了汇编函数__lookup_processor_type,获得在mach-xxx.c文件中通过MACHINE_START(_type,_name)间接定义的结构体struct machine_desc实例,并打印结构体成员name如下:
[    0.000000] Machine: zxd_GEC210
  • ④根据mdesc->soft_reboot设置reboot的模式为硬件复位或软件复位;
  • ⑤将在__mmap_switched汇编函数中保存的atags地址转为虚拟地址,兼容更老版本启动参数,转化其为atags格式;save_atags(tags)保存atags的内容(依赖CONFIG_ATAGS_PROC,默认未定义);parse_tags(tags)调用__tagtable(tag, fn)定义的struct tagtable(如下图所示)中的parse成员函数进行解析tag:(1) ATAG_CMDLINE——保存启动命令到default_command_line;(2) ATAG_MEM——记录DDR内存块信息到meminfo;遇到未识别的tag打印"Ignoring unrecognised tag 0x%08x\n"
    这里写图片描述
  • ⑥初始化全局变量struct mm_struct init_mm成员start_codeend_codeend_databrkarch/arm/kernel/vmlinux.lds定义的_text_etext_edata_end
  • ⑦将u-boot传递过来的启动命令复制到boot_command_linecmd_lineparse_early_param()——解析boot_command_line得到参数及其值,参数在do_early_param()中匹配定义在.init.setup段中的struct obs_kernel_param(用include/linux/init.h的宏__setup(str, fn)定义),用其成员setup_func初始化内核。如:(1) "root="——其值保存到变量saved_root_name;(2) "rootfstype="——保存到变量root_fs_names;(3) "console="——解析得到终端的从设备号和配置,保存到变量console_cmdline[0]中;(4)"mem="——无;(5)"mtdparts="——保存到变量cmdline
  • paging_init(mdesc);创建页表项,取代第一章__create_page_tables建的简单页表,划分各种内存区和映射。代码内容多,要求理解内存管理机制才看懂。打印信息如下:
    devicemaps_init(mdesc) --> mdesc->map_io() --> MACHINE_START(SMDKC110, "SMDKC110").map_io() --> smdkc110_map_io() --> s3c24xx_init_clocks(24000000) --> (cpu->init_clocks)(xtal) --> s5pv210_init_clocks(xtal),构建SOC内部时钟树,添加定义在arch/arm/mach-s5pv210/clock.c的时钟源进链表clocks(arch/arm/plat-samsung/clock.c),关闭init_clocks_disable[]init_dmaclocks[]罗列的时钟源;
[    0.000000] Memory policy: ECC disabled, Data cache writeback
[    0.000000] CPU S5PV210/S5PC110 (id 0x43110220)
[    0.000000] S3C24XX Clocks, Copyright 2004 Simtec Electronics
[    0.000000] S5PV210: PLL settings, A=1000000000, M=667000000, E=80000000 V=54000000
[    0.000000] S5PV210: ARMCLK=1000000000, HCLKM=200000000, HCLKD=166750000
[    0.000000] HCLKP=133400000, PCLKM=100000000, PCLKD=83375000, PCLKP=66700000
[    0.000000] sclk_dmc: source is sclk_a2m (0), rate is 200000000
[    0.000000] sclk_onenand: source is hclk_dsys (1), rate is 166750000
[    0.000000] sclk: source is mout_mpll (6), rate is 66700000
[    0.000000] sclk: source is mout_mpll (6), rate is 66700000
[    0.000000] sclk: source is mout_mpll (6), rate is 66700000
[    0.000000] sclk: source is mout_mpll (6), rate is 66700000
[    0.000000] sclk_mixer: source is sclk_dac (0), rate is 54000000
[    0.000000] sclk_spdif: source is sclk_audio (0), rate is 24000000
[    0.000000] sclk_fimc: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_fimc: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_fimc: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_cam0: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_cam1: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_fimd: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_mmc: source is mout_epll (7), rate is 80000000
[    0.000000] sclk_mmc: source is mout_epll (7), rate is 80000000
[    0.000000] sclk_mmc: source is mout_epll (7), rate is 80000000
[    0.000000] sclk_mmc: source is mout_epll (7), rate is 80000000
[    0.000000] sclk_mfc: source is sclk_a2m (0), rate is 200000000
[    0.000000] sclk_g2d: source is sclk_a2m (0), rate is 200000000
[    0.000000] sclk: source is sclk_a2m (0), rate is 200000000
[    0.000000] sclk_csis: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_spi: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_spi: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_pwi: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_pwm: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_mdnie: source is ext_xtal (0), rate is 24000000
[    0.000000] sclk_mdnie_pwm: source is ext_xtal (0), rate is 24000000
[    0.000000] s5pv210: 37748736 bytes system memory reserved for mfc at 0x30b3e000
[    0.000000] s5pv210: 37748736 bytes system memory reserved for mfc at 0x40000000
[    0.000000] s5pv210: 25165824 bytes system memory reserved for fimc0 at 0x42400000
[    0.000000] s5pv210: 10137600 bytes system memory reserved for fimc1 at 0x43c00000
[    0.000000] s5pv210: 25165824 bytes system memory reserved for fimc2 at 0x445ab000
[    0.000000] s5pv210: 8388608 bytes system memory reserved for jpeg at 0x45dab000
[    0.000000] s5pv210: 1536000 bytes system memory reserved for fimd at 0x465ab000
[    0.000000] s5pv210: 3072000 bytes system memory reserved for texstream at 0x46722000
[    0.000000] s5pv210: 3379200 bytes system memory reserved for pmem_gpu1 at 0x46a10000
[    0.000000] s5pv210: 8388608 bytes system memory reserved for g2d at 0x46d49000
  • devicemaps_init(mdesc) --> mdesc->map_io() --> MACHINE_START(SMDKC110, "SMDKC110").map_io() --> smdkc110_map_io() --> s3c24xx_init_uarts(smdkc110_uartcfgs, ARRAY_SIZE(smdkc110_uartcfgs)); --> (cpu->init_uarts)(cfg, no); --> s5pv210_common_init_uarts(cfg, no),根据machine提供的属性构造串口平台设备,并记录到全局变量s3c24xx_uart_devs(arch/arm/plat-samsung/dev-uart.c)中,统计串口设备数到nr_uarts(arch/arm/plat-samsung/init.c)。
  • request_standard_resources(&meminfo, mdesc);iomem_resource内申请meminfo指定的DRAM内存区域作为struct resource,名为"System RAM";然后在"System RAM"内申请kernel_codekernel_datastruct resource。这些resource节点为父子关系。
  • cpu_init();设置各种处理器模式的栈起始地址。IRQ模式的栈地址为stacks[0].irq[0]中止模式的为stacks[0].abt[0]未定义指令异常模式的为stacks[0].und[0]+++ tcm_init();初始化TCM(紧耦合内存),依赖CONFIG_HAVE_TCM(未定义)。+++ 初始化函数指针init_arch_irq为单板struct machine_descinit_irq成员等等,具体为:init_arch_irq = mdesc->init_irq;system_timer = mdesc->timer;init_machine = mdesc->init_machine;+++ early_trap_init();将定义在entry-armv.S的异常中断向量表及其处理分支可用于用户空间使用的内核函数(如__kuser_memory_barrier),信号返回机器码sigreturn_codes系统调用重启机器码syscall_restart_code,重定位到CONFIG_VECTORS_BASE地址上;然后回写该向量页的指令缓存,设置USER MODE的domain权限为client(01),根据创建的page maps表设置0xc0000000开始的地址SVC模式读写,USR模式不能读写。

  14. mm_init_owner(&init_mm, &init_task);空,依赖CONFIG_MM_OWNER(未定义)。

  15. setup_command_line(command_line);将被碰过的启动命令boot_command_line复制到saved_command_line,将未被碰过的启动命令保存到static_command_line,以备未来的引用。

  16. setup_nr_cpu_ids()setup_per_cpu_areas()smp_prepare_boot_cpu(),空,依赖CONFIG_SMP

  17. build_all_zonelists(NULL):建立并初始化所有的struct zonelist,伙伴系统分配器会从zonelist开始分配内存,zonelist有一个_zonerefs数组,数组里有一个成员会指向zone数据结构。_zonerefs数组的第一个成员指向的zone是页面分配器的第一个候选者,其他成员则是第一个候选者分配失败之后才考虑,优先级逐渐降低。

[    0.000000] Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 130048

  18. page_alloc_init(); 空,依赖于CONFIG_HOTPLUG_CPU(未定义)。

  19. 打印字符串boot_command_lineparse_early_param()记录到在setup_arch()被调用过而没有执行实际的内容。parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);匹配启动命令中,与设备驱动用宏module_param(name, type, perm)(原型位于include/linux/moduleparam.h)定义并链接进__param段的struct kernel_param之成员name同名的变量,最终将启动命令中的变量值,赋值到设备驱动定义的变量。

[    0.000000] Kernel command line: root=/dev/mtdblock4 rootfstype=yaffs console=ttySAC0,115200n8 mem=512M mtdparts=s5p-nand:1m(boot),5m@0x100000(recovery),5m@0x600000(kernel),3m@0xb00000(ramdisk),-(rootfs)

以下调用仍在大量使用bootmem的内存,需在调用kmem_cache_init()前执行。

  20. pidhash_init();从bootmem分配内存生产pid哈希表实体pid_hash,并初始化之。pid哈希表根据机器中内存的大小进行扩展;1GB或更大内存的机器会有从最少16 到4096 个slots。打印如下信息:

[    0.000000] PID hash table entries: 2048 (order: 1, 8192 bytes)

  21. vfs_caches_init_early();从bootmem分配内存生产dentry和inode哈希表实体dentry_hashtableinode_hashtable,并初始化之。目录项缓存哈希表(散列表)dentry_hashtable和相应的哈希函数用来快速地将给定路径解析为相关目录项对象。inode哈希表inode_hashtable用于快速查找代表具体文件的索引节点。打印如下信息:

[    0.000000] Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)
[    0.000000] Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)

  22. sort_main_extable();以保存在__ex_table段的结构体struct exception_table_entry之成员insn为键值进行堆排序。内核发生未对齐内存访问(alignment)、一级页表地址转换错误(First Level Translation Fault)、缺页异常(page fault)后,异常处理流程中会在__ex_table段利用二分法搜索对应的struct exception_table_entry,并执行其成员fixup指定地址处函数进行修复动作。所以需对__ex_table段内容进行排序,才方便后续的二分法搜索。

  23. trap_init();空。

  24. mm_init();建立kernel内存分配器,启用伙伴算法。

  • page_cgroup_init_flatmem();,空;
  • mem_init();将bootmem分配器管理的低端内存及通过meminfo得知的高端内存释放到伙伴系统中;bootmem位图本身占用的低端内存物理页也被释放进伙伴系统,对于内核、初始页表、struct page实例、percpu变量、dentry_hashtable、inode_hashtable等已经被占用的区域不会被释放(对于内核开始的一段,后面会释放)。统计并打印总物理页数、已分配(reserved_pages)、未分配(free_pages)页数,和打印kernel的虚拟内存分布:
[    0.000000] Memory: 512MB = 512MB total
[    0.000000] Memory: 355436k/355436k available, 168852k reserved, 0K highmem
[    0.000000] Virtual kernel memory layout:
[    0.000000]     vector  : 0xffff0000 - 0xffff1000   (   4 kB)
[    0.000000]     fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
[    0.000000]     DMA     : 0xff000000 - 0xffe00000   (  14 MB)
[    0.000000]     vmalloc : 0xe0800000 - 0xfc000000   ( 440 MB)
[    0.000000]     lowmem  : 0xc0000000 - 0xe0000000   ( 512 MB)
[    0.000000]     modules : 0xbf000000 - 0xc0000000   (  16 MB)
[    0.000000]       .init : 0xc0008000 - 0xc0032000   ( 168 kB)
[    0.000000]       .text : 0xc0032000 - 0xc0631000   (6140 kB)
[    0.000000]       .data : 0xc0632000 - 0xc0677360   ( 277 kB)
  • kmem_cache_init()(mm/slub.c),创建用于kmalloc()函数的slab描述符struct kmem_cache kmalloc_caches[KMALLOC_CACHES] __cacheline_aligned;。例如16B、32B、64B、128B、···、32MB等大小,分别创建名为kmalloc-16、kmalloc-32、kmalloc-64······的slab描述符;如分配30Byte的一个小内存块时调用kmalloc(30, GFP_KERNEL),系统会从名为kmalloc-32的slab描述符中分配一个对象出来。打印目前生成的slab层数量,缓存行大小等内容如下:
[    0.000000] SLUB: Genslabs=9, HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
  • pgtable_cache_init();空。
  • vmalloc_init();初始化vmap_area_pcpu_holeVMALLOC_END。由于struct vm_struct *vmlist仍为NULL,所以没有做过多初始化工作。

在启动任何中断之前,需设置调度器(如计时器中断)。在调用smp_init()时完成拓扑设置,但与此同时,我们仍有一个有效的调度器。

  25. sched_init();初始化调度器。对相关数据结构分配内存,初始化root_task_group,初始化每个CPU的rq队列(包括其中的cfs队列和实时进程队列),将init_task进程转变为idle进程

  26.preempt_disable();irqs_disabled(),关闭抢占和禁止irq中断。在我们第一次调用cpu_idle()之前,在早期启动调度是非常脆弱的。

  27. rcu_init();(kernel/rcutree.c),构建RCU层次结构。初始化3个struct rcu_state,分别是rcu_sched_statercu_bh_statercu_preempt_state;在rcu_init_one()中,构建了rcu_statercu_nodercu_data之间的树形结构关系,初始化了关键的数据结构成员。单独注册了一个softirq回调函数rcu_process_callbacks()。注册了CPU Notifier子系统。给系统中每个online的CPU都发送了一个CPU_UP_PREPARE事件到CPU Notifier子系统中,在回调函数rcu_cpu_notify()中处理事件。打印如下信息:

[    0.000000] Hierarchical RCU implementation.
[    0.000000] 	RCU-based detection of stalled CPUs is disabled.
[    0.000000] 	Verbose stalled-CPUs detection is disabled.

  28. radix_tree_init();基数树机制建立。Linux基数树(radix tree)是将指针与long整数键值相关联的机制,它存储有效率,并且可快速查询,用于指针与整数值的映射(如:IDR机制)、内存管理等。流程为:调用函数kmem_cache_create()分配类型为radix_tree_node、名称为radix_tree_node的slab高速缓存,存入全局变量radix_tree_node_cachep中 ;调用函数radix_tree_init_maxindex初始化树中各层的最大索引数组height_to_maxindex(1-6层) ;调用函数hotcpu_notifier,设置热插拔cpu时的回调radix_tree_callback()

  29. early_irq_init();(kernel/irq/handle.c),初始化struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp的各数组元素的结构体成员irqkstat_irqs,更多操作只用于smp结构。打印irq中断总数:

[    0.000000] NR_IRQS:393

  30. init_IRQ();将kernel/irq/handle.c定义的struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp之成员status或上IRQ_NOREQUEST | IRQ_NOPROBE,即标记所有IRQ为不能被申请和触发;通过init_arch_irq()间接调用mdesc->init_irq(在arch/arm/mach-xxx/mach-yyy.c定义),我的单板最终调用的是s5pv210_init_irq()

  • s5p_init_irq() --> vic_init()
    打印VIC的虚拟地址、cellid和制造商ID。初始化向量中断控制器VIC为禁止对应的中断;vic_init2()初始化了ARM primecell PL192和PL190(外设IP);vic_set_irq_sources()设置了irq_desc[]中代表每一个中断源的成员chipvic_chipchip_data为VIC基地址、handle_irqhandle_level_irq()status清除标志IRQ_NOREQUESTIRQ_NOPROBE(即有效和可触发);vic_pm_register()初始化struct vic_device vic_devices[CONFIG_ARM_VIC_NR]以备电源管理控制。
  • s5p_init_irq() --> s3c_init_vic_timer_irq()
    初始化timer中断描述符。修改irq_desc[]中代表timer0~timer4在VIC的中断描述符之成员handle_irqs3c_irq_demux_vic_timer(),代表timer内部的中断描述符修改成员chips3c_irq_timerhandle_irqhandle_level_irqstatus标志为有效,handler_data为内部中断号。
  • s5p_init_irq() --> s3c_init_uart_irqs()
    初始化uart中断描述符。mask掉uart所有分支中断,初始化分支中断Modem、Transmit、Error、Receive对应的中断描述符各成员;chips3c_irq_uartchip_data&uart_irqs[0~3]handle_irqhandle_level_irq()status标志有效。而uart主中断描述符的成员handler_data设为&uart_irqs[0~3]handle_irqs3c_irq_demux_uart()
  • s5p_init_irq() --> chip = get_irq_chip(wakeup_source[irq]); chip->set_wake = s3c_irq_wake;
    wakeup_source[]罗列的唤醒中断源的irq_desc[irq].chip->set_wake设置为s3c_irq_wake(),以设置SOC厂商提供的设置唤醒源实例到中断描述符中。
[    0.000000] VIC @fd000000: id 0x00041192, vendor 0x41
[    0.000000] VIC @fd010000: id 0x00041192, vendor 0x41
[    0.000000] VIC @fd020000: id 0x00041192, vendor 0x41
[    0.000000] VIC @fd030000: id 0x00041192, vendor 0x41

  31. prio_tree_init();初始化优先搜索树的基础图表unsigned long index_bits_to_maxindex[BITS_PER_LONG];。如下该图表约束了prio-tree的高度,数组的内容是该索引下最大的heap,每一个树节点都有一个index_bits字段,它表示了用index_bits个比特就能表达最大的heap,而此index_bits减1正是“这些”比特在数组中的下标。

bit-usedarray-indexmax-heap
101b
2111b
32111b
431111b
:::
323111111111111111111111111111111111b

  32. init_timers();建立低精度定时器机制。

  • timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id());
    内核定时器机制的初始化,执行返回结果必须为NOTIFY_OK。初始化了struct tvec_base boot_tvec_bases的各成员,struct tvec_base用于组织、管理所有软件定时器;在 SMP 系统中,每个 CPU 有一个;该结构体更多细节参考《Linux内部的时钟处理机制全面剖析》
  • init_timer_stats();
  • register_cpu_notifier(&timers_nb);
    向 cpu_chain 通知链注册元素 timers_nb,该元素的回调函数用于初始化指定 CPU 上软件定时器机制相关数据结构。
  • open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    注册定时器软中断处理函数。最终执行代码等价softirq_vec[TIMER_SOFTIRQ].action = run_timer_softirq

  33. hrtimers_init();建立高精度定时器机制。

  • hrtimer_cpu_notify(&hrtimers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id());
    内核高精度定时器的初始化,主要是初始化了struct hrtimer_cpu_base的各成员。
  • register_cpu_notifier(&hrtimers_nb);
    向 cpu_chain 通知链注册元素 hrtimers_nb,该元素的回调函数用于初始化指定 CPU 上高精度定时器机制相关数据结构。
  • open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
    注册高精度定时器软中断处理函数。最终执行代码等价softirq_vec[HRTIMER_SOFTIRQ].action =run_hrtimer_softirq

  34. softirq_init();初始化tasklet基础机制的链表tasklet_vectasklet_hi_vec;初始化所有软件中断的工作链表softirq_work_list;向 cpu_chain 通知链注册元素 remote_softirq_cpu_notifier,该元素的回调函数执行将死掉或冰冻的CPU的softirq_work_list分别附加到本地CPU的工作表并触发它们;注册tasklet和高优先级tasklet软件中断(TASKLET_SOFTIRQHI_SOFTIRQ),中断响应函数分别为tasklet_actiontasklet_hi_action

  35. timekeeping_init();初始化提供时间服务的timekeeping模块。更多细节可参考《Linux时间子系统之(四):timekeeping》

  • read_persistent_clock(&now); read_boot_clock(&boot);
    从带有备用电池的不间断时钟中读出时间信息,和读出启动时间信息;这两函数需要arch实现,例如从RTC读时间;
  • ntp_init();
    ntp机制相关的变量的初始化,hrtimer机制的初始化;
  • clock = clocksource_default_clock(); if (clock->enable) clock->enable(clock);
    为timekeeping设置默认参考时钟源,采用了一个在此时一定是ready的clock source,则是基于jiffies 的clock source;
  • timekeeper_setup_internals(clock);
    建立默认参考时钟源与timekeeping的伙伴关系;
  • ⑤初始化real time clock、monotonic clock和monotonic raw clock

  36. time_init();执行路径system_timer->init();–> mdesc->timer->init();(arch/arm/mach-s5pv210/mach-zxd_gec210.c) --> s5p_systimer.init();(arch/arm/plat-s5p/hr-time-rtc.c) --> s5p_timer_init(),打通定时器资源及其中断源。

  • ①初始化定时器相关全局变量;间接调用s5pv210_clk_ip3_ctrl()启动时钟源systimerrtc(arch/arm/mach-s5pv210/clock.c);
  • s5p_timer_setup();
    RTC硬件单元(参考SOC的datasheet之《4.2.1 REAL TIME CLOCK OPERATION DESCRIPTION》)的时钟由外部32.768kHz晶振通过xrtcxti(抽象于arch/arm/plat-s5p/clock.c)引脚输入,RTC事件时钟用静态变量clk_event描述;设置滴答定时器的频率为256Hz,初始化并注册其抽象变量struct clock_event_device clockevent_tick_timer,并打印其时间转换成计数值的乘法因子mult(clock_ticks = (nanoseconds * mult) >> shift),最大和最小可定时周期为max_delta_nsmin_delta_ns,计数速率为32768Hz,设置的滴答周期为256Hz,如下log所示。调度时钟(用struct clk *clk_sched描述)参考时钟源由外部时钟xusbxti提供;执行s5p_init_clocksource()——初始化struct clocksource clocksource_s5p,初始化system timer(参考datasheet的section 07_timer之2.1 OVERVIEW OF SYSTEM TIMER)硬件单元(设置其参考时钟源为xusbxti,输入进定时器的时钟频率为125kHz=24MHz/192,计数值为0xffffffff,中断自动重装载计数值,启动定时器,使能中断源),初始化sched_timer_running为1。clocksource_register(&clocksource_s5p)注册clocksource_s5p进内核。
[    0.000000] mult[140737]
[    0.000000] max_delta_ns[2937815369]
[    0.000000] min_delta_ns[30517]
[    0.000000] rate[32768]
[    0.000000] HZ[256]
  • setup_irq(IRQ_RTC_TIC, &s5p_tick_timer_irq);
    向内核导入rtc-tick中断描述符s5p_tick_timer_irq
  • setup_irq(IRQ_SYSTIMER, &s5p_systimer_irq);
    向内核导入system timer中断描述符s5p_systimer_irq

  37. profile_init();默认未定义所依赖的CONFIG_PROFILING,空操作。

  38. if (!irqs_disabled())检查cpsr寄存器的总中断位是否开启,此时开启总中断过早。

  39. early_boot_irqs_on();默认未定义CONFIG_TRACE_IRQFLAGS致空。

  40. local_irq_enable();最终执行汇编指令cpsie i操作cpsr的中断标志位,开启中断

  41. gfp_allowed_mask = __GFP_BITS_MASK;初始化全局页分配标志位掩码,默认值为全功能权限;

  42. kmem_cache_init_late();(mm/slub.c),空。

  43. console_init();(drivers\char\tty_io.c),控制台——tty核心所依赖的线路规程(line discipline)和tty驱动的初始化,细节如下:

  • tty_ldisc_begin();注册默认的N_TTY线路规程实例tty_ldisc_N_TTY到tty线路规程层tty_ldisc.c中;
  • ②执行控制台初始化调用段.con_initcall.init中的函数指针(用宏console_initcall()声明的函数),我配置的单板执行了函数s5pv210_serial_console_init():用s3c24xx_uart_drv(tty_driver)构造控制台驱动s3c24xx_serial_console,使用machine提供的参数配置串口,注册s3c24xx_serial_consoleprintk.c中输出如下log。
[    0.000000] console [ttySAC0] enabled

  44. lockdep_info();空。需定义CONFIG_LOCKDEP,为了打印死锁检测模块lockdep的属性。

  45. locking_selftest();空。依赖CONFIG_DEBUG_LOCKING_API_SELFTESTS

  46. if (initrd_start && !initrd_below_start_ok && page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn)在initrd启动成功的前提下打印其内存地址,实际if条件不满足。

  47. page_cgroup_init();执行完mem_cgroup_disabled()(依赖CONFIG_CGROUP_MEM_RES_CTLR)后退出该函数。

  48. enable_debug_pagealloc();依赖CONFIG_DEBUG_PAGEALLOC,空。

  49. kmemtrace_init();依赖CONFIG_KMEMTRACE,空。

  50. kmemleak_init();依赖CONFIG_DEBUG_KMEMLEAK,空。

  51. debug_objects_mem_init();依赖CONFIG_DEBUG_OBJECTS,空。

  52. idr_init_cache();调用函数kmem_cache_create()分配类型为idr_layer、名称为"idr_layer_cache"的slab高速缓存,存入全局变量idr_layer_cache中。idr机制内部采用radix树实现,用于将整数和指针关联起来,如应用到I2C的设备地址和设备结构体的关联。

  53. setup_per_cpu_pageset();前面所用per_cpu管理结构都是临时静态分配的。现在per_cpu机制已经启动,所以重新动态分配一个per_cpu管理结构并初始化。——遍历每一个内存域,如果某内存域有实际内存,就初始化该内存域的per_cpu管理结构。

  54. numa_policy_init();依赖CONFIG_NUMA,空。

  55. late_time_init,该函数指针未在单板相关代码中赋值。

  56. sched_clock_init();初始化全局变量sched_clock_running为1。

  57. calibrate_delay();先粗略获得__delay()(arch/arm/lib/delay.S) 延迟一个jiffies内部所需要的循环次数loops_per_jiffy,再通过折半查找法将该数值精确到LPS_PREC(默认为8) 位。打印loops_per_jiffy/(500000/HZ)(即所谓BogoMIPS值,是Linux操作系统中衡量计算机处理器运行速度的的一种尺度),以及loops_per_jiffy的值,如下所示。

[    0.465018] Calibrating delay loop... 998.15 BogoMIPS (lpj=1949696)

  58. pidmap_init();通过比较来bump出可分配的最大和最小pid值,分别更新到变量pid_maxpid_max_min并打印如下所示;初始化了init_pid_ns.pidmap[0]的成员:给pid映射页(位图)申请内存,置位位图中的0位以保留pid 0(idle进程);调用宏KMEM_CACHE()(实为kmem_cache_create()分配类型为struct pid、名称为"pid"的slab高速缓存,存入变量init_pid_ns.pid_cachep中。

[    0.546806] pid_max: default: 32768 minimum: 301

  59. anon_vma_init();调用kmem_cache_create()分配类型为struct anon_vma、名称为"anon_vma"的slab高速缓存,存入变量anon_vma_cachep中。调用宏KMEM_CACHE()(实为kmem_cache_create()分配类型为struct anon_vma_chain、名称为"anon_vma_chain"的slab高速缓存,存入变量anon_vma_chain_cachep中。

  60. thread_info_cache_init();空。

  61. cred_init();调用kmem_cache_create()分配类型为struct cred、名称为"cred_jar"的slab高速缓存,存入变量cred_jar中。

  62. fork_init(totalram_pages);调用kmem_cache_create()分配类型为struct task_struct、名称为"task_struct"的slab高速缓存,存入变量task_struct_cachep中。执行架构相关特定task缓存初始化。所有进程描述符和内核堆栈所占的空间不能超过物理空间的1/8,所以用总物理页面数求得默认最大线程数max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);,且该变量不得小于20条以用于启动系统。修改init_task进程的“资源限制结构体”中系统最大进程数挂起信号量数max_threads/2,可通过$ cat /proc/self/limits查看。

  • init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
  • init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
  • init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC];

  63. proc_caches_init();首先创建了进程相关结构体的slab描述符——调用kmem_cache_create()分配类型分别为struct sighand_structstruct signal_structstruct files_structstruct fs_structstruct mm_structstruct vm_area_struct,名称分别为"sighand_cache"、“signal_cache”、“files_cache”、“fs_cache”、“mm_struct”、"vm_area_struct"的slab高速缓存,存入变量sighand_cachepsignal_cachepfiles_cachepfs_cachepmm_cachepvm_area_cachep中。然后调用mmap_init()CPU域初始化全局变量vm_committed_as.count为0。

  64. buffer_init();(fs\buffer.c)依赖CONFIG_BLOCK。调用kmem_cache_create()分配类型为struct buffer_head、名称为"buffer_head"的slab高速缓存,存入变量bh_cachep中。kernel要求限制buffer_head占用ZONE_NORMAL页面低于10%,通过空闲页面数算得最多可分配的缓冲区头数存于全局变量max_buffer_heads。调用函数hotcpu_notifier,设置热插拔cpu时的回调buffer_cpu_notify()

  • 背景:kernel操作磁盘块时,需要先将其内容调入内存中(所谓“缓冲区”),这就衍生出些相关控制信息(如块属于哪一个块设备,块对应于哪个缓冲区),所以使用struct buffer_head作为缓冲区的描述符,即“缓冲区头”,包含了kernel操作缓冲区所需的全部信息。

  65. key_init();依赖未定义的CONFIG_KEYS,空。

  66. security_init();依赖未定义的CONFIG_SECURITY,空。

  67. dbg_late_init();依赖未定义的CONFIG_KGDB,与kgdb功能相关,空。

  68. vfs_caches_init(totalram_pages);vfs、sysfs、rootfs、块设备fs抽象层、字符设备的bdi的初始化,具体如下:

  • ①求保守可分配页面数:mempages = 总物理页面 - kernel占用页面 x 150%;
  • ②建立用于分配文件路径名对象的slab描述符:调用kmem_cache_create()分配长度为PATH_MAX、名称为"names_cache"的slab高速缓存描述符,存入变量names_cachep中;
  • dcache_init();建立用于分配目录项对象的slab描述符:调用kmem_cache_create()分配类型为struct dentry、名称为"dentry"的slab高速缓存描述符,存入变量dentry_cache中。调用register_shrinker注册了回收器struct shrinker dcache_shrinker,其回收函数为shrink_dcache_memorydentry是由slab分配,用于表示虚拟文件系统目录结构的对象;在dentry的引用记数被减为0的时候,dentry并不是直接被释放,而是被放到一个LRU链表中缓存起来,便于后续的使用。在内存使用紧张时,需要收缩缓存大小,通常回收dentry来腾出内存给其他地方使用;shrink_dcache_memory()扫描LRU链表,获取每个超级块的未被使用dentry的LRU,然后从中回收一些最老的dentry。
  • inode_init();建立用于分配索引节点对象的slab描述符:调用kmem_cache_create()分配类型为struct inode、名称为"inode_cache"的slab高速缓存描述符,存入变量inode_cachep中。调用register_shrinker注册了回收器struct shrinker icache_shrinker,其回收函数为shrink_icache_memory随着dentry的释放,对应的inode将被减引用,也可能引起inode被释放。inode被释放后也是放在LRU链表中,回调函数shrink_icache_memory()用来回收这些未使用的inode,从而inode中关联的磁盘高速缓存也将被释放。
  • files_init(mempages);建立用于分配文件对象的slab描述符:调用kmem_cache_create()分配类型为struct file、名称为"filp"的slab高速缓存描述符,存入变量filp_cachep中。保守估算文件对象与相关联的索引节点和目录项对象占用约1kB,限制这些内容占用内存为可分配页面数的10%,则求得系统最多能同时打开的文件数赋值于files_stat.max_files$ cat /proc/sys/fs/file-max),且限制其值不小于NR_FILE。初始化struct fdtable_defer fdtable_defer_list,初始化sysctl_nr_open_max。初始化CPU域变量struct percpu_counter nr_files.count为0;
  • mnt_init();初始化读写信号量struct rw_semaphore namespace_sem。建立用于分配文件系统对象的slab描述符:调用kmem_cache_create()分配类型为struct vfsmount、名称为"mnt_cache"的slab高速缓存描述符,存入变量mnt_cache中。分配一页内存用于存储挂载点哈希表,打印并初始化该表可存储的记录数如下所示。sysfs_init()sysfs文件系统是处于内存中的虚拟文件系统,各种设备的拓扑结构导出为文件后的产物,建立struct sysfs_dirent的slab描述符,初始化sysfs内的inode,注册并挂载struct file_system_type sysfs_fs_type。创建名为"fs"的kobject并添加进内核,保存于struct kobject *fs_kobj;,从而映射为目录/sys/fsinit_rootfs();(fs/ramfs/inode.c)初始化ramfs文件系统的bdi设备struct backing_dev_info ramfs_backing_dev_info,调用register_filesystem()注册根文件系统struct file_system_type rootfs_fs_type到kernel。init_mount_tree()挂载并初始化根文件系统rootfs,并初始化init_task.nsproxy->mnt_nscurrent->fs
[    0.546920] Mount-cache hash table entries: 512
  • bdev_cache_init();创建用于分配struct bdev_inode的名为"bdev_cache"的slab描述符,注册并挂载名为"bdev"用于管理块设备的文件系统struct file_system_type bd_type,初始化超级块对象blockdev_superblock
  • chrdev_init();字符设备相关,调用kobj_map_init()初始化cdev_map,初始化bdi设备struct backing_dev_info directly_mappable_cdev_bdi

  69. signals_init();建立用于分配信号队列的slab描述符:调用kmem_cache_create()分配类型为struct sigqueue、名称为"sigqueue"的slab高速缓存描述符,存入变量sigqueue_cachep中。

  70. page_writeback_init();

  • writeback_set_ratelimit():初始化回写页阈值ratelimit_pages = vm_total_pages / (num_online_cpus() * 32);,且16 ≤ratelimit_pages≤1024
  • register_cpu_notifier(&ratelimit_nb):向 cpu_chain 通知链注册元素ratelimit_nb,该元素的回调函数ratelimit_handler用于CPU启动/关闭时,调用writeback_set_ratelimit()初始化回写页阈值。
  • shift = calc_period_shift();:已知脏内存占总可脏内存百分比阈值vm_dirty_ratio(脏内存超过该值则启动回写),和可脏总内存,求得脏内存页数阈值,函数返回该值的shift
  • prop_descriptor_init(&vm_completions, shift);初始化表示页回写模块的总虚拟内存的比例描述符struct prop_descriptor vm_completions;
  • prop_descriptor_init(&vm_dirties, shift);初始化表示页回写模块的脏虚拟内存的比例描述符struct prop_descriptor vm_dirties;

  71. proc_root_init();"/proc"文件系统的注册挂载,及其各种主要节点的创建。

  • ①创建"struct proc_inode"的slab描述符;往kernel注册并挂载文件系统对象struct file_system_type proc_fs_type,得到vfs挂载对象struct vfsmount *proc_mnt
  • ②建立或从/proc/self/链接得到"/proc"下的节点(目录)mountsnetsysvipcfsdriverfs/nfsdtty(包括里面的节点)、device-tree(依赖CONFIG_PROC_DEVICETREE)、bussys(初始化了proc_iopsproc_fops)。

  72. cgroup_init();cgroup(control groups),它提供了一套机制用于控制一组特定进程对资源的使用。初始化cgroup的bdi设备struct backing_dev_info cgroup_backing_dev_info,初始化struct cgroup_subsys *subsys[CGROUP_SUBSYS_COUNT]中包含的cgroup子系统并打印如下内容,并将这写子系统以哈希表形式组织添加进struct css_set init_css_set中。注册"cgroup"虚拟文件系统(/sys/fs/cgroup),在"/proc"下创建文件cgroups(/proc/cgroups),其文件操作集为const struct file_operations proc_cgroupstats_operations

[    0.547350] Initializing cgroup subsys debug
[    0.547403] Initializing cgroup subsys cpuacct
[    0.547447] Initializing cgroup subsys freezer

  73. cpuset_init();依赖未定义的CONFIG_CPUSETS,空。

  74. taskstats_init_early();依赖未定义的CONFIG_TASKSTATS,空。

  75. delayacct_init();依赖未定义的CONFIG_TASK_DELAY_ACCT,空。

  76. check_bugs();(arch/arm/include/asm/bugs.h),检查架构相关的bugs,实际执行check_writebuffer_bugs(),将物理页面映射到不同虚拟地址上,对不同虚拟地址上先后写不同的内容,检查他们的一致性,以避免“物理地址别名问题”,结果打印如下。

[    0.549411] CPU: Testing write buffer coherency: ok

  77. acpi_early_init();高级配置与电源接口(Advanced Configuration and Power Interface)功能的初始化,依赖未定义的CONFIG_ACPI,空。

  78. sfi_init_late();简单固件接口(SFI)是英特尔公司开发的一种轻量级固件方法,用于将静态表导出到操作系统。依赖未定义的CONFIG_SFI,空。

  79. ftrace_init();ftrace(function tracer) 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析。依赖未定义的CONFIG_FTRACE_MCOUNT_RECORD,空。

  80. rest_init();

  • rcu_scheduler_starting();(kernel/rcutree.c) 标志rcu_scheduler_active为1,标识rcu调度器为启动状态;

我们需要先生成init进程,以便它获得pid=1,然而init进程最终想创建kthreads内核线程,如果我们在创建kthreadd守护进程(pid=2)之前进行调度到init进程,将会发生OOPS。所以在init进程一进来就在等待完成量kthreadd_done

  • kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);创建init进程(pid=1),init进程一进来就在等待完成量kthreadd_done
  • numa_default_policy();该函数想设定内存分配策略为MPOL_DEFAULT,但依赖未定义的CONFIG_NUMA,空。
  • kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);创建kthreadd守护进程(pid=2),该进程的使命就是对其他内核线程进行管理和调度;
  • threadd_task = find_task_by_pid_ns(pid, &init_pid_ns);init_pid_ns即pid命名空间中查找kthreadd守护进程并获取其task_struct以保存到全局变量threadd_task中;
  • complete(&kthreadd_done);发射完成量kthreadd_done提示kthreadd守护进程已经创建完成,以允许init进程继续执行;
  • unlock_kernel();解除大内核锁(Linux 2.6.39 彻底踢出内核),允许其他处理器在核心态并行运行;
  • init_idle_bootup_task(current);设置idle进程的调度器类型为最低等级的idle_sched_class,调度器类型优先级为:stop > deadline > real time > fair > idle
  • preempt_enable_no_resched();内核抢占计数preempt_count减1,即开启内核抢占,但不立即进行抢占式调度;
  • schedule();主动让出CPU,调度到其它进程;
  • preempt_disable(); cpu_idle();禁止抢占,然后进入idle进程主循环体;开启fiq中断,在while(1)循环中,通过need_resched()获悉当前调度器是否需要进行调度,从而决定是cpu进入低功耗模式(SOC相关的代码),还是主动让出CPU发生调度;另外还会通过leds_event()来发射当前idle进程的状态(依赖于CONFIG_LEDS)。

  81. kernel_init(); 待补充…

  82. kthreadd(); 待补充…

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值