Linux启动流程

2023-12-21 12:44:28

Linux系统启动流程大致分为三个阶段,uboot,kernel和根文件系统。

uboot

uboot的启动又可以分为三个阶段,汇编阶段、C语言阶段和启动内核阶段。

汇编阶段

  • 定义入口(start.S):uboot中因为有汇编阶段参与,因此不能直接找main.c。
  • 设置异常向量:当硬件发生故障的时候CPU会强制PC指针指向对应的异常入口执行代码。
  • 设置CPU为SVC模式(设置CPU速度、时钟频率和中断控制寄存器)。

注:

svc模式本身就属于特权模式,本身就可以访问那些受控资源,而且,比sys模式还多了些自己模式下的影子寄存器,所以,相对sys模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。所以,从理论上来说,虽然可以设置为sys和svc模式的任一种,但是从uboot方面考虑,其要做的事情是初始化系统相关硬件资源,需要获取尽量多的权限,以方便操作硬件,初始化硬件。

从uboot的目的是初始化硬件的角度来说,设置为svc模式,更有利于其工作。因此,此处将CPU设置为SVC模式。

  • 初始化内存控制器(MMU),实现虚拟地址到物理地址的映射。
  • 跳转到lowlevel_init函数,关看门狗,供电锁存,时钟初始化。
  • 初始化堆栈,DDR。

C语言阶段

  • uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片····)、uboot本身的一些东西(uboot的命令、环境变量等····)。因为uboot需要从网络上或者flash上读取内核和文件系统个,需要会有flash设备的驱动程序。
  • 调用一系列的初始化函数:init_sequence里的init函数是一个函数指针数组,数组中存储了很多个函数指针,会去调用其他初始化函数。
  • cpu_init(CPU内部的初始化),board_init(x210开发板相关的初始化),interrupt_init(初始化定时器),env_init(环境变量有关的初始化),
  • init_baudrate(初始化串口通信的波特率),serial_init(初始化串口),display_banner(用来串口输出显示uboot的信息)。

启动内核

  • 启动内核第一步:加载内核到DDR中,boot只需要从SD卡的kernel分区去读取内核镜像到DDR中即可。
  • 启动内核第二步:校验内核格式(zImage格式,uImage格式)。
  • 启动内核第三步:内核传参。

具体函数调用

start.s
// 汇编环境
=> IRQ/FIQ/lowlevel/vbar/errata/cp15/gic  // ARM 架构相关的 lowlevel 初始化
=> _main
  => stack  // 准备好 C 环境需要的栈
  // 【第一阶段】 C 环境初始化,发起一系列的函数调用
  => board_init_f: init_sequence_f[]
    initf_malloc
    arch_cpu_init  // SoC 的 lowlevel 初始化
    serial_init    // 串口初始化
    dram_init      // 获取 ddr 容量信息
    reserve_mmu    // 从 ddr 末尾开始往低地址 reserve 内存
    reserve_video
    reserve_uboot
    reserve_malloc
    reserve_global_data
    reserve_fdt
    reserve_stacks
    dram_init_banksize
    sysmem_init
    setup_reloc  // 确定 U-Boot 自身要 reloc 的地址
  // 汇编环境
  => relocate_code // 汇编实现 U-Boot 代码的 relocation
  // 【第二阶段】 C 环境初始化,发起一系列的函数调用
  => board_init_r: init_sequence_r[]
    initr_caches   // 使能 MMU 和 I/Dcache
    initr_malloc
    bidram_initr
    sysmem_initr
    initr_of_live  // 初始化of_live
    initr_dm       // 初始化dm框架
    board_init     // 平台初始化,最核心部分
      board_debug_uart_init     // 串口 iomux、clk 配置
      init_kernel_dtb           // 切到 kernel dtb
      clks_probe                // 初始化系统频率
      regulators_enable_boot_on // 初始化系统电源
      io_domain_init            // io-domain 初始化
      set_armclk_rate           // __weak,ARM 提频(平台有需求才实现)
      dvfs_init                 // 宽温芯片的调频调压
      rk_board_init             // __weak,由各个具体平台进行实现
    console_init_r
    board_late_init  // 平台 late 初始化
      rockchip_set_ethaddr  // 设置 mac 地址
      rockchip_set_serialno // 设置 serialno
      setup_boot_mode       // 解析 "reboot xxx" 命令、识别按键和 loader烧写模式、recovery
      charge_display        // U-Boot 充电
      rockchip_show_logo    // 显示开机 logo
      soc_clk_dump          // 打印 clk tree
      rk_board_late_init    // __weak,由各个具体平台进行实现
    run_main_loop  // 进入命令行模式,或执行启动命令

