之前写过 linux-3.0.1 ok6410a 的启动流程, 从 arch/arm/boot/compressed/head.S 到 mm_init 完成
回忆 linux-3.0.1 ok6410a 的启动流程
这里讲述了 内存镜像的生成过程以及压缩/无压缩镜像启动的不同打印
压缩内核 会打印 Uncompressing Linux… done, booting the kernel ,再打印 Booting Linux on physical CPU
非压缩内核直接打印 Booting Linux on physical CPU
这里讲述了 压缩内核的入口(arch/arm/boot/compressed/head.S 中的start )及出口(到非压缩内核)(mov pc, r4 @ call kernel) 和 非压缩内核的入口(arch/arm/kernel/head.S 中的 stext)及出口(start_kernel) (b start_kernel)
这里简述了 arm压缩内核的 启动流程
这里简述了 arm非压缩内核的启动流程
这里 简述了 arm64 和 risc-v 架构的架构相关启动流程
这里 简述了各种地址(物理地址 虚拟地址 链接地址 运行地址)概念
这里 讲述了 uImage的头部
这里 从目录角度讲述了 目录在启动过程中扮演的角色
这里 讲述了 从 start 到 start_kernel 的过程中的内存分布
这里 讲述了 过程中的一些与内存相关的符号
这里 讲述了 过程中的一些与内存相关的内核配置
简述 linux-5.11 OK6410A 启动流程
1 从u-boot 到 arch/arm/boot/compressed/head.S 中的start
- 打印信息
bootm 0x50008000
## Booting kernel from Legacy Image at 50008000 ...
Image Name: Linux-5.11.0-00006-g1829225a7fa
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2913264 Bytes = 2.8 MiB
Load Address: 50008000
Entry Point: 50008000
Verifying Checksum ... OK
Loading Kernel Image
Starting kernel ...
Booting Linux on physical CPU 0x0
- 流程
u-boot
1. 将 zImage 放置到 50008000
2. 设置 r0 为 0
3. 设置 r1 为 1626 // 这个是 smdk6410的board id
4. 设置 r2 为 atags的 地址 0x50000100 , 此前 设置 了 setup_start_tag setup_commandline_tag setup_memory_tags setup_end_tag , 这个有 152 字节
5. 并 将pc 置为 0x50008000
linux
0x50008000 为 arch/arm/boot/compressed/head.S 中的start // 需要去分析 arch/arm/boot/compressed/vmlinux.lds
2 从 arch/arm/boot/compressed/head.S 中的start 到 arch/arm/kernel/head.S 中的 stext
- 运行前的状态
内存 :
Image : 0x50008000 - 0x502D12C7 2.8MB
atags : 0x50000100 - 0x50000197 152字节
cpu寄存器 :
r0 : 0
r1 : 1626
r2 : 0x50000100
pc : 0x50008000
协处理器寄存器 :
mmu off
dcache off
icache off
没有关注CONFIG_ARM_VIRT_EXT,TODO
- 流程
简述 : 大概就是 利用 解压代码A 将 arch/arm/boot/compressed/piggy_data 解压 为 Image
过程中涉及到了 很多事情
1. 解压后的 Image 起始位置的确定
2. 解压后的 Image 是否与 当前kernel运行空间 重叠
如果重叠
要搬运当前kernel
要处理搬运后的符号链接问题(解压代码A的符号链接)
3. 解压核心代码 decompress_kernel
可直接 加载 运行 Image,从而忽略这个过程, 与加载 uImage并运行 产生的效果是一样的
效果 : arch/arm/kernel/head.S 中的 stext 运行时的状态
arch/arm/boot/compressed/head.S
184 .section ".start", "ax"
...
235 mov r7, r1 @ save architecture ID
236 mov r8, r2 @ save atags pointer
...
250 not_angel: // 解决 Booting from Angel 的问题
251 safe_svcmode_maskall r0 // 关闭IRQ与FIQ
252 msr spsr_cxsf, r9 @ Save the CPU boot mode in SPSR
...
288 add r4, r4, #TEXT_OFFSET // r4 中的值 0x50008000 , 为 解压后 Image的 起始位置
...
305 blcs cache_on // 开cache
...
307 restart: adr r0, LC1 // 如果 会 overwrite,则进行relocate之后,会跳到这里,然后继续往下再次判断是否需要 overwrite
...
313 get_inflated_image_size r9, r10, lr // 得到 Image 的大小
...
435 add r10, r10, #16384 // 计算当前zImage是否与解压后的Image有重叠.
436 cmp r4, r10 // 如果有重叠,则 重定位 zImage
437 bhs wont_overwrite
---------------------------------------------------------- relocate自身 的开始
...
481 sub r9, r6, r5 @ size to copy
...
491 #ifdef DEBUG
...
503 dbgkc r5, r6, r10, r9 // 打印了 C:0x500080C0-0x502D1240->0x5088F000-0x50B58180
...
506 #endif
...
513 badr r0, restart
514 add r0, r0, r6
515 mov pc, r0
---------------------------------------------------------- relocate自身 的结束,会跳转到 restart 标号,而不是往下走
517 wont_overwrite: // 重定位第一步(代码搬运)完成,或者是没有重定位,直接跳转到这里 , 本例是 重定位完成
...
535 orrs r1, r0, r5
536 beq not_relocated
...
551 * Relocate all entries in the GOT table. // 重定位第二步,解决符号地址问题
...
563 /* bump our bss pointers too */ // 重定位第三步,清空bss段
...
582 not_relocated: mov r0, #0
583 1: str r0, [r2], #4 @ clear bss
...
610 bl decompress_kernel // 解压zImage 为 Image
...
616 bl cache_clean_flush
617 bl cache_off
...
623 bne __enter_kernel @ boot kernel directly
1410 __enter_kernel:
...
1414 ARM( mov pc, r4 ) @ call kernel // 调用 Image的 arch/arm/kernel/head.S 中的 stext
- 打印相关
C:0x500080C0-0x502D1240->0x5088F000-0x50B58180
Uncompressing Linux... done, booting the kernel.
上述打印需要 内核配置支持
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_LL=y
CONFIG_DEBUG_UNCOMPRESS=y
- 地址相关
zImage 在解压缩前 进行了一次搬移 从 0x500080C0-0x502D1200 到 0x5088F000-0x50B58140
zImage 被 解压缩 为 Image,Image在哪里?
在 0x5088F000 前面,具体地址为
0x50008000 - 0x50008000 + 8938824(Image的大小) -1
即
0x50008000 - 0x5088E547
-rwxrwxr-x 1 suws suws 8938824 Apr 21 14:53 arch/arm/boot/Image
3 从 arch/arm/kernel/head.S 中的 stext 到 init/main.c 中的 start_kernel
- 运行前的状态
内存 :
Image : 0x50008000 - 0x5088E547 8.6M
atags : 0x50000100 - 0x50000197 152字节
cpu寄存器 :
r0 : 0
r1 : 1626
r2 : 0x50000100
pc : 0x50008000
协处理器寄存器 :
mmu off
dcache off
icache off
- 流程
arch/arm/kernel/head.S
77 ENTRY(stext)
...
89 safe_svcmode_maskall r9
...
92 bl __lookup_processor_type @ r5=procinfo r9=cpuid
...
106 adr_l r8, _text @ __pa(_text)
107 sub r8, r8, #TEXT_OFFSET @ PHYS_OFFSET
...
116 bl __vet_atags
...
123 bl __create_page_tables
...
144 ldr r13, =__mmap_switched @ address to jump to after
...
151 mov r8, r4 @ set TTBR1 to swapper_pg_dir // r8 中值 为 50004000 , r4 中的值 也为 50004000
...
153 ldr r12, [r10, #PROCINFO_INITFUNC]
154 add r12, r12, r10
155 ret r12 // 跳转到 arch/arm/mm/proc-v6.S 中的 __v6_setup ,并返回
156 1: b __enable_mmu // 跳转到 __enable_mmu
...
157 ENDPROC(stext)
arch/arm/kernel/head.S
420 __enable_mmu:
...
439 mcr p15, 0, r5, c3, c0, 0 @ load domain access register
440 mcr p15, 0, r4, c2, c0, 0 @ load page table pointer // 将 页表基址(50004000) 告知 mmu
...
442 b __turn_mmu_on // 跳转到 __turn_mmu_on
443 ENDPROC(__enable_mmu)
461 ENTRY(__turn_mmu_on)
...
468 mov r3, r13
469 ret r3 // 跳转到 __mmap_switched
470 __turn_mmu_on_end:
471 ENDPROC(__turn_mmu_on)
arch/arm/kernel/head-common.S
77 __mmap_switched:
...
106 bl __memset @ clear .bss
...
108 ldmia r4, {r0, r1, r2, r3}
109 str r9, [r0] @ Save processor ID
110 str r7, [r1] @ Save machine type
111 str r8, [r2] @ Save atags pointer
...
118 b start_kernel // 跳转到 start_kernel
- 重点函数解析 __create_page_tables
__create_page_tables
Clear the swapper page table // 16KB 空间 即 4K个页表(1个页表4Byte) 清0,1个页表map 1MB虚拟地址 , 正好 Map 4GB虚拟地址
addr value
50004000 0
50004004 0
50004008 0
5000400c 0
50004010 0
...
50007ffc 0
Create identity mapping to cater for __enable_mmu // __turn_mmu_on
addr value
50005400 50100c0e
Map our RAM from the start to the end of the kernel .bss section. // 9MB, 因为 我们的 Image 为 8.6M ,不足 9个MB
addr value
50007000 50000c0e
50007004 50100c0e
50007008 50200c0e
5000700c 50300c0e
50007010 50400c0e
50007014 50500c0e
50007018 50600c0e
5000701c 50700c0e
50007020 50800c0e
Then map boot params address in r2 if specified
addr value
50007fe0 50000c0e
50007fe4 50100c0e
// 一旦开启 mmu , 多了虚拟地址 ,对应的虚拟地址为
500x xxxx __enable_mmu
C00x xxxx kernel
ff8x xxxx atags
10 0000 是 1MB
页表的含义:50000c0e
Section base address : 500 00000
Section
B:1
C:1
XN:0
Domain:0000 // 表明 属于 domain0 , D0
IMP:0
AP:11
TEX:000
APX:0
S:0
nG:0
- 重点过程解析 虚拟地址和连接地址一致问题
__turn_mmu_on
在 非压缩内核启动流程中
重定位前, 访问 全局符号 时,因为全局符号是链接地址,所以要将链接地址转换为运行地址(运行的物理地址)
重定位技术: MMU
重定位后, 访问全局符号 时,因为全局符号时虚拟地址,MMU自动将虚拟地址转换为运行地址,直接给出(指令或数据)
而 MMU 开启前, 创建了 三组页表,
其中对 整个 Image 所在内存的映射,
做出来的 start_kernel 虚拟地址 和 start_kernel 的链接地址 是一样的
不是因为巧合,是必须这么做.
下面看一下 链接地址的生成过程A(决定了链接地址) 和 页表的创建过程B(决定了虚拟地址)
A:
arch/arm/kernel/vmlinux.lds
10 . = ((0xC0000000)) + 0x00008000;
11 .head.text : {
12 _text = .;
arch/arm/kernel/vmlinux.lds.S
50 . = PAGE_OFFSET + TEXT_OFFSET;
51 .head.text : {
52 _text = .;
arch/arm/include/asm/memory.h
24 #define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
include/generated/autoconf.
377 #define CONFIG_PAGE_OFFSET 0xC0000000
arch/arm/Makefile
141 textofs-y := 0x00008000
238 TEXT_OFFSET := $(textofs-y)
B:
arch/arm/kernel/head.S
232 /*
233 * Map our RAM from the start to the end of the kernel .bss section.
234 */
235 add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER) // r4 = 50004000 , r0 = 50007000
// C0008000 的虚拟地址和 50004000的页表基址决定了 必须在 50007000 写页表
236
237 ldr r6, =(_end - 1) // _end : c08c413c
238 orr r3, r8, r7 // 计算写入的值(页表描述符) ,值为 50000c0e
// 这个值决定了 该表为 section表
// 索引该页表时,物理地址为 0x50000000 - 0x50100000
239 add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER) // 计算 末位地址
// 此时已经准备好
240 1: str r3, [r0], #1 << PMD_ORDER // 写入
241 add r3, r3, #1 << SECTION_SHIFT // 重新计算写入的值(页表描述符)
242 cmp r0, r6 // 比较写入的地址
243 bls 1b // 地址不同,执行循环
arch/arm/include/asm/pgtable-2level.h
95 #define SECTION_SHIFT 20
arch/arm/kernel/head.S
44 #define PG_DIR_SIZE 0x4000
45 #define PMD_ORDER 2
- 重点函数解析 __mmap_switched
__mmap_switched
arch/arm/kernel/head-common.S
68 * The following fragment of code is executed with the MMU on in MMU mode,
69 * and uses absolute addresses; this is not position independent.
...
71 * r0 = cp#15 control register (exc_ret for M-class) // 用来 mcr p15, 0, r0, c1, c0, 0 @ write control reg (arch/arm/kernel/head.S L476)
72 * r1 = machine ID
73 * r2 = atags/dtb pointer
74 * r9 = processor ID
...
76 __INIT
77 __mmap_switched:
78
79 mov r7, r1 // 将 machine id 放入 r7 , 值为1626
80 mov r8, r2 // 将 atags pointer 放入 r8 , 值为 0xC0000100 (虚拟地址)
81 mov r10, r0 // 将 ??? 放入 r10
83 adr r4, __mmap_switched_data // 将 __mmap_switched_data 的地址放入 r4
84 mov fp, #0 // 将 0 放入 r11
101 ARM( ldmia r4!, {r0, r1, sp} ) // 将内存中的数据加载到 寄存器中
// 将 __bss_start 放入 r0 , 该符号为连接符号,在 System.map 查到地址
// 将 __bss_stop 放入 r1
// 将 init_thread_union + THREAD_START_SP 放入 sp
...
104 sub r2, r1, r0 // r2 = r1 - r0, 计算 .bss段 的长度 , 0x00035BF4
105 mov r1, #0 // 将 0 放入 r1 中
106 bl __memset @ clear .bss // 此时r0为__bss_start,r1为0,r2为长度,调用 __memset 来 clear bss
107
108 ldmia r4, {r0, r1, r2, r3} // 将内存中的数据加载到 寄存器中
// 将 processor_id 放入 r0
// 将 __machine_arch_type 放入 r0
// 将 __atags_pointer 放入 r2
// 将 cr_alignment 放入 r3
109 str r9, [r0] @ Save processor ID // 将r9中的值 放入 以r0中的值为地址的 地址中 ,赋值 unsigned int processor_id
110 str r7, [r1] @ Save machine type // 将r7中的值 放入 以r1中的值为地址的 地址中 ,赋值 unsigned int __machine_arch_type
111 str r8, [r2] @ Save atags pointer // 将r8中的值 放入 以r2中的值为地址的 地址中 ,赋值 unsigned int __atags_pointer __initdata
112 cmp r3, #0 // 比较 r3中的值 与 0 , r3 中存放的值 的值 就是 cr_alignment 变量初始化的值 为 0
113 strne r10, [r3] @ Save control register values // 不相等,则 将r10中的值 放入 以r3中的值为地址的 地址中 ,赋值 cr_alignment
...
117 mov lr, #0 // 将 0 放入 lr 中
118 b start_kernel // 跳转到 start_kernel , 不修改 lr
...
122 .type __mmap_switched_data, %object
123 __mmap_switched_data:
...
133 .long __bss_start @ r0
134 .long __bss_stop @ r1
135 .long init_thread_union + THREAD_START_SP @ sp
136
137 .long processor_id @ r0
138 .long __machine_arch_type @ r1
139 .long __atags_pointer @ r2
...
141 .long cr_alignment @ r3
...
146 .size __mmap_switched_data, . - __mmap_switched_data
- b start_kernel 运行前的状态
b start_kernel 运行前的状态
cpu寄存器
r0 :
r1 :
r2 :
r3 :
r4 :
r5 :
r6 :
r7 :
r8 :
r9 :
r10 :
r11 :
r12 :
r13 : init_thread_union + THREAD_START_SP // init_thread_union + (((1 << 12) << 1) - 8) // 0xc0800000 + (十进制)8184 = 0xC0801FF8
r14 : 0
pc : start_kernel 的虚拟地址 0xc07009f0
spsr : 0x600001d3
cpsr : 0x200001d3
Supervisor Mode
ARM Instruction Set
IRQ disabled
FIQ disabled
Disables imprecise data aborts
little endian
运行非cpsr相关指令时会修改的位
dsp指令
GE[3:0] bit[19:16]
在ARMv6中,SIMD指令使用位[19:16] 作为结果的单个字节或半字的大于或等于(GE)标志。您可以使用这些标志来控制以后的SEL指令
Q bit[27]
在ARMv5及更高版本的E变体中,CPSR的位[27]的Q标志称为Q标志,用于指示在某些面向DSP的指令中是否发生了溢出和/或饱和。
arm指令
N bit[31] 负
Z bit[30] 零
C bit[29] 进位
V bit[28] 溢出
协处理器cp15寄存器
cp15 Register 1: Control register : 0x00c5387d
mmu : enabled
Alignment fault checking: disabled
Dcache : enabled
write buffer : enabled
The use of the S and R bits is deprecated in VMSAv6
Icache : enabled
Normal replacement strategy
subpages : disabled
cp15 Register 2: Translation table base : 0x50004000
页表基址寄存器,存放的是物理地址
cp15 Register 3: Domain access control : 0x00000051
D0 : 01 : Client Accesses are checked against the access permission bits in the TLB entry
D1 : 00 : No access Any access generates a domain fault
D2 : 01 : Client Accesses are checked against the access permission bits in the TLB entry
D3 : 01 : Client Accesses are checked against the access permission bits in the TLB entry
D4 : 00 : No access Any access generates a domain fault
...
cp15 Register 7: cache management functions
instr_sync : mcr p15, 0, r0, c7, c5, 4
// Flush prefetch buffer (PrefetchFlush)
// Instruction barrier
内存
物理内存
页表 : 物理地址: 0x50005400 - 0x50005403 __turn_mmu_on
页表 : 物理地址: 0x50007000 - 0x50007023 Image
页表 : 物理地址: 0x50007fe0 - 0x50007fe7 atags
Image : 物理地址: 0x50008000 - 0x5088E547 8.6MB
atags : 物理地址: 0x50000100 - 0x50000197 152字节
虚拟内存
针对页表 : 用 下面(Image) 的映射关系
针对Image : 虚拟地址:0xC000 0000 - 0xC08F FFFF 9MB <-> 物理地址 0x5000 0000 - 0x508F FFFF 9MB
针对atags : 虚拟地址:0xFF80 0000 - 0xFF9F FFFF 2MB <-> 物理地址 0x5000 0000 - 0x501F FFFF 2MB
页表内容:
三组页表都是段表(bit[1:0]为10)
bit[31:20] 为 Section base address , 这个值在不同页表中不同
// 例如 500 表示 Section base address : 500 00000
其他bit都是相同的, bit[19:0] 为 0x00c0e
bit[1:0]:10 // 表示该First-level descriptor是一个 Section descriptor
Memory region attributes
TEX:000
B:1
C:1
S:0
表示 Outer and inner write back, no write allocate
// 对于 s3c6410 来说 , 无 Outer , inner 为 16KB的 dcache
// 也就是说 dcache 支持 write back, 支持 no write allocate
// write allocate/write through/write back 是 缓存策略的范畴, 全局搜索 缓存策略
Memory type : Normal
// 属于内存属性的范畴 , 还没理清楚
Page shareable : S设置则shareable,否则not shareable //该例为 not shareable
// 属于内存属性的范畴 , 还没理清楚
Memory access control
Domains permissions
Domain:0000
// 表明 属于 domain0 , D0
// 参与 Domains permissions
// D0 为01,表示 Accesses are checked against the access permission bits in the TLB entry
// 即 Domain permissions 是 OK的,但还要检查 access permission bits
Access permissions
APX:0
AP:11
// 参与 Access permissions
// 表示 Full access
XN:0
// 表示包含可执行代码
IMP:0
//微架构实现定义 , 需查询 ARM1176 的 TRM 手册
nG:0
//页表翻译条目 在TLB中应标记为全局
Image 虚拟内存分段
Image 虚拟地址 : 0xC0008000 - 0xC088E547 8.6MB
// include/asm-generic/sections.h
_stext _etext
.code : c0100000 - c0600000
__start_rodata __end_rodata
.rodata : c0600000 - c06b6000
_sdata _edata
.data c0800000 - c088e548
__bss_start __bss_stop
.bss : c088e548 - c08c413c
init_thread_union + THREAD_START_SP
.stack : - C0801FF8
.heap : null
Image 全局变量
arch/arm/kernel/setup.c 中的 unsigned int processor_id : 0x410fb766
arch/arm/boot/compressed/misc.c 中的 unsigned int __machine_arch_type : 0x0000065a // 1626
arch/arm/kernel/setup.c 中的 unsigned int __atags_pointer : 0x50000100
// SUDEBUG : arch/arm/kernel/setup.c,setup_arch,line = 1090,atags_vaddr:0xff800100
arch/arm/kernel/entry-armv.S 中的 cr_alignment : 0x00c5387d // cp15 Register 1: Control register 的值
其他
- 过程 2 从 arch/arm/boot/compressed/head.S 中的start 到 arch/arm/kernel/head.S 中的 stext 的等价过程
参考 https://blog.csdn.net/u011011827/article/details/115766762 中的 镜像Image的执行
bash ./scripts/mkuboot.sh -A arm -O linux -C none -T kernel -a 0x50008000 -e 0x50008000 -n 'Linux-5.11.0-00004-g2b88cbadf5c-dirty' -d arch/arm/boot/Image arch/arm/boot/uImage2
=> tftp 0x50008000 uImage2
=> bootm 0x50008000
- 缓存策略
一、CPU读数据
1. Read through
即直接从内存中读取数据;
2. Read allocate
先把数据读取到Cache中,再从Cache中读数据。
二、CPU写数据
1. 若hit命中,有两种处理方式:
Write-through:
write through:
write is done synchronously both to the cache and to the backing store。
(直写模式)在数据更新时,把数据同时写入Cache和后端存储。此模式的优点是操作简单;缺点是因为数据修改需要同时写入存储,数据写入速度较慢。
Write-back (also called write-behind) write back
initially, writing is done only to the cache. The write to the backing store is postponed until the cache blocks containing the data are about to be modified/replaced by new content。
(回写模式)在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储(即先把数据写到Cache中,再通过flush方式写入到内存中)。
此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。
2. 若miss,有两种处理方式:
Write allocate (also called fetch on write)
data at the missed-write location is loaded to cache, followed by a write-hit operation. In this approach, write misses are similar to read misses.
先把要写的数据载入到Cache中,写Cache,然后再通过flush方式写入到内存中; 写缺失操作与读缺失操作类似。
No-write allocate (also called write-no-allocate or write around)
data at the missed-write location is not loaded to cache, and is written directly to the backing store. In this approach, only the reads are being cached。
并不将写入位置读入缓存,直接把要写的数据写入到内存中。这种方式下,只有读操作会被缓存。
- swapper_pg_dir
arch/arm/kernel/head.S L28
swapper_pg_dir is the virtual address of the initial page table.
.globl swapper_pg_dir // 页表基址 对应的 虚拟地址
.equ swapper_pg_dir, (((0xC0000000)) + 0x00008000) - 0x4000