ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux启动流程之arm (一)

2021-10-23 23:32:37  阅读:196  来源: 互联网

标签:__ info r5 流程 init 内核 Linux arch arm


老的内核版本,不带dts内核:

1. 机器 ID,启动参数

启动文件head.S,主要完成如下几件事:

  • (0)判断是否支持此CPU
  • (1)如何比较机器ID是:(判断是否支持单板)
  • (3)创建页表。
  • (4)使能MMU。
  • (5)跳转到 start_kernel (它就是内核的第一个 C 函数)

2.分析内核源代码

1.通过make uImage V=1详细查看内核编译时的最后一条命令可知。

  • 内核中排布的第一个文件是:arch/arm/kernel/head.S
  • 链接脚本:arch/arm/kernel/vmlinux.lds

UBOOT启动时首先在内存里设置了一大堆参数。

接着启动内核:

theKernel(0, bd->bi_arch_number, bd->bi_boot_params);

theKernel 就是内核的入口地址。有3 个参数。

  • 参1为0 r0
  • 参2为机器ID r1
    • machine numbers:linux/arch/arm/tools/mach-types
  • 参3是上面那些参数所存放的地址r2
    所以,内核一上来肯定要处理这些参数。

3.内核启动

内核启动:最终目标是就运行应用程序。对于Linux 来说应用程序在根文件系统中,要挂接。

3.1.老内核,处理UBOOT 传入的参数

内核中排布的第一个文件是:arch/arm/kernel/head.S

arch/arm/kernel/head.S
arch/arm/boot/compressed/head.S

内核编译出来后比较大,可以压缩很小。在压缩过的内核前部加一段代码“自解压代码”。
这样内核运行时,先运行“自解压代码”。然后再执行解压缩后的内核。

我们看不用解压的head.S文件

mrc p15, 0, r9, c0, c0     @get processor id
bl __lookup_processor_type @r5=procinfo r9=cpuid

__lookup_processor_type查找处理器类型内核能够支持哪些处理器,是在编译内核时定义下来的。内核启动时去读寄存器:获取ID。