uboot初始化流程关键代码

定义的数组如下:

static init_fnc_t init_sequence_r[] = {
	initr_trace,
	initr_reloc,
	/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
	initr_caches,
	/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
	 *	 A temporary mapping of IFC high region is since removed,
	 *	 so environmental variables in NOR flash is not availble
	 *	 until board_init() is called below to remap IFC to high
	 *	 region.
	 */
#endif
	initr_reloc_global_data,

	/*
	 * Some platform requires to reserve memory regions for some firmware
	 * to avoid kernel touches it, but U-Boot may have communication with
	 * firmware by share memory. So that we had better reserve firmware
	 * region after the initr_caches() which enables MMU and init
	 * translation table, we need firmware region to be mapped as cacheable
	 * like other regions, otherwise there would be dcache coherence issue
	 * between firmware and U-Boot.
	 */
	board_initr_caches_fixup,

#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
	initr_unlock_ram_in_cache,
#endif
	initr_barrier,
	initr_malloc,
#ifdef CONFIG_BIDRAM
	bidram_initr,
#endif
#ifdef CONFIG_SYSMEM
	sysmem_initr,
#endif
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
	initr_console_record,
#ifdef CONFIG_SYS_NONCACHED_MEMORY
	initr_noncached,
#endif
	bootstage_relocate,

	interrupt_init,
#ifdef CONFIG_ARM
	initr_enable_interrupts,
#endif
	interrupt_debugger_init,

#ifdef CONFIG_OF_LIVE
	initr_of_live,
#endif
#ifdef CONFIG_DM
	initr_dm,
#endif

#ifdef CONFIG_USING_KERNEL_DTB
	initr_env_nowhere,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_R)
	board_early_init_r,
#endif

#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV)
	board_init,	/* Setup chipselects */
#endif

#if defined(CONFIG_USING_KERNEL_DTB) && !defined(CONFIG_ENV_IS_NOWHERE)
	initr_env_switch,
#endif

	/*
	 * TODO: printing of the clock inforamtion of the board is now
	 * implemented as part of bdinfo command. Currently only support for
	 * davinci SOC's is added. Remove this check once all the board
	 * implement this.
	 */
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info, /* Setup clock information */
#endif
#ifdef CONFIG_EFI_LOADER
	efi_memory_init,
#endif
	stdio_init_tables,
	initr_serial,
	initr_announce,
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_NEEDS_MANUAL_RELOC
	initr_manual_reloc_cmdtable,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_MIPS)
	initr_trap,
#endif
#ifdef CONFIG_ADDR_MAP
	initr_addr_map,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_POST
	initr_post_backlog,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do early PCI configuration _before_ the flash gets initialised,
	 * because PCU ressources are crucial for flash access on some boards.
	 */
	initr_pci,
#endif
#ifdef CONFIG_ARCH_EARLY_INIT_R
	arch_early_init_r,
#endif
	power_init_board,
#ifdef CONFIG_MTD_NOR_FLASH
	initr_flash,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86)
	/* initialize higher level parts of CPU like time base and timers */
	cpu_init_r,
#endif
#ifdef CONFIG_PPC
	initr_spi,
#endif
#ifdef CONFIG_CMD_NAND
	initr_nand,
#endif
#ifdef CONFIG_CMD_ONENAND
	initr_onenand,
#endif
#ifdef CONFIG_MTD_BLK
	initr_mtd_blk,
#endif
#ifdef CONFIG_MMC
	initr_mmc,-------------------------------初始化mmc设备
#endif
#ifndef CONFIG_USING_KERNEL_DTB
	initr_env,
#endif
#ifdef CONFIG_SYS_BOOTPARAMS_LEN
	initr_malloc_bootparams,
