学习笔记 --- LINUX内核启动第二阶段分析(不考虑自解压过程)

时间:2021-09-09 16:45:40

上篇文章中分析了Linux内核从head.s启动:

	.section ".text.head", "ax"
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __vet_atags
bl __create_page_tables

1 开始设置ARM模式为管理模式,禁止中断,然后获取处理器ID

2 调用__lookup_processor_type 判断内核是否支持该处理器,这里略,不讲!

3 处理器ID验证完后调用  __lookup_machine_type  判断内核是否支持该机器类型:

__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)
第二行4b的位置是:

4:	.long	.
.long __arch_info_begin
.long __arch_info_end
所以2,3行执行之后:

r4 = .  r5=__arch_info_begin  r6=__arch_info_end
4,5,6行都有解释,目的就是为了计算出__arch_info_begin 和__arch_info_end在SDRAM里面实际存放地址,这两个东西是链接文件里面定义的arch.info.init段的起始和结束标志:

__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
而arch.info.init段存放的是一个结构体,从代码可以看出:

/*
* 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 \
};

该结构体格式为:

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);
};
定义的实体:

MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, //就是0x30000100

.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

还有很多很多不同的单板都定义了这个,可以知道,这个arch.info.init段里面放的就是linux内核支持的所有单板的参数信息,再回到汇编,7-10行就是判断每个单板的机器类型(读到寄存器r3)是否和uboot传入的单板参数(存在寄存器r1)匹配,如果匹配则内核支持该单板。

4 接着执行__vet_atags :

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

ldr r5, [r2, #0] @ is first tag ATAG_CORE?
cmp r5, #ATAG_CORE_SIZE
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE
cmp r5, r6
bne 1f

mov pc, lr @ atag pointer is ok

1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
这个函数就是去验证uboot传入的TAG参数区域的ATAG_CORE_SIZE与ATAG_CORE是否和内核的匹配,这段TAG参数起始地址存在寄存器r2,读出来再比较这两项是否一致,如果一致则通过。

5 接下来建立页表

6 使能MMU

最后跳转到 start_kernel 进入C语言代码段,开始打印版本信息,然后执行下面这段代码,主要作用就是解析uboot传入的参数:

void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;

unwind_init();

setup_processor();
mdesc = setup_machine(machine_arch_type); //设置机器类型,返回machine_desc结构体,这个结构体通过MACHINE_START设置好了
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");

if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) //就是结构体里面的S3C2410_SDRAM_PA + 0x100 = 0x30000100,这个地址存放uboot设置好的TAG参数
tags = phys_to_virt(mdesc->boot_params); //转为虚拟地址,返回tags,tags指向uboot存放的TAG参数起始地址

/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE) //如果TAG参数不匹配,则使用内核默认的参数
tags = (struct tag *)&init_tags;

if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}

init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;

memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
parse_cmdline(cmdline_p, from); //解析命令行,就是解析uboot传入的bootargs环境变量,它为TAG参数的一部分
paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
smp_init_cpus();
#endif

cpu_init();
tcm_init();

/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq; //把结构体里的参数都放在独自的变量里单独表示
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
上面执行完后,返回bootargs参数到cmdline_p,然后再执行下面函数,将命令行地址存放在全局指针saved_command_line里面:

static void __init setup_command_line(char *command_line)
{
saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);
static_command_line = alloc_bootmem(strlen (command_line)+1);
strcpy (saved_command_line, boot_command_line);
strcpy (static_command_line, command_line);
}
然后下面会用unknown_bootoption函数来解析这个命令行,比如文件系统挂载到哪个地方,使用串口几作为监控端等;

接下来是一大堆初始化,执行到最后rest_init:

kernel_init ---- prepare_namespace ---- mount_root (挂接根文件系统)

挂接好之后 init_post:

static noinline int init_post(void)
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) //打开终端文件 ----用于标准printf打印
printk(KERN_WARNING "Warning: unable to open an initial console.\n");

(void) sys_dup(0); //复制终端文件 用于 标准scanf输入
(void) sys_dup(0); //复制终端文件 用于 标准error打印

current->signal->flags |= SIGNAL_UNKILLABLE;

if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}

/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) { //
run_init_process(execute_command); //执行uboot传入的“init=xxx”指定的应用脚本
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init"); //如果上面uboot没有指定,那么往下执行默认路径的脚本
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

panic("No init found. Try passing init= option to kernel.");
}
上面挂接文件系统mount_root ,挂接到哪里是由环境变量bootargs里面的“root=” 类参数决定的,是通过unknown_bootoption函数解析出来的。

假设uboot传进来参数为  “root=/dev/mtdblock3” 则表示挂载到mtd设备的第4个分区,至于这个分区对应哪里是在nandflash的驱动里面指定的:

static struct mtd_partition friendly_arm_default_nand_part[] = {
[0] = {
.name = "uboot",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "param",
.offset = 0x00040000,
.size = 0x00020000,
},
[2] = {
.name = "Kernel",
.offset = 0x00060000,
.size = 0x00500000,
},
[3] = {
.name = "root",
.offset = 0x00560000,
.size = 1024 * 1024 * 1024,
},
[4] = {
.name = "nand",
.offset = 0x00000000,
.size = 1024 * 1024 * 1024, //
}
};

上面可以知道第4个分区就是root根文件系统分区,这个在一直nand驱动的时候要和uboot传进的参数对应起来。