关于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.tag
和hdr.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、机器id、atags地址、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_kernel
C语言函数。
二、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()
:激活第一个处理器。将当前处理器标记为online
、present
等。
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_platform
、elf_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_code
、end_code
、end_data
、brk
为arch/arm/kernel/vmlinux.lds定义的_text
、_etext
、_edata
、_end
。 - ⑦将u-boot传递过来的启动命令复制到
boot_command_line
、cmd_line
;parse_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_code
和kernel_data
的struct 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_desc
的init_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_line
。parse_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_hashtable
、inode_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_hole
为VMALLOC_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_state
、rcu_bh_state
和rcu_preempt_state
;在rcu_init_one()
中,构建了rcu_state
、rcu_node
和rcu_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
的各数组元素的结构体成员irq
、kstat_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[]
中代表每一个中断源的成员chip
为vic_chip
、chip_data
为VIC基地址、handle_irq
为handle_level_irq()
、status
清除标志IRQ_NOREQUEST
和IRQ_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_irq
为s3c_irq_demux_vic_timer()
,代表timer内部的中断描述符修改成员chip
为s3c_irq_timer
,handle_irq
为handle_level_irq
,status
标志为有效,handler_data
为内部中断号。 - ③
s5p_init_irq() --> s3c_init_uart_irqs()
:
初始化uart中断描述符。mask掉uart所有分支中断,初始化分支中断Modem、Transmit、Error、Receive对应的中断描述符各成员;chip
为s3c_irq_uart
,chip_data
为&uart_irqs[0~3]
,handle_irq
为handle_level_irq()
,status
标志有效。而uart主中断描述符的成员handler_data
设为&uart_irqs[0~3]
,handle_irq
为s3c_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-used | array-index | max-heap |
---|---|---|
1 | 0 | 1b |
2 | 1 | 11b |
3 | 2 | 111b |
4 | 3 | 1111b |
: | : | : |
32 | 31 | 11111111111111111111111111111111b |
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_vec
和tasklet_hi_vec
;初始化所有软件中断的工作链表softirq_work_list
;向 cpu_chain 通知链注册元素 remote_softirq_cpu_notifier
,该元素的回调函数执行将死掉或冰冻的CPU的softirq_work_list
分别附加到本地CPU的工作表并触发它们;注册tasklet和高优先级tasklet软件中断(TASKLET_SOFTIRQ
与HI_SOFTIRQ
),中断响应函数分别为tasklet_action
、tasklet_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()
启动时钟源systimer
和rtc
(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_ns
与min_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_console
到printk.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_max
和pid_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_struct、struct signal_struct、struct files_struct、struct fs_struct、struct mm_struct、struct vm_area_struct,名称分别为"sighand_cache"、“signal_cache”、“files_cache”、“fs_cache”、“mm_struct”、"vm_area_struct"的slab高速缓存,存入变量sighand_cachep
、signal_cachep
、files_cachep
、fs_cachep
、mm_cachep
、vm_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_memory
。dentry是由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/fs
。init_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_ns
、current->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"下的节点(目录)mounts
、net
、sysvipc
、fs
、driver
、fs/nfsd
、tty
(包括里面的节点)、device-tree
(依赖CONFIG_PROC_DEVICETREE
)、bus
、sys
(初始化了proc_iops
、proc_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();
待补充…