#endif
	INIT_FUNC_WATCHDOG_RESET
	initr_secondary_cpu,
#if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET)
	mac_read_from_eeprom,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do pci configuration
	 */
	initr_pci,
#endif
	stdio_add_devices,
	initr_jumptable,
#ifdef CONFIG_API
	initr_api,
#endif
	console_init_r,		/* fully init console as a device */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
	console_announce_r,
	show_board_info,
#endif
#ifdef CONFIG_ARCH_MISC_INIT
	arch_misc_init,		/* miscellaneous arch-dependent init */
#endif
#ifdef CONFIG_MISC_INIT_R
	misc_init_r,		/* miscellaneous platform-dependent init */
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
	initr_kgdb,
#endif

#if defined(CONFIG_MICROBLAZE) || defined(CONFIG_M68K)
	timer_init,		/* initialize timer */
#endif
#if defined(CONFIG_LED_STATUS)
	initr_status_led,
#endif
	/* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
	initr_ethaddr,
#endif
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init,
#endif
#if defined(CONFIG_SCSI) && !defined(CONFIG_DM_SCSI)
	INIT_FUNC_WATCHDOG_RESET
	initr_scsi,
#endif
#ifdef CONFIG_BITBANGMII
	initr_bbmii,
#endif
#ifdef CONFIG_CMD_NET
	INIT_FUNC_WATCHDOG_RESET
	initr_net,
#endif
#ifdef CONFIG_POST
	initr_post,
#endif
#if defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_IDE)
	initr_pcmcia,
#endif
#if defined(CONFIG_IDE)
	initr_ide,
#endif
#ifdef CONFIG_LAST_STAGE_INIT
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Some parts can be only initialized if all others (like
	 * Interrupts) are up and running (i.e. the PC-style ISA
	 * keyboard).
	 */
	last_stage_init,
#endif
#ifdef CONFIG_CMD_BEDBUG
	INIT_FUNC_WATCHDOG_RESET
	initr_bedbug,
#endif
#if defined(CONFIG_PRAM)
	initr_mem,
#endif
#ifdef CONFIG_PS2KBD
	initr_kbd,
#endif
	run_main_loop,
};

在board_init_r函数中的调用如下:

关于CONFIG_USING_KERNEL_DTB的处理如下:

uboot启动流程总结

uboot的语言构成:10%的汇编语言;90%的C语言

uboot的启动特性:稳定性;速度

uboot的简化版启动流程:

1、设置状态寄存器 cpsr ,使CPU进入 SVC 特权模式,并且禁止 FIQ 和 IRQ;

2、关闭看门狗、中断、MMU、Cache;

3、初始化部分寄存器和外设(时钟、串口、Flash、内存);

4、自搬移uboot到内存中运行;

5、设置栈空间并初始化global_data;

6、剩余大部分硬件的初始化;

7、搬移Linux内核到内存;

?

内核启动过程

汇编阶段

__lookup_processor_type:读取出硬件的CPU ID号,__lookup_machine_type:本函数校验的是机器码,

__vet_atags:用来校验uboot通过tag给内核传的参数。

C语言阶段

  • lockdep_init:锁定依赖,是一个内核调试模块,处理内核自旋锁死锁问题相关的。
  • boot_init_stack_canary:用来防止栈溢出。
  • cgroup_init_early:control group,内核提供的一种来处理进程组的技术。
  • local_irq_disable:屏蔽当前CPU上的所有中断。
  • lock_kernel:获得大内核锁,该锁可以用来锁定整个内核。
  • page_address_init:函数初始化高端内存页表池的链表。
  • 打印内核版本信息。
  • 最后会执行init进程,init进程刚开始运行的时候是内核态,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态。
  • init进程会去挂载根文件系统,打开控制台,访问一个设备,就要去打开这个设备对应的文件描述符。譬如/dev/fb0这个设备文件就代表LCD显示器设备。

根文件系统

根文件系统是特殊用途的文件系统,文件系统是一些代码,是一套软件,这套软件的功能就是对存储设备的扇区进行管理,将这些扇区的访问变成了对目录和文件名的访问.

文章来源:https://blog.csdn.net/l00102795/article/details/135122959
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。