__lookup_processor_type:
    adr r3, __lookup_processor_type_data
    ldmia   r3, {r4 - 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:  ldmia   r5, {r3, r4}            @ value, mask
    and r4, r4, r9          @ mask wanted bits
    teq r3, r4
    beq 2f
    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    cmp r5, r6
    blo 1b
    mov r5, #0              @ unknown processor
2:  mov pc, lr
ENDPROC(__lookup_processor_type)

注意:前面的内容是连续的。能够对虚拟地址到物理地址进行线性映射。虚拟地址+虚拟到物理地址的偏移=物理地址

首先 r3 等于__lookup_processor_type_data的地址如下。这是实际存在的地址。UBOOT 启动内核时,MMU 还没启动。所以这是物理地址。

  • ldmia r3, {r4 - r6},将r3依次赋值给r4-r6,这是相当于long类型数组r4=. ,r5=__proc_info_begin,r6=__proc_info_end,size = . - __lookup_processor_type_data是虚拟地址
  • sub r3, r3, r4: r4 等于“.”虚拟地址,r3是物理地址,得到virt和phys之间的偏移. r3最后等于offset
  • add r5, r5, r3add r6, r6, r3 :它们加上这个偏差值后,就变成了__proc_info_begin__proc_info_end的真正物理地址了。

__lookup_processor_type_data内容如下:

/*  
 * Look in <asm/procinfo.h> for information about the __proc_info structure.
 */
    .align  2
    .type   __lookup_processor_type_data, %object
__lookup_processor_type_data:
    .long   .
    .long   __proc_info_begin
    .long   __proc_info_end
    .size   __lookup_processor_type_data, . - __lookup_processor_type_data

其中__proc_info_begin,__proc_info_end没有在内核源码中定义,是在 链接脚本 中定义的(vmlinux.lds)。

	__proc_info_begin = .;
	__arch_info_begin = .;
	__proc_info_end = .;

中间夹着 *(.arch .info .init),*表示所有文件。这里是指所有文件的 .arch .info .init段。(架构、信息、初始化)即架构相关的初始化信息全放在这里。它开始地址
是__arch_info_begin,结束地址是 __arch_info_end。这两个地址上从. = (0xc0000000) + 0x00008000(虚拟地址)一路的增涨下来的。

. = (0xc0000000) + 0x00008000;
.text.haed: {
	_stext = .;
	_sinittext = .;
	*(.text.head)
}
.init : { /*Init code and data*/
	*(.init.text)
	_einittext = .;
	__proc_info_begin = .;
	__arch_info_begin = .;
	__proc_info_end = .;
	__arch_info_begin = .;
	*(.arch.info.init)
	__arch_info_end = .;
	_tagtable_begin = .;
	*(.taglist.init)
	_tagtable_end = .;
}

详细看下节内核
看处理器ID 后,看内核是否可以支持这个处理器。若能支持则继续运行,不支持则跳到“_error_p”中去:

    beq __error_p @yes , error 'p'
    adr r0, str_p1
    bl  printascii
    mov r0, r9
    bl  printhex8
    adr r0, str_p2
    bl  printascii
    b   __error 
str_p1: .asciz  "\nError: unrecognized/unsupported processor variant (0x"
str_p2: .asciz  ").\n"
    .align
__error:
1:  mov r0, r0
    b   1b

__error这是个死循环。

一个编译好的内核能支持哪些单板,都是定下来的。(make CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm vexpress_defconfig编译内核时候制定)内核上电后会检测下看是否支持当前的
单板。若可以支持则继续往下跑,不支持则 __error_a 跳到死循环。
内核支持多少单板,就有多少个这种以 “MACHINE_START”开头,以“MACHINE_END”
定义起来的代码。每种单板都有机器ID(结构体中的nr),机器ID 是整数。
UBOOT 传来这个参数:

theKernel(0, bd->bi_arch_numer, bd->bi_boot_params);

与内核的这部分刚好对应(如下部分),内核将这部分代码编译进去了,就支持这段代码定义的 单板。上面这段代码中的结构体有
一个属性,它的段被强制设置到了体被放在 vmlinux.lds 定义的:

__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;

2410,2440,qt2410 的单板代码都强制放在这个地方。内核启动时,会从 __arch_info_begin = . 开始读,读到 __arch_info_end = . 一个一个的将单板信息取出来。将里面的机器ID 和UBOOT 传进来的机器ID 比较。相同则表示内核支持这个
单板。

下面就是比较机器ID了。(内核中的和UBOOT 传进来的)看arch\arm\kernel\head-common.S
从“__lookup_machine_type:”中可知

r5=__arch_info_begin
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
  • r5 是: __arch_info_begin
  • r1 是UBOOT 传来的参数:bi_arch_number
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

最后比较成功后,会回到:head.S

bl __lookup_machine_type @r5 = machinfo
movs r8, r5 @invliad machine (r5=0)?

以上 单板机器ID 比较完成。

3.2 新内核,处理UBOOT 传入的参数

r2决定使用atags还是使用dtb(CONFIG_OF_FLATTREE)

  • ATAG_CORE:探测RAM前16k内存,要求指针对齐.
  • CONFIG_OF_FLATTR:如果使能,则接收dtb地址
head.S->head_common.S __vet_atags:
# r2 either valid atags pointer, valid dtb pointer, or zero

__vet_atags:
    tst r2, #0x3               @ aligned?
    bne 1f

    ldr r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
    ldr r6, =OF_DT_MAGIC        @ is it a DTB?
    cmp r5, r6
    beq 2f
#endif
    cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?
    cmpne   r5, #ATAG_CORE_SIZE_EMPTY
    bne 1f
    ldr r5, [r2, #4]
    ldr r6, =ATAG_CORE
    cmp r5, r6
    bne 1f

2:  mov pc, lr              @ atag/dtb pointer is ok

1:  mov r2, #0
    mov pc, lr
ENDPROC(__vet_atags)

4.内核

4.1 老内核定义

知道上面这个关系后,我们则一定要知道在代码里面谁定义了.arch.info.init 这些东西。在内核中搜索它们。grep *.arch.info.init * -rR
查它们的定义:include/asm-arm/mach/arch.h +53

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#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				\
};

例如:sm2440

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
	.atag_offset	= 0x100,

	.init_irq	= s3c2440_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.init_time	= smdk2440_init_time,
MACHINE_END

上段代码展开:

static const struct machine_desc __mach_desc_S3C2440 
	__used	
	__attributed__((__section__(".arch.info.init"))) = { 
		.nr = MACH_TYPE_S3C2440,
		.name = "SMDK2440",
	.phys_io = S3C2410_PA_UART,
	.io_pg_offset = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
	.boot_params = S3C2410_SDRAW_PA + 0x100,
	.init_irq = s3c24xx_init_irq,
	.map_io = _smkd2440_map_io,
	.init_machine = smdk2440_machine_init,
	.timer = &s3c24xx_timer,
};

4.2 新内核定义

// init/main.c
setup_arch(&cmd_line);

// arch/arm/kernel/setup.c
void __init setup_arch(char **cmd_line_p)
{
  mdesc = setup_machine_fdt(__atags_pointer);
}

//arch/arm/kernel/devtree.c
struct machine_desc *__init setup_machine_fdt(unsigned int dt_phys)
{
    struct boot_param_header *devtree = phys_to_virt(dt_phys);
	/* check device tree validity */
    if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)
        return NULL;

	/* Search the mdescs for the 'best' compatible value match */
	initial_boot_params = devtree;
	dt_root = of_get_flat_dt_root();
	for_each_machine_desc(mdesc) {
	  score = of_flat_dt_match(dt_root, mdesc->dt_compat);
	  if (score > 0 && score < mdesc_score) {
		mdesc_best = mdesc;
		mdesc_score = score;
	  }
	}
}
  • 其中:__atags_pointer r2 dtb地址
  • dt_rootinitial_boot_params由__atags_pointer计算得到,即相当于从r2传入

