内核启动过程中机器码的确定

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型号的确认》,思路都是一样的。

以上是关于内核启动过程中机器码的确定的主要内容,如果未能解决你的问题,请参考以下文章

32163165

Linux内核启动

如何确定Linux内核源代码目录即,KBUILD的路径

LINUX PID 1和SYSTEMD PID 0 是内核的一部分,主要用于内进换页,内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程(代码片段

ubuntu16.04启动过程怎么选择内核版本

以编程方式查找机器上的内核数