内核启动过程中机器码的确定
Posted 代二毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核启动过程中机器码的确定相关的知识,希望对你有一定的参考价值。
1、前言
嵌入式设备都是高度定制的,每一款开发板都需要移植内核,直接将从官网下载的内核编译是不能在开发板上运行的,需要针对开发板做一些修改。又因为内核是高度可配置的,所以我们移植内核的结果就是得到一些开发板独有的配置文件。内核需要记录当前版本的内核支持哪些开发板,于是内核就维护了支持的开发板序列,每个开发板都会有一个机器码作为唯一标识。启动内核时需要传递机器码,通过r1寄存器传递,内核会去查询传过来的机器码是否在支持的机器码序列中,如果查询到则按该机器码对应的配置文件进行启动;如果查询不到,说明内核不支持该开发板,启动终止。
2、描述开发板的结构体:struct machine_desc
struct machine_desc
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
;
这是描述开发板信息的结构体,需要重点关注的是nr,也就是机器码,这是开发板的唯一标识。init_machine函数指针是开发板硬件初始化的函数,硬件初始化的相关函数都放到此函数中。该结构体在arch/arm/include/asm/mach/arch.h中定义。
3、*(.arch.info.init)段
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
链接脚本中有arch.info.init段,这个段是用来存放开发板信息的,内核支持的开发板都会把描述开发板信息的struct machine_desc结构体赋予arch.info.init段属性,这样在链接的时候就链接在一起,便于将来遍历。
4、MACHINE_START宏和MACHINE_END宏
#define MACHINE_START(_type,_name) \\
static const struct machine_desc __mach_desc_##_type \\
__used \\
__attribute__((__section__(".arch.info.init"))) = \\
.nr = MACH_TYPE_##_type, \\
.name = _name,
#define MACHINE_END \\
;
#endif
通过MACHINE_START和MACHINE_END来定义描述开发板信息的struct machine_desc结构体。这两个宏的作用:定义一个静态的struct machine_desc结构体,并赋予结构体arch.info.init的段属性。
5、添加开发板的描述信息(struct machine_desc)
MACHINE_START(SMDKV210, "SMDKV210")
.phys_io = S3C_PA_UART & 0xfff00000,
.io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
.boot_params = S5P_PA_SDRAM + 0x100,
.init_irq = s5pv210_init_irq,
.map_io = smdkc110_map_io,
.init_machine = smdkc110_machine_init,
.timer = &s5p_systimer,
MACHINE_END
/*将上面的宏展开*/
static const struct machine_desc __mach_desc_SMDKV210 \\
__used \\
__attribute__((__section__(".arch.info.init"))) = \\
.nr = MACH_TYPE_SMDKV210, //#define MACH_TYPE_SMDKV210 2456
.name = "SMDKV210",
.phys_io = S3C_PA_UART & 0xfff00000,
.io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
.boot_params = S5P_PA_SDRAM + 0x100,
.init_irq = s5pv210_init_irq,
.map_io = smdkc110_map_io,
.init_machine = smdkc110_machine_init,
.timer = &s5p_systimer,
;
struct machine_desc结构体的定义是在板级配置文件里定义的,以S5PV210开发板为例,是在arch/arm/mach-s5pv210/mach-smdkv210.c文件里定义的。在编译内核时并不是把所有的开发板都编译进去,可以通过配置文件或者menuconfig进行配置。
6、内核启动时校验机器码
······
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
······
代码摘抄自head.S文件,内核启动的汇编阶段。
(1)调用__lookup_machine_type函数,查找当前开发板是否在内核支持的开发板序列中;
(2)如果查找到则r5寄存器的值是对应的struct machine_desc结构体的地址;否则r5寄存器的值为0;
(3)如果r5=r8,也就是r5的值是零,跳转到__error_a函数,终止内核启动;
7、__lookup_machine_type函数
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long .
.long __arch_info_begin @链接脚本指定.arch.info.init的起始地址
.long __arch_info_end @链接脚本指定.arch.info.init的结束地址
__lookup_machine_type:
adr r3, 4b
ldmia r3, r4, r5, r6
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
ENDPROC(__lookup_machine_type)
(1)这里有个隐含的知识点:r1保存的是机器码,是uboot启动内核时传参传过来的;
(2)__lookup_machine_type函数的思路就是得到.arch.info.init段的起始地址和结束地址,遍历struct machine_desc结构体,直到找到对应的机器码或者是遍历结束。更详细的汇编语句介绍参考《内核启动过程中对CPU型号的确认》,思路都是一样的。
以上是关于内核启动过程中机器码的确定的主要内容,如果未能解决你的问题,请参考以下文章
LINUX PID 1和SYSTEMD PID 0 是内核的一部分,主要用于内进换页,内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程(代码片段