for_each_machine_desc:对应宏

/*        
 * Machine type table - also only accessible during boot
 */           
extern struct machine_desc __arch_info_begin[], __arch_info_end[];
#define for_each_machine_desc(p)            \
    for (p = __arch_info_begin; p < __arch_info_end; p++)

这里mdesc对应lds中变量,即DT_MACHINE_START中的信息


//linux\arch\arm\mach-at91\at91rm9200.c
static void __init at91rm9200_dt_device_init(void)
{
	//注意这里解析 dts
	of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

	arm_pm_idle = at91rm9200_idle;
	arm_pm_restart = at91rm9200_restart;
	at91rm9200_pm_init();
}

static const char *at91rm9200_dt_board_compat[] __initconst = {
	"atmel,at91rm9200",
	NULL
};

DT_MACHINE_START(at91rm9200_dt, "Atmel AT91RM9200")
	.init_time      = at91rm9200_dt_timer_init,
	.map_io		= at91_map_io,
	.init_machine	= at91rm9200_dt_device_init,
	.dt_compat	= at91rm9200_dt_board_compat,
MACHINE_END

看看结构体:machine_desc

struct machine_desc {
	unsigned int		nr;		/* architecture number	机器ID*/
	const char		*name;		/* architecture name	*/
	unsigned long		atag_offset;	/* tagged list (relative) */
	const char *const 	*dt_compat;	/* array of device tree
						 * 'compatible' strings	*/

	unsigned int		nr_irqs;	/* number of IRQs */

#ifdef CONFIG_ZONE_DMA
	phys_addr_t		dma_zone_size;	/* size of DMA-able area */
#endif

	unsigned int		video_start;	/* start of video RAM	*/
	unsigned int		video_end;	/* end of video RAM	*/

	unsigned char		reserve_lp0 :1;	/* never has lp0	*/
	unsigned char		reserve_lp1 :1;	/* never has lp1	*/
	unsigned char		reserve_lp2 :1;	/* never has lp2	*/
	enum reboot_mode	reboot_mode;	/* default restart mode	*/
	unsigned		l2c_aux_val;	/* L2 cache aux value	*/
	unsigned		l2c_aux_mask;	/* L2 cache aux mask	*/
	void			(*l2c_write_sec)(unsigned long, unsigned);
	struct smp_operations	*smp;		/* SMP operations	*/
	bool			(*smp_init)(void);
	void			(*fixup)(struct tag *, char **);
	void			(*dt_fixup)(void);
	void			(*init_meminfo)(void);
	void			(*reserve)(void);/* reserve mem blocks	*/
	void			(*map_io)(void);/* IO mapping function	*/
	void			(*init_early)(void);
	void			(*init_irq)(void);
	void			(*init_time)(void);
	void			(*init_machine)(void);
	void			(*init_late)(void);
	void			(*restart)(enum reboot_mode, const char *);
};

3.3 创建页表

bl __create_page_tables

内核的链接地址从虚拟地址

. = (0xc00000000) + 0x00008000;

开始。这个地址并不代表真实存在的内存。我们的内存是从 0x3000 0000 开始的。故这里面要建立一个页表,启动 MMU 。

3.4 使能MMU

ldr r13, __switch_data @ address to jump to after mmu has been enabled
adr lr, __enbable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC

mmu has been enabled 当MMU 使能后,会跳到 __switch_data 中去。
如何跳到 __switch_data ,则看:__enable_mmu

	.type __switch_data, %object
__switch_data:
	.long __mmap_switched
	.long __data_loc @ r4
	.long __data_start @ r5
	.long __bss_start @ r6
	.long __end 	  @ r7
	.long processor_id @ r4
	.long __machine_arch_type @ r5
	.long cr_alignment @ r6
	.long init_thread_union + THREAD_START_SP @ sp

在head_common.S 文件中,__switch_data 后是:__mmap_switched

__mmap_switched:
	adr r3, __switched_data + 4
	ldmia r3!, {r4, r5, r6, r7}
	cmd r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
	ldrne fp, [r4] , #4
	strne fp, [r5] , #4
	bne 1b

	mov fp, #0 @ clear BSS (and zero fp)

1: cmp r6, r7
	strcc fp, [r6], #4
	bcc 1b

	ldmia r3, {r4, r4, r6, sp}
	str r9, [r4] @ Save processor ID
	str r1, [r5] @ Save machine type
	bic r4, f0, #CR_A
	stmia r6, {r0, r4}
	b start_kernel @跳转到第一个C函数中来处理UBOOT传来的参数

4.start_kernel(它就是内核的第一个C函数)

UBOOT 传进来的启动参数,参2:机器ID 在head.Skh 中会比较。
参3:传进来的参数,就是在这个第一个C函数start_kernel 中处理
分析第一个C 函数 start_kernel :在 main.c 文件中

asmlinkage __visible void __init start_kernel(void)

标签:__,info,r5,流程,init,内核,Linux,arch,arm
来源: https://www.cnblogs.com/ellabrain/p/15449949.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有