目录
[6,ROM 磁盘 (.romdisk)](#6,ROM 磁盘 (.romdisk))
7,数据段的巧妙设计 (.data_shadow 和 .data)
[8,ARM 特定段](#8,ARM 特定段)
[9,BSS 段 (.bss)](#9,BSS 段 (.bss))
[11,DMA 和堆区域](#11,DMA 和堆区域)
一,整体架构分析
1,内存区域划分
主内存 (ram)
DMA 区域 (dma)
堆内存 (heap)
2,处理器模式栈分配
Undefined 模式栈: 256KB (0x40000)
Abort 模式栈: 256KB (0x40000)
IRQ 模式栈: 256KB (0x40000)
FIQ 模式栈: 256KB (0x40000)
SVC 模式栈: 1MB (0x100000)
二,关键部分详细解析
1.输出格式与入口
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
生成 ELF 32位 格式,默认小端字节序,支持大端作为备用
入口点为 _start(通常是启动代码)
2,代码段 (.text)
.text :
{
PROVIDE(__image_start = .);# 镜像起始地址
PROVIDE(__text_start = .);
.obj/arch/arm32/mach-feifei/start.o (.text)# 启动代码必须放在最前面
*(.text*)# 所有代码段
*(.init.text)
*(.exit.text)
*(.glue*)
*(.note.gnu.build-id)
PROVIDE(__text_end = .);
} > ram
3,初始化调用段 (.initcall)
.initcall ALIGN(8) :
{
PROVIDE(__initcall_start = .);
KEEP(*(.initcall_0.text))
KEEP(*(.initcall_1.text))
KEEP(*(.initcall_2.text))
KEEP(*(.initcall_3.text))
KEEP(*(.initcall_4.text))
KEEP(*(.initcall_5.text))
KEEP(*(.initcall_6.text))
KEEP(*(.initcall_7.text))
KEEP(*(.initcall_8.text))
KEEP(*(.initcall_9.text))
PROVIDE(__initcall_end = .);
} > ram
级别 0: 最底层硬件初始化(时钟、内存控制器)
级别 1: 核心子系统
...
级别 9: 应用程序级初始化
KEEP() 确保即使未被引用也不会被优化掉
4,退出调用段 (.exitcall)
.exitcall ALIGN(8) :
{
PROVIDE(__exitcall_start = .);
KEEP(*(.exitcall_9.text))
KEEP(*(.exitcall_8.text))
KEEP(*(.exitcall_7.text))
KEEP(*(.exitcall_6.text))
KEEP(*(.exitcall_5.text))
KEEP(*(.exitcall_4.text))
KEEP(*(.exitcall_3.text))
KEEP(*(.exitcall_2.text))
KEEP(*(.exitcall_1.text))
KEEP(*(.exitcall_0.text))
PROVIDE(__exitcall_end = .);
} > ram
与初始化相反的顺序(9→0),符合资源释放的依赖关系(后初始化的先释放)
5,内核符号表 (.ksymtab)
.ksymtab ALIGN(8) :
{
PROVIDE(__ksymtab_start = .);
KEEP(*(.ksymtab.text))
PROVIDE(__ksymtab_end = .);
} > ram
用途:内核符号导出,可能用于:
模块动态加载
系统调用表
调试信息
6,ROM 磁盘 (.romdisk)
.romdisk ALIGN(8) :
{
PROVIDE(__romdisk_start = .);
KEEP(*(.romdisk))
PROVIDE(__romdisk_end = .);
} > ram
.rodata ALIGN(8) :
{
PROVIDE(__rodata_start = .);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
PROVIDE(__rodata_end = .);
} > ram
将文件系统映像(如 initramfs)链接到内核镜像中,实现单一镜像启动
7,数据段的巧妙设计 (.data_shadow 和 .data)
.data_shadow ALIGN(8) :
{
PROVIDE(__data_shadow_start = .);
PROVIDE(__data_shadow_end = (. + SIZEOF(.data)));
PROVIDE(__image_end = .);
} > ram
.data : AT(ADDR(.data_shadow))
{
PROVIDE(__data_start = .);
*(.data*)
. = ALIGN(8);
PROVIDE(__data_end = .);
} > ram
工作原理:
ROM 中:.data_shadow 区域(保存初始化数据的副本)
RAM 中:.data 区域(运行时实际位置)
启动时:需要将 .data_shadow 的内容复制到 .data
内存布局:
.text
.rodata
.data_shadow ← 初始化数据在镜像中的存储位置
.ARM.*
.bss
.stack
.data ← 运行时数据在RAM中的实际位置(物理上在.stack之后)
AT(ADDR(.data_shadow)) 表示:
.data 段的 加载地址(LMA)= .data_shadow 的地址
.data 段的 运行地址(VMA)= 实际RAM位置
启动代码需要做:
// 将初始化数据从ROM复制到RAM
memcpy(__data_start, __data_shadow_start,
__data_end - __data_start);
8,ARM 特定段
.ARM.exidx ALIGN(8) :
{
PROVIDE (__exidx_start = .);
*(.ARM.exidx*)# 异常处理索引表(栈展开)
PROVIDE (__exidx_end = .);
} > ram
.ARM.extab ALIGN(8) :
{
PROVIDE (__extab_start = .);
*(.ARM.extab*)# 异常处理展开表
PROVIDE (__extab_end = .);
} > ram
用途:C++ 异常处理、栈回溯、调试
9,BSS 段 (.bss)
.bss ALIGN(8) (NOLOAD) :
{
PROVIDE(__bss_start = .);
*(.bss*)# 未初始化全局变量
*(.sbss*)# 未初始化的公共变量
*(COMMON)
. = ALIGN(8);
PROVIDE(__bss_end = .);
} > ram
NOLOAD:不占用镜像空间,但运行时需要清零
启动代码需要:
memset(__bss_start, 0, __bss_end - __bss_start)
10,多模式栈分配 (.stack)
.stack ALIGN(8) (NOLOAD) :
{
PROVIDE(__stack_start = .);
PROVIDE(__stack_und_start = .);
. += STACK_UND_SIZE;
PROVIDE(__stack_und_end = .);
. = ALIGN(8);
PROVIDE(__stack_abt_start = .);
. += STACK_ABT_SIZE;
PROVIDE(__stack_abt_end = .);
. = ALIGN(8);
PROVIDE(__stack_irq_start = .);
. += STACK_IRQ_SIZE;
PROVIDE(__stack_irq_end = .);
. = ALIGN(8);
PROVIDE(__stack_fiq_start = .);
. += STACK_FIQ_SIZE;
PROVIDE(__stack_fiq_end = .);
. = ALIGN(8);
PROVIDE(__stack_srv_start = .);
. += STACK_SRV_SIZE;
PROVIDE(__stack_srv_end = .);
. = ALIGN(8);
PROVIDE(__stack_end = .);
} > ram
11,DMA 和堆区域
.dma ALIGN(8) (NOLOAD) :
{
PROVIDE(__dma_start = ORIGIN(dma));
PROVIDE(__dma_end = ORIGIN(dma) + LENGTH(dma));
} > dma
.heap ALIGN(8) (NOLOAD) :
{
PROVIDE(__heap_start = ORIGIN(heap));
PROVIDE(__heap_end = ORIGIN(heap) + LENGTH(heap));
} > heap
物理地址固定:直接使用内存区域的原点(ORIGIN)
NOLOAD:不加载到镜像中
用途:
DMA:外设直接内存访问缓冲区
Heap:动态内存分配(malloc/free)
12,调试信息段
将所有调试段放到地址 0,表示不加载到目标系统,仅用于调试
.stab 0 : { *(.stab) }# 符号表
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.debug_abbrev 0 : { *(.debug_abbrev) }# debug_*表示DWARF 调试信息
.debug_info 0 : { *(.debug_info) }
.debug_line 0 : { *(.debug_line) }
.debug_pubnames 0 : { *(.debug_pubnames) }
.debug_aranges 0 : { *(.debug_aranges) }
三,启动流程分析
阶段1:硬件启动
CPU 从复位向量开始执行
跳转到 _start(在 start.o 中)
阶段2:启动代码 (start.o)
assembly
_start:
// 1. 设置异常向量表
// 2. 初始化关键硬件(时钟、内存控制器)
// 3. 设置各模式栈指针
ldr sp, =__stack_srv_end // 设置SVC模式栈
// 4. 清零BSS段
ldr r0, =__bss_start
ldr r1, =__bss_end
mov r2, #0
bl memset
// 5. 复制.data段
ldr r0, =__data_start // 目标:RAM中的.data
ldr r1, =__data_shadow_start // 源:ROM中的.data_shadow
ldr r2, =__data_end
sub r2, r2, r0 // 计算大小
bl memcpy
// 6. 调用初始化函数
ldr r0, =__initcall_start
ldr r1, =__initcall_end
bl call_initcalls // 按顺序调用initcall_0到9
// 7. 跳转到main
bl main
阶段3:初始化调用链
// 系统初始化顺序
initcall_0: // 最早
-
内存控制器初始化
-
缓存初始化
-
基本串口输出
initcall_1:
-
时钟系统
-
电源管理
initcall_2:
-
中断控制器
-
定时器
initcall_3:
-
设备树解析
-
平台设备
... // 中间各级
initcall_9: // 最晚
-
文件系统
-
应用程序启动
四,reset_handler处理
reset:
/* Enter svc mode cleanly and mask interrupts */
mrs r0, cpsr
eor r0, r0, #0x1a
tst r0, #0x1f
bic r0, r0, #0x1f
orr r0, r0, #0xd3
bne 1f
orr r0, r0, #0x100
adr lr, 2f
msr spsr_cxsf, r0
.word 0xe12ef30e /* msr elr_hyp, lr */
.word 0xe160006e /* eret */
1: msr cpsr_c, r0
2: nop
/* Set vector base address register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
mrc p15, 0, r0, c1, c0, 0
bic r0, #(1<<13)
mcr p15, 0, r0, c1, c0, 0
/* Enable neon/vfp unit */
mrc p15, 0, r0, c1, c0, 2
orr r0, r0, #(0xf << 20)
mcr p15, 0, r0, c1, c0, 2
isb
mov r0, #0x40000000
vmsr fpexc, r0
/* Initialize stacks */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r1, r0, #0x1b
msr cpsr_cxsf, r1
ldr sp, _stack_und_end
bic r0, r0, #0x1f
orr r1, r0, #0x17
msr cpsr_cxsf, r1
ldr sp, _stack_abt_end
bic r0, r0, #0x1f
orr r1, r0, #0x12
msr cpsr_cxsf, r1
ldr sp, _stack_irq_end
bic r0, r0, #0x1f
orr r1, r0, #0x11
msr cpsr_cxsf, r1
ldr sp, _stack_fiq_end
bic r0, r0, #0x1f
orr r1, r0, #0x13
msr cpsr_cxsf, r1
ldr sp, _stack_srv_end
/* Copyself to link address */
adr r0, _start
ldr r1, =_start
cmp r0, r1
beq 1f
ldr r0, _image_start
adr r1, _start
ldr r2, _image_end
sub r2, r2, r0
bl memcpy
1: nop
/* Copy data section */
ldr r0, _data_start
ldr r3, _image_start
ldr r1, _data_shadow_start
sub r1, r1, r3
adr r3, _start
add r1, r1 ,r3
ldr r2, _data_shadow_start
ldr r3, _data_shadow_end
sub r2, r3, r2
bl memcpy
/* Clear bss section */
ldr r0, _bss_start
ldr r2, _bss_end
sub r2, r2, r0
mov r1, #0
bl memset
/* Call _main */
ldr r1, =_main
mov pc, r1
_main:
mov r0, #1;
mov r1, #0;
bl main
b _main
五,undefined_handler处理
undefined_handler:
ldr sp, _stack_und_end
sub sp, sp, #72
stmia sp, {r0 - r12}
add r8, sp, #60
mrs r1, cpsr
mrs r2, spsr
orr r2, r2, #0xc0
msr cpsr_c, r2
mov r0, r0
stmdb r8, {sp, lr}
msr cpsr_c, r1
sub lr, lr, #4
str lr, [r8, #0]
mrs r6, spsr
str r6, [r8, #4]
str r0, [r8, #8]
mov r0, sp
bl gdbserver_handle_exception
ldmia sp, {r0 - r12}
mov r0, r0
ldr lr, [sp, #60]
add sp, sp, #72
movs pc, lr
六,irq_handler处理
中断发生时保存被中断任务的上下文
.macro irq_save_regs
sub sp, sp, #72@在 IRQ 模式栈上分配 72 字节,此时 CPU 已自动切换到 IRQ 模式
stmia sp, {r0 - r12}@保存通用寄存器 :r0-r12 到栈中位置 0-48
add r8, sp, #60@r8 = sp + 60,指向将要保存 lr_irq 的位置
stmdb r8, {sp, lr}^
str lr, [r8, #0]
mrs r6, spsr@保存 SPSR
str r6, [r8, #4]@保存SPSR的 位置:[r8+4] = [sp+64]
str r0, [r8, #8]@实际上保存的是当前 r0 的值到
mov r0, sp将栈指针保存到 r0,作为参数传递给 C 语言中断处理函数
.endm
sub sp, sp, #72
-
分配栈帧:在 IRQ 模式栈上分配 72 字节
-
当前模式:此时 CPU 已切换到 IRQ 模式(由硬件自动完成)
stmia sp, {r0 - r12}
-
保存通用寄存器:r0-r12 到栈中位置 0-48
-
关键点 :保存的是中断发生时的寄存器值
add r8, sp, #60
-
计算指针 :
r8指向栈帧中特殊寄存器区域(偏移60) -
r8=sp + 60,指向将要保存lr_irq的位置
stmdb r8, {sp, lr}^
-
最复杂的指令! 需要分解理解:
-
stmdb:存储多个,先减地址后存储(Decrement Before) -
{sp, lr}^:^表示存储被中断模式的 SP 和 LR -
实际操作:
-
r8 = r8 - 8(因为要存2个寄存器,每个4字节) -
存储
sp_usr到[r8](即[sp+52]) -
存储
lr_usr到[r8+4](即[sp+56])
-
-
str lr, [r8, #0]
-
保存 IRQ 模式的 LR :此时的
lr是lr_irq -
ARM 中断机制 :发生 IRQ 时,硬件自动将
pc+4保存到lr_irq -
保存位置:
[r8]=[sp+60]
mrs r6, spsr和str r6, [r8, #4]
-
保存 SPSR:被中断模式的 CPSR
-
SPSR 包含:处理器模式、中断使能位、条件标志等
-
保存位置:
[r8+4]=[sp+64]
str r0, [r8, #8]
- 这里保存
r0寄存器在add r8, sp, #60之后的当前值
mov r0, sp
-
设置参数 :将栈指针保存到
r0 -
目的:作为参数传递给 C 语言中断处理函数
-
C 函数可以通过
r0访问保存的寄存器上下文
中断返回时恢复上下文
.macro irq_restore_regs
ldmia sp, {r0 - lr}^
mov r0, r0@延迟操作,确保流水线完成清空
ldr lr, [sp, #60]@恢复中断返回地址 从栈中加载 lr_irq
add sp, sp, #72
subs pc, lr, #4
.endm
ldmia sp, {r0 - lr}^
-
恢复通用寄存器和用户模式寄存器:
-
^表示恢复到用户模式的寄存器 -
从栈中恢复 r0-r12、sp_usr、lr_usr
-
-
实际恢复的寄存器:
-
r0-r12:通用寄存器
-
sp:用户模式栈指针(实际上被忽略?)
-
lr:用户模式链接寄存器
-
mov r0, r0
-
空操作:看似无用,实则有重要目的!
-
作用 :确保上一条 ldmia 完成
-
ARM 架构特性:
ldm指令有延迟效应,下一条指令可能还在使用旧寄存器值 -
mov r0, r0创建一个依赖,确保流水线清空
ldr lr, [sp, #60]
-
恢复中断返回地址 :从栈中加载
lr_irq -
注意 :此时的
lr是 IRQ 模式的链接寄存器
add sp, sp, #72
- 释放栈帧:恢复 IRQ 模式栈指针
subs pc, lr, #4
-
中断返回:关键指令!
-
subs:带标志设置的减法 -
pc:目标寄存器是程序计数器 -
lr:包含中断返回地址(pc+4) -
#4:减去4,返回到被中断的指令 -
自动操作 :
subs pc, ...还会自动将 SPSR 复制到 CPSR
irq:
ldr sp, _stack_irq_end#栈是从高地址向低地址方向的
irq_save_regs#中断发生时保存被中断任务的上下文
bl interrupt_handle_exception#c语言irq中断处理函数
irq_restore_regs#中断返回时恢复上下文