1 linux内核启动过程分析.docx
- 文档编号:10380733
- 上传时间:2023-05-25
- 格式:DOCX
- 页数:24
- 大小:23.61KB
1 linux内核启动过程分析.docx
《1 linux内核启动过程分析.docx》由会员分享,可在线阅读,更多相关《1 linux内核启动过程分析.docx(24页珍藏版)》请在冰点文库上搜索。
1linux内核启动过程分析
linux内核启动过程分析
嵌入式linux系统从软件角度来看可分为四部分:
bootloader,linux内核,文件系统和应用程序。
在这里我选取的内核版本是linux2.6.28,硬件平台选择smdk6410。
Bootloader是系统启动或复位后首先被执行的代码,它的主要作用是初始化处理器,初始化ram,初始化相应的外设(uart,usb等等),下载内核映像(或文件系统)到ram相应的位置,然后跳转到内核下载地址c0008000,将控制权交给linux内核。
Linux内核下载到ram中的映像一般是zImage。
这是压缩版本的内核,首先要进行解压操作。
调用decompress_kernel()(位于arch/arm/boot/compressed/misc.c)进行解压缩操作,然后再次跳到c0008000,进行真正的内核初始化操作。
我们重点放在讲解内核映像解压之后linux内核的启动过程。
内核初始化启动过程如下:
1)__lookup_processor_type(),查找处理器类型。
2)__lookup_machine_type(),查找机器类型。
3)__vet_atags()。
4)__create_page_tables(),创建页表。
5)__enable_mmu(),使能MMU。
6)__mmap_switched(),拷贝数据,清BBS。
7)start_kernel(),进入真正的内核初始化函数。
8)smp_setup_processor_id();
9)unwind_init();
10)lockdep_init();
11)debug_objects_early_init();
12)cgroup_init_early();
13)local_irq_disable();
14)early_boot_irqs_off();
15)early_init_irq_lock_class();
16)lock_kernel();
17)tick_init();
18)boot_cpu_init();
19)page_address_init();
20)setup_arch(&command_line);
21)mm_init_owner(&init_mm,&init_task);
22)setup_command_line(command_line);
23)unwind_setup();
24)setup_per_cpu_areas();
25)setup_nr_cpu_ids();
26)smp_prepare_boot_cpu();
27)sched_init();
28)preempt_disable();
29)build_all_zonelists();
30)page_alloc_init();
31)parse_early_param();
32)sort_main_extable();
33)trap_init();
34)rcu_init();
35)init_IRQ();
36)pidhash_init();
37)init_timers();
38)hrtimers_init();
39)softirq_init();
40)timekeeping_init();
41)time_init();
42)sched_clock_init();
43)profile_init();
44)early_boot_irqs_on();
45)local_irq_enable();
46)console_init();
47)lockdep_info();
48)locking_selftest();
49)vmalloc_init();
50)vfs_caches_init_early();
51)cpuset_init_early();
52)page_cgroup_init();
53)mem_init();
54)enable_debug_pagealloc();
55)cpu_hotplug_init();
56) kmem_cache_init();
57) debug_objects_mem_init();
58) idr_init_cache();
59) setup_per_cpu_pageset();
60) numa_policy_init();
61) if(late_time_init)
62) late_time_init();
63) calibrate_delay();
64) pidmap_init();
65) pgtable_cache_init();
66) prio_tree_init();
67) anon_vma_init();
68) thread_info_cache_init();
69) fork_init(num_physpages);
70) proc_caches_init();
71) buffer_init();
72) key_init();
73) security_init();
74) vfs_caches_init(num_physpages);
75) radix_tree_init();
76) signals_init();
77) page_writeback_init();
78) proc_root_init();
79) cgroup_init();
80) cpuset_init();
81) taskstats_init_early();
82) delayacct_init();
83) check_bugs();
84) acpi_early_init();
85) ftrace_init();
86) rest_init();
1.1__lookup_processor_type()
话说内核映像解压后,又跳到c0008000这个地址。
这个地址指向内核代码的什么地方,我们肯定很想知道。
在arch/arm/kernel/vmlinux.lds.S中,可以发现这样的代码:
SECTIONS
{
#ifdefCONFIG_XIP_KERNEL
.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
.=PAGE_OFFSET+TEXT_OFFSET;
#endif
.text.head:
{
_stext=.;
_sinittext=.;
*(.text.head)
}
…
}
一般内核都不配置成XIP方式的,所以这段脚本等同于:
SECTIONS
{
.=PAGE_OFFSET+TEXT_OFFSET;
.text.head:
{
_stext=.;
_sinittext=.;
*(.text.head)
}
…
}
这段脚本告诉我们SECTIONS的起始地址是.text.head的起始地址_stext,且
_stext=PAGE_OFFSET+TEXT_OFFSET;
PAGE_OFSET在.config文件中设置:
PAGE_OFFSET=0xC0000000;
TEXT_OFFSET在架构目录下的Makefile文件中设置:
textofs-y :
=0x00008000
TEXT_OFFSET:
=$(textofs-y)
结合arch/arm/kernel/head.S,会发现如下代码:
.section".text.head","ax"
ENTRY(stext)
msr cpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE@ensuresvcmode
@andirqsdisabled
mrcp15,0,r9,c0,c0 @getprocessorid
bl __lookup_processor_type @r5=procinfor9=cupid
…
ENDPROC(stext)
第一句代码的意思是表示下面的内容都属于.text.head段的,”ax”表示这段内容是可分配且可执行的(allocableandexecutable)。
所以c0008000处放的代码就是stext的入口地址。
接下来的
msr cpsr_c,#PSR_F_BIT|PSR_I_BIT|SVC_MODE
就是把fiq_mask(快速中断屏蔽位)和irq_mask(快速中断屏蔽位)都置位,同时把处理器模式设置为svc模式。
这就是要告诉闲杂人等不要来打扰,这里要办重要的事。
cpsr_c代表当前状态寄存器;鉴于当前状态寄存器的重要性,arm特意开发了msr指令,专门用来设置当前状态寄存器。
mrcp15,0,r9,c0,c0
是将协处理器cp15c0的值赋值到r9中。
接下来的
bl __lookup_processor_type
是长跳转到__lookup_processor_type,查看processorID是否被内核支持。
__lookup_processor_type:
adr r3,3f
ldmda r3,{r5-r7}
sub r3,r3,r7 @getoffsetbetweenvirt&phys
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
1:
ldmia r5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
teq r3,r4
beq 2f
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)
cmpr5,r6
blo 1b
movr5,#0 @unknownprocessor
2:
movpc,lr
ENDPROC(__lookup_processor_type)
adr是条伪指令,作用就是把标号为3位置的地址赋值给r3寄存器。
3后面加f是表示这是个长距离(far)的标号。
有同学可能就要问了,ldr也能起到这个作用,为什么不用ldr?
首先ldrr3,3f取的是标号3这个地址的内容,而不是地址本身;其次,可以用ldrr3,=3f来取地址本身,但这是一个绝对地址;而adr取得的是相对地址。
如果要保证程序在任何内存都能运行,就必须保证代码是地址无关的,也就是PIC(positionindependentcode)。
显然adr伪指令很对PIC的胃口,它的取相对地址方式符合PIC的设定。
.long __proc_info_begin
.long __proc_info_end
3:
.long .
.long __arch_info_begin
.long __arch_info_end
我们接着往下看。
ldmda r3,{r5-r7}
sub r3,r3,r7 @getoffsetbetweenvirt&phys
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
1:
ldmia r5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
teq r3,r4
beq 2f
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)
cmpr5,r6
blo 1b
movr5,#0 @unknownprocessor
2:
movpc,lr
ldmadar3,(r5-r7)是把标签3所指的地址的内容(也就是标签3的虚拟地址)赋值给r7,把比标签3所指的地址小4的地址的内容(也就是__proc_info_end)赋值给r6,把比标签3所指的地址小8的地址的内容(__proc_info_begin)赋值给r5。
这里的虚拟地址是线性逻辑地址,它和物理地址之间有着一一映射关系。
因为__proc_info_begin和__proc_info_end都是虚拟地址,此时我们MMU还没有打开,就必须要使用物理地址。
这就需要我们先把它们转换为物理地址。
接下来的三句代码就是完成这样的工作。
__proc_info_begin和__proc_info_end是在vlinux.lds.S中定义的。
__proc_info_begin=.;
*(.proc.info.init)
__proc_info_end=.;
这说明在__proc_info_begin和__proc_info_end之间的是所有的.proc.info.init段。
我们可以在arch/arm/mm/proc_*.S中找到相应的.proc.info.init段。
Smdk6410属于armv6,我们可以在proc_v6.S找到armv6处理器的id和id掩码。
之后的代码就是把处理器的id和id掩码赋值到r3,r4中;把r9与处理器掩码做与操作,然后与处理器id(r3)比较,看是否相等;如不相等,就取下一个处理器id进行比较;如果到最后都没有处理器id相符,就将r5赋值为0:
1:
ldmia r5,{r3,r4} @value,mask
and r4,r4,r9 @maskwantedbits
teq r3,r4
beq 2f
add r5,r5,#PROC_INFO_SZ @sizeof(proc_info_list)
cmpr5,r6
blo 1b
movr5,#0 @unknownprocessor
2:
movpc,lr
最后一句是跳出__lookup_processor_type函数。
跳出之后会对处理器id是否有效做一个判断;如果不是有效的处理器,就进行相应的错误处理;如果是有效的处理器,就进行机器类型查找:
movs r10,r5 @invalidprocessor(r5=0)?
beq __error_p @yes,error'p'
bl __lookup_machine_type @r5=machinfo
1.2__lookup_machine_type()
机器类型的查找代码如下:
__lookup_machine_type:
adr r3,3b
ldmia r3,{r4,r5,r6}
sub r3,r3,r4 @getoffsetbetweenvirt&phys
add r5,r5,r3 @convertvirtaddressesto
add r6,r6,r3 @physicaladdressspace
1:
ldr r3,[r5,#MACHINFO_TYPE] @getmachinetype
teq r3,r1 @matchesloadernumber?
beq 2f @found
add r5,r5,#SIZEOF_MACHINE_DESC @nextmachine_desc
cmpr5,r6
blo 1b
movr5,#0 @unknownmachine
2:
movpc,lr
ENDPROC(__lookup_machine_type)
我们可以看到,这和处理器类型查找函数很类似,在这里只进行简单的解说。
.long __proc_info_begin
.long __proc_info_end
3:
.long .
.long __arch_info_begin
.long __arch_info_end
__arch_info_begin和__arch_info_end在arch/arm/kernel/vlinux.lds.S中定义:
__arch_info_begin=.;
*(.arch.info.init)
__arch_info_end=.;
.arch.info.init段我们可以找到在arch/arm/include/asm/mach/arch.h中有引用:
#defineMACHINE_START(_type,_name) \
staticconststructmachine_desc__mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init")))={ \
.nr =MACH_TYPE_##_type, \
.name =_name,
#defineMACHINE_END \
};
我们可以在arch/arm/mach-*.c文件中找到一系列关于MACHINE_START所定义的结构。
1.3__vet_atags()
函数代码如下:
__vet_atags:
tst r2,#0x3 @aligned?
bne 1f
ldr r5,[r2,#0] @isfirsttagATAG_CORE?
subsr5,r5,#ATAG_CORE_SIZE
bne 1f
ldr r5,[r2,#4]
ldr r6,=ATAG_CORE
cmpr5,r6
bne 1f
movpc,lr @atagpointerisok
1:
movr2,#0
movpc,lr
ENDPROC(__vet_atags)
atag是bootloader传递给linux内核的参数列表。
这个参数列表是以tag的列表形式来表示的。
这个列表起始位置的tag是ATAG_CORE,用来表示这是一个有效的tag列表。
如果起始tag不是ATAG_CORE,就认为bootloader没有传递tag参数给内核。
以下是tag值的定义和描述,以及tag结构的定义。
Tagname
Value
Size
Description
ATAG_NONE
0x00000000
2
Emptytagusedtoendlist
ATAG_CORE
0x54410001
5(2ifempty)
Firsttagusedtostartlist
ATAG_MEM
0x54410002
4
Describesaphysicalareaofmemory
ATAG_VIDEOTEXT
0x54410003
5
DescribesaVGAtextd
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux内核启动过程分析 linux 内核 启动 过程 分析