目录
- 内核构建过程和ARM处理器
- 内核构建过程
- ARM处理器
- 内核的启动
- 内核的初始化
- 内核的执行
- 整体指向---setup_arch
引言:迫于对于Linux
新版本内存管理的渴望,我开启了Linux
新版本的游荡,Linux 0.11
版本明显不够味,Linux 0.99
以下均是如此,内存管理和文件目录架构均没有太大的变化,而市面上唯一找到的便属这本《ARM Linux内核源码剖析》了,在它的基础上,我在本篇对该版本的arm
架构代码进行词词解析,对!就是词词解析!我觉得把他解析完我写一本书都不为过了!来!启程!
参考资料:
- ARM指令集详解–汇编
内核构建过程和ARM处理器
内核构建过程
即可启动二进制文件zImage
的生成过程,通过内核初始化、内核配置、内核构建和内核安装过程。
# 内核初始化
make distclean
make mrproper
# 内核配置
kconfig
make menuconfig, gconfig, xconfig
# 内核构建 vmlinux, head.S, misc.S => zImage
# vmlinux->gzip->piggy.gz->piggy.o 内核文件 head.S 内核初始化程序 misc.S 解压缩
kbuild
make all, zImage, modules
# 内核安装
make install, modules_install
为何要压缩成zImage
文件?由于嵌入式系统具有的资源十分有限,为了提高这种环境下内核内存的负载率和执行效率,使用压缩后的内核二进制文件zImage
ARM处理器
ARM
的意思是Advanced RISC Machine
。ARM处理器以Berkeley RISC架构为基础。RISC ( Reduced Instruction Set Computer,精简指令系统计算机)比CISC ( Complex Instruction Set Computer,复杂指令系统计算机)指令结构更简单,可在一小时内快速处理指令。ARM采用RISC方式降低指令复杂度,利用管道提高指令处理速度,由此提升性能。ARM中适用的RISC特征如下。
- 指令:ARM使用的指令相对较少。这些指令将提供一个循环周期内能够执行的简单指令通过具有一定长度的指令可实现管道。
- 管道:指令在管道中是并列执行的。通过管道执行时,一边解码(decode)当前指令,一边获取( fetch )下一指令。
- 负载存储((load-store)结构:ARM处理器会执行寄存器储存的指令。将内存中的数据读取到寄存器时使用load命令,将寄存器中的数据输入内存时使用store命令。
处理器架构和核心
ARM
主要架构如下
- ARMv4架构:使用32位地址区域,可运行32位的ISA ( Instruction Set Architecture,指令集架构)。ARMv4T架构还具有16位Thumb指令包。
- ARMv5TE架构:向ARMISA添加了已改善的Thumb架构和Enhanced DSP指令包。它将改善ARM/Thumb彼此间的运行,包含了程序兼容,提高了性能。
- ARMv6架构:改善了对内存系统、异常处理、多进程环境的支持等内容,并包含了支持运行SIMD ( Single Instruction Multiple Data,单指令多数据)的媒体指令。
- ARMv7-A架构:支持Linux、Linux第三方( Montavista、QNX、风河系统)、Symbian ,Windows CE等大部分操作系统。具有ARM、Thumb、Thumb-2、Jazelle、DSP指令包,并包含Advanced SIMD扩展指令——NEON的软件多媒体处理功能。
命名规则
符号 | 意义 |
x | 处理器族 |
y | MMU/MPU |
z | 缓存 |
T | Thumb 16位指令解码器 |
D | JTAG调试器 |
M | 扩展高速乘子 |
l | 嵌入式ICE微极化池( microcell) |
E | DSP扩展指令 |
J | Jazelle Java加速功能 |
F | 浮点数处理装置 |
s | 综合版本 |
在ARM
处理器架构中,ARM7
采用冯·诺依曼体系结构,ARM9
采用修正后的哈佛体系结构
处理器内部结构
ARM
处理器的运行模式如下
-
User
模式:执行普通用户应用程序时的处理器运行模式 -
System
模式:使用与User模式相同的寄存器,但可完全读写CPSR ( Current Program StatusRegister,当前程序状态寄存器)的特殊模式。 -
FIQ(Fast Interrupt Request,快速中断请求)
模式:为处理快速中断而执行的处理器运行模式。 -
IRQ (Interrupt Request,中断请求)
模式:为处理通用中断而执行的处理器运行模式。 -
SVC (Supervisor
)模式:操作系统内核运行的通用处理器运行模式,发生Reset或软件中断(sWI)时执行。 -
Abort
模式:内存访问失败时执行的处理器运行模式。 -
Undefined
模式:处理器要执行未定义的指令时执行的处理器运行模式。
几个特殊寄存器
-
r13
:作为栈指针(sp)使用,存储当前处理器模式的栈顶端地址值。 -
r14
:作为链接寄存器( lr )使用,储存调用子程序时的返回地址。 -
r15
:处理器读取指令后,通过程序计数器( pc)储存下一指令地址。 -
CPSR
和SPSR
:程序状态保存寄存器,其中CPSR显示程序执行时的状态,CPSR显示程序执行时的状态。
处理器异常
处理器发生异常或中断时,在程序计数器( pc)中输入特定存储器地址以处理异常/中断,位置的指令处理器将中断当前执行的程序,并读取向量表中相应
在处理器中可将向量表设置为高位( high )地址使用,微软操作系统即具备该特征。设置为Low向量表时,分为0x00000000;设置为High向量表时,分为0xFFFFO000。
异常分类
- Reset:接通电源时首次使用的位置。
- Undefined Instruction:执行未定义指令时使用。
- Software Interrupt:执行SWI指令时调用,并作为系统核心使用。
- Prefetch Abort:发生于无法读取内存指令时。
- Data Abort:发生于无法读写数据内存时。
- IRQ:为中断处理器当前执行流程,由外部硬件使用。
- FIQ:和IRQ类似,但由需要快速响应时间的硬件使用。
硬件拓展功能
缓存
内存管理
协处理器:CP15
,控制缓存和MMU
内核的启动
head.S
文件(因为我的逻辑是按照ARM
版本解析的,且有先后次序,所以复制的顺序和结构可能会和源码不一致,望见谅)
进入启动加载后结束首个启动-start标签
start:
b 1f @跳转指令,跳转到标号1的位置执行
...
1: mov r7, r1 @ r7保存架构ID
mov r8, r2 @ r8保存atags信息
...
#ifndef __ARM_ARCH_2__ @ ARM 2
...
mrs r2, cpsr @ 读状态寄存器指令 cpsr为状态寄存器
tst r2, #3 @ 比较指令 是否为user模式
bne not_angel @ 跳转指令
...
not_angel: @ 禁用中断
mrs r2, cpsr @ 读状态寄存器指令 cpsr为状态寄存器
orr r2, r2, #0xc0 @ 将0xc0写入到r2寄存器,即1100 0000在7位和8位设置关闭FIQ和IRQ
msr cpsr_c, r2 @ 反映到cpsr并关闭中断
...
#else @ARM 3
teqp pc, #0x0c000003 @ 关闭中断
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp} @ 将r0指向的地址的多个字赋值给后面的寄存器
subs r0, r0, r1 @ calculate the delta offset
@ if delta is zero, we are
beq not_relocated @ 跳转指令
@ were linked at.
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word zreladdr @ r4
.word _start @ r5
.word _got_start @ r6
.word _got_end @ ip
.word user_stack+4096 @ sp
BSS系统域初始化-not_relocated标签
解压zImage
这一压缩内核的准备工作有:初始化BSS
区域,激活缓存以及设置动态内存区域。
not_relocated:
mov r0, #0
1: str r0, [r2], #4 @ clear bss str为将寄存器r0的数据保存到内存[r2]+4
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3 @ 因为bss位于__bss_start到__end之间,前面对bss区域进行初始化为0,此处检验是否整个bss段全部0
blo 1b
bl cache_on @ 激活缓存,将用于解压的动态内存空间的起始地址user_stack+4KB和最终地址user_stack+4KB+64KB存入寄存器r1和r2,至此完成解压内核的所有准备
mov r1, sp @ malloc space above stack 分配栈空间位置
add r2, sp, #0x10000 @ 64k max 分配栈空间大小
@ 检查是否覆盖
cmp r4, r2
bhs wont_overwrite
sub r3, sp, r5 @ > compressed kernel size
add r0, r4, r3, lsl #2 @ allow for 4x expansion
cmp r0, r5
bls wont_overwrite
cache_on:
mov r3, #8 @ 将常数8存入寄存器r3
b call_cache_fn @ 跳转指令
call_cache_fn:
adr r12, proc_types @ 特殊结构,用r12指向pro_types
#ifdef CONFIG_CPU_CP15 @ 协处理器coprocessor1 CP15是否存在
@ p15指令操作的协处理器名,一般是p0~p15
@ 0为为特定操作码
@ r6为目的寄存器
@ c0为存放第一个操作数的协处理器
@ c0为存放第二个操作数的协处理器
mrc p15, 0, r6, c0, c0 @ 获得处理器 ID ,mrc指令为协处理器到ARM寄存器到的数据传送指令
#else
ldr r6, =CONFIG_PROCESSOR_ID @否则从宏定义获取处理器ID
#endif
1: ldr r1, [r12, #0] @ 处理器ID
ldr r2, [r12, #4] @ 得到mask码
eor r1, r1, r6 @ (real ^ match) 异或运算
tst r1, r2 @ & mask 进行比较
addeq pc, r12, r3 @ call cache function 如果cpsr的z位为1,则将pc=r12+r3 r3前面赋值为8,用作偏移量
add r12, r12, #4*5 @ 如果不一致, 在r12加上20,一直比较直到找到该处理器型号
b 1b @ 跳转指令,进行循环调用,直到找到符合该版本的缓存指令
proc_types: @ 该结构管理打开缓存(cache_on),清除缓存(cache flush),关闭缓存(cache off)的功能,这些功能根据`ARM`处理器的版本而不同,常数8用作偏移量,指向proc_types列表各项的“打开缓存”子程序的起始地址
.word 0x41560600 @ ARM6/610
.word 0xffffffe0
b __arm6_mmu_cache_off @ works, but slow
b __arm6_mmu_cache_off
mov pc, lr
@ b __arm6_mmu_cache_on @ untested
@ b __arm6_mmu_cache_off
@ b __armv3_mmu_cache_flush
.word 0x00000000 @ old ARM ID
.word 0x0000f000
mov pc, lr
mov pc, lr
mov pc, lr
.word 0x41007000 @ ARM7/710
.word 0xfff8fe00
b __arm7_mmu_cache_off
b __arm7_mmu_cache_off
mov pc, lr
__armv4_mmu_cache_on:
mov r12, lr @ 将返回地址存入r12
bl __setup_mmu @跳转指令到__setup_mmu
mov r0, #0 @r0寄存器清0
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer 将写缓冲的内容更新到内存
mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs 清除指令缓存 数据缓存和TLB
mrc p15, 0, r0, c1, c0, 0 @ read control reg 读控制寄存器
orr r0, r0, #0x5000 @ I-cache enable, RR cache replacement 设置控制寄存器的指令缓存激活位,round robin缓存交替策略激活位
orr r0, r0, #0x0030
bl __common_mmu_cache_on @ 跳转指令
mov r0, #0 @ r0寄存器清0
mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs 清除指令缓存 数据缓存和TLB
mov pc, r12 #并返回
__setup_mmu: @用于初始化解压内核所需的页目录项
sub r3, r4, #16384 @ Page directory size r4-16384
bic r3, r3, #0xff @ Align the pointer 位清除指令 r3& (0xff的反码)
bic r3, r3, #0x3f00
mov r0, r3 @r3=r0 @ r3=0x50004000,这是zImage加载的物理地址 16KB,也是页目录的起始地址
mov r9, r0, lsr #18
mov r9, r9, lsl #18 @ start of RAM
add r10, r9, #0x10000000 @ a reasonable RAM size
mov r1, #0x12
orr r1, r1, #3 << 10
add r2, r3, #16384
1: cmp r1, r9 @ if virt > start of RAM
orrhs r1, r1, #0x0c @ set cacheable, bufferable
cmp r1, r10 @ if virt > end of RAM
bichs r1, r1, #0x0c @ clear cacheable, bufferable
str r1, [r0], #4 @ 1:1 mapping
add r1, r1, #1048576
teq r0, r2
bne 1b
mov r1, #0x1e
orr r1, r1, #3 << 10
mov r2, pc, lsr #20
orr r1, r1, r2, lsl #20
add r0, r3, r2, lsl #2
str r1, [r0], #4
add r1, r1, #1048576
str r1, [r0]
mov pc, lr
ENDPROC(__setup_mmu)
__common_mmu_cache_on: @指令缓存激活及缓存策略适用
#ifndef DEBUG
orr r0, r0, #0x000d @ Write buffer, mmu
#endif
mov r1, #-1 @r1=-1
mcr p15, 0, r3, c2, c0, 0 @ load page table pointer 将r3内保存的页目录地址值村润cp15的TTBR
mcr p15, 0, r1, c3, c0, 0 @ load domain access control
b 1f
.align 5 @ cache line aligned
1: mcr p15, 0, r0, c1, c0, 0 @ load control register 循环设置缓存策略、指令缓存激活设置
mrc p15, 0, r0, c1, c0, 0 @ and read it back to
sub pc, lr, r0, lsr #32 @ properly flush pipeline
proc_type
结构
-
CPU ID match
-
CPU ID mask
-
cache on subroutine
-
cache off subroutine
-
cache flush subroutine
cp15
对于zImage
加载的物理地址各结构都不同,可以在arch/arm/$(MACH)/Makefile.boot
中找到,如
# mach-aaec2000
zreladdr-y := 0xf0008000
# mach-at91
ifeq ($(CONFIG_ARCH_AT91CAP9),y)
zreladdr-y := 0x70008000
params_phys-y := 0x70000100
initrd_phys-y := 0x70410000
else
zreladdr-y := 0x20008000
params_phys-y := 0x20000100
initrd_phys-y := 0x20410000
endif
__setup_mmu
中将页目录中的4096项进行初始化,将访问权限设为可读/写,而且,对其中256项设置为cacheable
和bufferable
(页目录以1MB
管理内存单元)
我们之前通过not_relocated
和cache_on
做好了解压之前的准备工作:初始化BSS
区域、设置动态内存区域、打开缓存等。现在正式对压缩后的内核映像zlmage
进行解压,并为内核初始化进行设置。
解压内核并避免覆盖-wont_overwrite
@ 检查是否覆盖
cmp r4, r2
bhs wont_overwrite
sub r3, sp, r5 @ > compressed kernel size
add r0, r4, r3, lsl #2 @ allow for 4x expansion
cmp r0, r5
bls wont_overwrite
wont_overwrite: @解压内核并避免覆盖
mov r0, r4 @ r0为内核起始地址
mov r3, r7 @ r3=r7 设备ID
bl decompress_kernel @ 调用解压内核的子程序
b call_kernel @ 没有问题的话直接跳转到call_kernel指令
call_kernel:
bl cache_clean_flush @ 刷新缓存 根据处理器ID找到相关处理器型号的缓存框架即可
bl cache_off @ 缓存禁用 同上
mov r0, #0 @ must be zero r0=0
mov r1, r7 @ restore architecture number r1=r7
mov r2, r8 @ restore atags pointer r2=r8
mov pc, r4 @ call kernel pc=r4
ulg
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,
int arch_id)
{
output_data = (uch *)output_start; /* Points to kernel start */
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
makecrc();
putstr("Uncompressing Linux...");
gunzip();
putstr(" done, booting the kernel.\n");
return output_ptr;
}
cache_off: mov r3, #12 @ cache_off function
b call_cache_fn
cache_clean_flush: @ 缓存清理
mov r3, #16
b call_cache_fn
call_cache_fn:
adr r12, proc_types @特殊结构
#ifdef CONFIG_CPU_CP15 @协处理器coprocessor1 CP15是否存在
@ p15指令操作的协处理器名,一般是p0~p15
@ 0为为特定操作码
@ r6为目的寄存器
@ c0为存放第一个操作数的协处理器
@ c0为存放第二个操作数的协处理器
mrc p15, 0, r6, c0, c0 @ 获得处理器 ID ,mrc指令为协处理器到ARM寄存器到的数据传送指令
#else
ldr r6, =CONFIG_PROCESSOR_ID @否则从宏定义获取处理器ID
#endif
1: ldr r1, [r12, #0] @ 处理器ID
ldr r2, [r12, #4] @ 得到mask码
eor r1, r1, r6 @ (real ^ match) 异或运算
tst r1, r2 @ & mask 进行比较
addeq pc, r12, r3 @ call cache function 如果cpsr的z位为1,则将pc=r12+r3 r3前面赋值为8,用作偏移量
add r12, r12, #4*5 @ 如果不一致, 在r12加上20
b 1b @ 跳转指令,进行循环调用,直到找到符合该版本的缓存指令
内核的初始化
通过引导加载项加载内核后,首先执行的部分就是stext
,执行该标签时要求如下状态,此时在stext
中,首先转换为SVC
模式,并禁用IRQ
。然后调用多个检查程序,查找处理器和机器信息,并检查atag
信息,朱家设置页表后启动MMU
MMU = off
D_Cache = off
r0 = 0
r1 = machine number
r2 = stags pointer
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode 写状态寄存器指令 转换成SVC模式,禁用IRQ
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id 得到处理器ID
bl __lookup_processor_type @ r5=procinfo r9=cpuid 寻找CPU信息
movs r10, r5 @ invalid processor (r5=0)? 检查CPU信息是否找到的程序
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo 寻找机器信息
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a' 检查机器信息是否找到的程序
bl __vet_atags @ 检查atag信息
bl __create_page_tables
@ 对虚拟内存内存创建
ldr r13, __switch_data @ r13=__switch_data
@ mmu has been enabled
adr lr, __enable_mmu @ lr=__enable_mmu
add pc, r10, #PROCINFO_INITFUNC @ 进程初始化函数 r10存储了proc_info_list结构体的起始地址,加上PROCINFO_INITFUNC可调用__v6_setup
@ proc_info_list结构体保存着处理器信息,一般通过proc_info_begin和proc_info_end得到
ENDPROC(stext)
为了让内核正常运行,必须把握内核要执行的处理器信息和机器信息。
__lookup_processor_type: @ 此时要查找的处理器的CPU ID保存至r9中
adr r3, 3f @ r3=3f
ldmda r3, {r5 - r7} @ ldmda r3, {r5 - r7}意思是r3指示的内存数据依次加载到寄存器r7,r6,r5中去 r7=. r6=__proc_info_end,r5=__proc_info_begin
sub r3, r3, r7 @ 得到__proc_info_end和__proc_info_begin的物理地址
add r5, r5, r3
add r6, r6, r3
1: ldmia r5, {r3, r4} @ __proc_info_begin到__proc_info_end保存着所有的处理器信息,循环依次输入,用proc_info_list结构体保存
and r4, r4, r9 @ 并比较cpu_val和CPU ID的值
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ
cmp r5, r6
blo 1b
mov r5, #0
2: mov pc, lr
ENDPROC(__lookup_processor_type) @ 注册到符号表
ENTRY(lookup_processor_type) @ 查找处理器类型的汇编代码的C API版本
stmfd sp!, {r4 - r7, r9, lr}
mov r9, r0
bl __lookup_processor_type
mov r0, r5
ldmfd sp!, {r4 - r7, r9, pc}
ENDPROC(lookup_processor_type)
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
在MMU禁用状态下将虚拟地址转换为物理地址。此时禁用了MMU
。无法通过虚拟地址访问内存,但是,保存处理器信息的区域地址__proc_info_begin
和__proc_info_end
是 编译内核指定的,且都是虚拟地址,因此,只有将这些虚拟地址变更为物理地址,才能访问具有处理器信息的proc_info_list
结构体
sub r3, r3, r7
add r5, r5, r3
add r6, r6, r3
r3 <- label 3 物理地址
r5 <- __proc_info_begin 虚拟地址
r6 <- __proc_info_end 虚拟地址
r7 <- .(location counter) 虚拟地址
对虚拟内存进行创建:对KERNEL_RAM_PADDR
相距0x4000的位置(KERNEL_RAM_PADDR-0x4000
)到KERNEL_RAM_PADDR
的所有页表项执行循环,并初始化为0,对相当于内核区域的项设置节区基址和cacheable
和bufferable
值。
KERNEL_RAM_PADDR=0x50008000
__create_page_tables:
pgtbl r4 @ page table address 页表起始地址
mov r0, r4 @ r0=0x50004000
mov r3, #0 @ r3=0
add r6, r0, #0x4000 @ r6=0x50004000+0x4000=0x50008000
1: str r3, [r0], #4 @ 0x50004000=0
str r3, [r0], #4 @ 0x50004004=0
str r3, [r0], #4 @ 0x50004008=0
str r3, [r0], #4 @ 0x5000400f=0
teq r0, r6 @ 全部初始化为0
bne 1b
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ r10保存着proc_info_list的地址,令r7=_cpu_mm_mmu_flag
mov r6, pc, lsr #20 @ start of kernel section
orr r3, r7, r6, lsl #20 @ flags + kernel base
str r3, [r4, r6, lsl #2] @ identity mapping
@ KERNEL_START = 0xc0008000 KERNEL_END = _end
add r0, r4, #(KERNEL_START & 0xff000000) >> 18 @r0=r4+0x3000
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! @[r0 + 0]
ldr r6, =(KERNEL_END - 1)
add r0, r0, #4
add r6, r4, r6, lsr #18
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
mov pc, lr
ENDPROC(__create_page_tables)
__v6_setup设置核心
运行完__create_page_tables
设置后,运行__v6_setup
程序设置当前处理器。
根据不同的ARM
架构,处理器初始化函数执行过程也不一样,v6
时调用__v6_setup
,v7
时调用__v7_setup
,调用后即执行初始化任务。
打开MMU并使用虚拟地址-__enable_mmu/__trun_mmu_on
运行完__v6_setup
之后,依次运行__enable_mmu
和__trun_mmu_on
为了控制MMU
的运行,使用协处理器15的各个寄存器,控制寄存器c1
寄存器将履行MMU
系统控制中默认寄存器的作用
__enable_mmu:
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
ENDPROC(__enable_mmu)
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
ENDPROC(__turn_mmu_on)
跳转到kernel_start-__mmap_switched
从此处开始,MMU
处于激活状态。从kernel_start
函数开始,代码由C
语言编写而成,因此需要__switch_data
标签中的data
、bss
、stack
值,并调用start_kernel
后要使用到的信息设置到__switch_data
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, r7, sp} @ 此处若执行start_kernel时,则运行静态声明的init线程,在sp寄存器中设置该线程的栈指针
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel @ 开始运行
ENDPROC(__mmap_switched)
内核的执行
主要是start_kernel
函数
asmlinkage void __init start_kernel(void)
{
smp_setup_processor_id();
lockdep_init();
debug_objects_early_init();
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_off();
early_init_irq_lock_class();
lock_kernel(); // 大内核锁
tick_init(); // 注册针对时钟事件的处理器
boot_cpu_init(); // 在CPU位图中注册当前运行CPU
page_address_init(); // 初始化高地址HIGHMEM管理
setup_arch(&command_line); // 设置架构
mm_init_owner(&init_mm, &init_task);
setup_command_line(command_line);
setup_per_cpu_areas();
setup_nr_cpu_ids();
smp_prepare_boot_cpu();
sched_init();
preempt_disable();
build_all_zonelists();
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
sort_main_extable();
trap_init();
rcu_init();
early_irq_init();
init_IRQ();
pidhash_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
sched_clock_init();
profile_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early\n");
early_boot_irqs_on();
local_irq_enable();
console_init();
if (panic_later)
panic(panic_later, panic_param);
lockdep_info();
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
"disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
vmalloc_init();
vfs_caches_init_early();
cpuset_init_early();
page_cgroup_init();
mem_init();
enable_debug_pagealloc();
cpu_hotplug_init();
kmem_cache_init();
kmemtrace_init();
debug_objects_mem_init();
idr_init_cache();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
calibrate_delay();
pidmap_init();
pgtable_cache_init();
prio_tree_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(num_physpages);
proc_caches_init();
buffer_init();
key_init();
security_init();
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
acpi_early_init();
ftrace_init();
rest_init();
}
一个个来看,此处我主要集中点在内存管理上。
smp_setup_processor_id();
lockdep_init(); // 用于调试
debug_objects_early_init(); // 用于调试
void __init __weak smp_setup_processor_id(void)
{
// 不执行
}
// 栈溢出感应,在ARM中未实现
boot_init_stack_canary();
// 初始化提供进程集成方法的cgroup
cgroup_init_early();
// 禁用IRQ
local_irq_disable();
// 协助调试
early_boot_irqs_off();
early_init_irq_lock_class();
// 初始化大内核锁
lock_kernel();
boot_cpu_init(); // 在CPU位图中注册当前运行CPU
page_address_init(); // 初始化高地址HIGHMEM管理
在包含热插拔信息的位图上添加执行init_task的CPU-boot_cpu_init()
内核中有位图,用来维护系统内CPU的状态信息,其中有cpu_possible_map
、cpu_online_map
、cpu_present_map
,位与CPU 1:1映射。
- cpu_possible_map:对系统中可执行当前热插拔的CPU的位图。该位图针对启动时系统支持的CPU数量,一旦设置了位,则不可添加或删除
- cpu_online_map:将联机(使用中)的所有CPU的位设置位1的位图。若已完成从内核调度或设备接收中断的准备,则设置该位。包括中断在内的所有OS服务移动到其他CPU时,该位被删除。
- cpu_present_map:显示系统内CPU的位图,但并非所有CPU均处于联机状态。
static void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
/* Mark the boot cpu "present", "online" etc for SMP and UP case */
set_cpu_online(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
}
管理高端内存-page_address_init
内核通过page_address_pool
访问HIGHMEM
,通过page_address_maps[]
进行管理。高端内存区域是不能直接地址化的内存页,因此,使用kmap()
映射高端内存,用散列表page_address_htable
另行管理HIGHMEM
中分配的内存。
整体指向—setup_arch
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
unwind_init();// 发生异常时,unwind信息判断将控制权移到何处。
setup_processor(); // 设置正在指向内核的处理器信息
mdesc = setup_machine(machine_arch_type); // 查找机器信息结构体并返回
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); // 分析并处理命令行参数
paging_init(mdesc); // 执行分页相关准备工作
request_standard_resources(&meminfo, mdesc); // 将不依赖平台并共同管理的源信息构建成树状结构
#ifdef CONFIG_SMP
smp_init_cpus();// 在系统内置CPU位图cpu_possible_map中对各核心做标记
#endif
cpu_init(); // 按IRQ、ABORT、SVC、UND模式指定要使用的栈空间
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
// 为了调用中断及异常代码,将各处理器代码及helper代码复制到异常向量表基址,并设置CPU域
early_trap_init();
}
unwind_init
:在C
语言中,发生异常时,用unwind
信息判断应当将控制权移到何处,它还能在重复调用函数而发生异常的情况下对栈进行回溯,从而获得调用自身的各函数信息,这种unwind
信息由异常处理列表保管
static inline int __init unwind_init(void)
{
return 0;
}
准备内存分页—page_init()
该函数的作用是设置页表、激活启动时的内存分配器并最终生成零页。该函数中设置的页表由内核使用,无法在用户空间使用
void __init paging_init(struct machine_desc *mdesc)
{
void *zero_page;
build_mem_type_table(); // 构建mem_type列表
sanity_check_meminfo(); // 对保存内存信息的meminfo结构体的值进行有效性检查
prepare_page_table(); // 准备页表,将对应于内核映像下方及内核看见的页目录项的pmd短均清空为0
bootmem_init(); // 激活启动使用的内存分配器,直到激活slab分配器
devicemaps_init(mdesc); // 对向量表进行映射,还执行使用设备时必须的操作
kmap_init(); // 对高端内存进行支持
top_pmd = pmd_off_k(0xffff0000);
/*
* allocate the zero page. Note that this always succeeds and
* returns a zeroed result.
*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE); // 生成初始化为0的零页
empty_zero_page = virt_to_page(zero_page); // 从生成的零页的虚拟地址获得page结构体
flush_dcache_page(empty_zero_page); // 清除零页,确保是缓存数据一致性
}
build_mem_type_table
:设置内存类型表
内存类型:根据内存使用目的的不同,内存类型对是否使用缓存、是否使用写缓冲、是否共享、域等信息的定义了不同设置,通过mem_type
全局结构体数组进行管理
prepare_page_table
:
- 将对应于内核映像下方的页目录各项的pmd ( 与pgd相同)段全部清空为0;
- 将对应于内核空间的页目录各项的pmd段全部清空为0。
static inline void prepare_page_table(void)
{
unsigned long addr;
/*
* Clear out all the mappings below the kernel image.
*/
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
#ifdef CONFIG_XIP_KERNEL
/* The XIP kernel is mapped in the module area -- skip over it */
addr = ((unsigned long)_etext + PGDIR_SIZE - 1) & PGDIR_MASK;
#endif
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Clear out all the kernel space mappings, except for the first
* memory bank, up to the end of the vmalloc region.
*/
for (addr = __phys_to_virt(bank_phys_end(&meminfo.bank[0]));
addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}