在前面的章节介绍了uboot
和
Linux
内核的一些相关内容。在来看
Linux
内核的大致启动流程,
Linux
内核的启动流程要比
uboot
复杂的多,涉及到的内容也更多,因此在本章节大致简单的了解一下
Linux
内核的启动流程。有兴趣的用户可以参考其他书籍或资料进行深入了解。
嵌入式linux
内核的启动全过程主要分为三个阶段。
第一阶段为内核自解压过程,第二阶段主要工作是设置ARM
处理器工作模式、使能
MMU
、设置一级页表等,而第三阶段则主要为
C
代码,包括内核初始化的全部工作。下面分别进行简单介绍。
30.1 Linux内核启动(一):Linux内核自解压过程
Linux内核有两种映像格式:一种是非压缩内核,叫Image,另一种是它的压缩版本,叫zImage。zImage是Image经过压缩形成的,
所以它的大小比Image小。但为了能使用zImage,必须在它的开头加上解压缩的代码,将zImage解压缩之后才能执行,因此它的执行速度比Image要慢
一些
。
内核压缩和解压缩代码都在目录kernel/arch/arm/boot/compressed
,编译完成后将产生
head.o
、
misc.o
、
piggy.gzip.o
、
vmlinux
、
decompress.o
这几个文件,
head.o
是内核的头部文件,负责初始设置;
misc.o
将主要负责内核的解压工作,它在
head.o
之后;
piggy.gzip.o
是一个中间文件,其实是一个压缩的内核
(kernel/vmlinux)
,只不过没有和初始化文件及解压文件链接而已;
vmlinux
是没有
(zImage
是压缩过的内核
)
压缩过的内核,就是由
piggy.gzip.o
、
head.o
、
misc.o
组成的,而
decompress.o
是为支持更多的压缩格式而新引入的。
在uboot
完成系统引导将
Linux
内核加载到内存之后,
调用do_bootm_linux()
,这个函数将跳转到
kernel
的起始位置。如果
kernel
没有被压缩,就可以启动了。如果
kernel
被压缩过,则要进行解压,在压缩过的
kernel
头部有解压程序。压缩过的
kernel
入口第一个文件源码位置在arch/arm/boot/compressed/head.S
。它将调用函数
decompress_kernel()
,这个函数在文件
arch/arm/boot/compressed/misc.c
中,
decompress_kernel()
又调用
arch_decomp_setup()
进行设置,然后调用
gunzip()
将内核放于指定的位置。
函数decompress_kernel
实现的功能:解压缩代码位于
kernel/lib/inflate.c
,
inflate.c
是从
gzip
源程序中分离出来的,包含了一些对全局数据的直接引用,在使用时需要直接嵌入到代码中。
gzip
压缩文件时总是在前
32K
字节的范围内寻找重复的字符串进行编码,
在解压时需要一个至少为
32K
字节的解压缓冲区,它定义为
window[WSIZE]
。
inflate.c
使用
get_byte()
读取输入文件,它被定义成宏来提高效率。输入缓冲区指针必须定义为
inptr
,
inflate.c
中对之有减量操作。
inflate.c
调用
flush_window()
来输出
window
缓冲区中的解压出的字节串,每次输出长度用
outcnt
变量表示。在
flush_window()
中,还必须对输出字节串计算
CRC
并且刷新
crc
变量。在调用
gunzip()
开始解压之前,调用
makecrc()
初始化
CRC
计算表。最后
gunzip()
返回
0
表示解压成功。在内核启动时一般会看到这样的输出:
UncompressingLinux...done, boo
ting the kernel.
当然有的内核没有这样的输出,是没有这一条打印语句。
30.2 Linux内核启动(二):ARM处理器相关设置
当Linux
内核自解压完成后,开始执行内核代码。内核的入口函数由链接脚本
vmlinux.lds
决定,需要编译内核源码,才会生成脚本文件。
首先分析Linux
内核的链接脚本文件arch/arm/kernel/vmlinux.lds
,通过链接脚本可以找到Linux内核的第一行程序是从哪里开始执行的。vmlinux.lds文件部分代码如下:
492 OUTPUT_ARCH(arm)
493 ENTRY(stext)
494 jiffies = jiffies_64;
495 SECTIONS
496 {
497 /*
498 * XXX: The linker does not define how output sections are
499 * assigned to input sections when there are multiple statements
500 * matching the same input section name. There is no documented
501 * order of matching.
502 *
503 * unwind exit sections must be discarded before the rest of the
504 * unwind sections get included.
505 */
506 /DISCARD/ : {
507 *(.ARM.exidx.exit.text)
508 *(.ARM.extab.exit.text)
509
......
645 }
第493
行的ENTRY 指明了Linux内核入口函数为stext,因此要分析Linux
内核第二阶段的启动流程,就得先从文件arch/arm/kernel/head.S 的stext处开始分析。
30.2.1 Linux内核入口函数stext
stext
是
Linux
内核的入口地址,在文件
arch/arm/kernel/head.S
中有如下内容:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
.....
*/
以上代码中的内容显示。Linux
内核启动之前要求如下:
①
关闭MMU
②
关闭D-cache
③
不用关心I-cache
④ r0 = 0
。
⑤ r1 = machine nr
(机器
ID
号)
⑥ r2 = atags
或者设备树(
dts
)首地址
Linux
内核的入口点
stext
其实相当于内核的入口函数,
stext
函数代码如下:
80 ENTRY(stext)
......
91 @ ensure svc mode and all interrupts masked
92 safe_svcmode_maskall r9
93
94 mrc p15, 0, r9, c0, c0 @ get processor id
95 bl __lookup_processor_type @ r5=procinfo r9=cpuid
96 movs r10, r5 @ invalid processor (r5=0)?
97 THUMB( it eq ) @ force fixup-able long branch encoding
98 beq __error_p @ yes, error 'p'
99
......
107
108 #ifndef CONFIG_XIP_KERNEL
......
113 #else
114 ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
115 #endif
116
117 /*
118 * r1 = machine no, r2 = atags or dtb,
119 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120 */
121 bl __vet_atags
......
128 bl __create_page_tables
129
130 /*
131 * The following calls CPU specific code in a position independent
132 * manner. See arch/arm/mm/proc-*.S for details. r10 = base of
133 * xxx_proc_info structure selected by __lookup_processor_type
134 * above. On return, the CPU will be ready for the MMU to be
135 * turned on, and r0 will hold the CPU control register value.
136 */
137 ldr r13, =__mmap_switched @ address to jump to after
138 @ mmu has been enabled
139 adr lr, BSYM(1f) @ return (PIC) address
140 mov r8, r4 @ set TTBR1 to swapper_pg_dir
141 ldr r12, [r10, #PROCINFO_INITFUNC]
142 add r12, r12, r10
143 ret r12
144 1: b __enable_mmu
145 ENDPROC(stext)
第92行,调用函数safe_svcmode_maskall确保CPU处于SVC模式,并且关闭了所有的中断。safe_svcmode_maskall定义在文件arch/arm/include/asm/assembler.h
中。
第94行,读处理器ID,ID值保存在r9
寄存器中。
第95行,__lookup_processor_type
调用结束返回原程序时,会将返回结果保存到寄存器中。其中
r5
寄存器返回一个用来描述处理器的结构体地址,并对
r5
进行判断,如果
r5
的值为
0
则说明不支持这种处理器,将进入
__error_p
。procinfo
是proc_info_list
类
型
的
结
构
体
,Linux
内核将每种处理器都抽象为一个proc_info_list 结构体,每种处理器都对应一个procinfo。proc_info_list在文件arch/arm/include/asm/procinfo.h 中的定义如下:
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
第121 行,调用函数__vet_atags
验证atags 或设备树(dtb)的合法性。函数__vet_atags
定义在文件arch/arm/kernel/head-common.S
中。
第128 行,调用函数__create_page_tables
创建页表。
第137 行,将函数__mmap_switched
的地址保存到r13 寄存器中。__mmap_switched
定义在文件arch/arm/kernel/head-common.S,__mmap_switched
最终会调用start_kernel
函数。
第144
行
,调用__enable_mmu
函
数
使
能MMU
,__enable_mmu
定
义
在
文
件arch/arm/kernel/head.S 中。__enable_mmu 最终会通过调用__turn_mmu_on
来打开MMU,__turn_mmu_on
最后会执行r13 里面保存的__mmap_switched 函数。
30.2.2 _mmap_switched函数
__mmap_switched 函数定义在文件arch/arm/kernel/head-common.S中,函数代码如下:
81 __mmap_switched:
82 adr r3, __mmap_switched_data
83
84 ldmia r3!, {r4, r5, r6, r7}
85 cmp r4, r5 @ Copy data segment if needed
86 1: cmpne r5, r6
87 ldrne fp, [r4], #4
88 strne fp, [r5], #4
89 bne 1b
90
91 mov fp, #0 @ Clear BSS (and zero fp)
92 1: cmp r6, r7
93 strcc fp, [r6],#4
94 bcc 1b
95
96 ARM( ldmia r3, {r4, r5, r6, r7, sp})
97 THUMB( ldmia r3, {r4, r5, r6, r7} )
98 THUMB( ldr sp, [r3, #16] )
99 str r9, [r4] @ Save processor ID
100 str r1, [r5] @ Save machine type
101 str r2, [r6] @ Save atags pointer
102 cmp r7, #0
103 strne r0, [r7] @ Save control register values
104 b start_kernel
105 ENDPROC(__mmap_switched)
第104
行最终调用start_kernel
来启动Linux 内核,start_kernel
函数定义在文件init/main.c
中。
从start_kernel 函数
开始Linux内核启动进入到下一个阶段。
下面内容请关注:Linux内核启动流程-迅为IMX6ULL开发板(二)
|