bootm启动linux内核
boot/bootm.c
do_sql -> sql_export -> call_bootm -> do_bootm -> boot_run
boot_run:
被bootm_run,bootz_run,booti_run调用
初始化status然后调用bootm_run_states
bootm_run_states:
首次启动时:先调用bootm_start
再调用bootm_pre_load:获得镜像(image)起始地址,镜像预加载
调用bootm_find_os:确定操作系统
其他加载
bootm_measure:启动过程中执行关键组件测量的函数,安全启动之类的
加载操作系统bootm_load_os
内存重定位boot_ramdisk_high:这里把ramdisk重定位了
设备树重定位boot_relocate_fdt(但是设备树重定位函数没执行,反而是后面用的arm/lib里面的bootm文件中的函数)
获取操作系统引导函数:bootm_os_get_boot_func(这里os=5,代表linux)
调用不同状态的处理函数
boot_fn(BOOTM_STATE_OS_PREP, bmi) 这里实际上是调用do_bootm_linux(在第三页详述)
不出错就bootm_selected_os运行操作系统(ARMV7,运行之后不返回)
bootm_start:
获得image.verify yesno
调用boot_start_lmb初始化逻辑内存块(logic memory block),预留该范围
boot_stage_markname:在commom/bootstage.c中
image.status修改为start
return 0;
bootm_find_os:
调用boot_get_kernel:
genimg_get_format() :检查指针是否指向合法镜像
执行操作系统相应的初始化赋值
boot_get_kernel:
确定内核镜像的enable类型
输出第一句:printf("## Booting kernel from Legacy Image at %08lx ...\n",img_addr);
输出镜像相关信息
调用image_get_kernel验证legacy镜像完整性,返回指向镜像头部的指针
复制镜像头部
保存指向镜像头的指针
commom/bootstage.c
这里都是一些跟启动进度记录有关的,不是很重要
boot_stage_markname:
设置flag
调用bootstage_add_record
bootstage_add_record:
功能:增加阶段记录(“bootm_start”),首次时增加
调用函数打印启动进度
boot/image-board.c
boot_ramdisk_high:
boot_reolacate_fdt
这两个函数看起来厉害,但是实际上都没有调用他们。是加载具体操作系统,获取对应操作系统函数之后才进行的重定位。
env/common.c
env_get:
通过哈希表获取环境变量
arch/arm/lib/bootm.c
do_bootm_linux:
这个函数被调用了两次,第一次是提前准备,在bootm_run_states中直接调用。第二次调用不再返回,是真跳转到内核,在bootm_selected_os中调用
第一次这函数调用了boot_prep_linux,进行跳转前的准备工作
image_setup_linux
这个函数进行了设备树的重定位
之前一直在找bootargs在哪里设置,结果一直找不到,结果发现bootargs被添加到设备树的节点里面去了
第二次这函数调用boot_jump_linux,jump只被调用一次,直接跳转到kernel,并没有伪跳转,假跳转可能只在uboot开发调试阶段使用(假跳转在调试验证、步骤分析等过程中很有用)第一次时是假跳转跳
__________________________________________________________________________
bootargs 通常是在设备树的 /chosen 节点中设置的一个属性,用于传递内核命令行参数
unsigned long machid = gd->bd->bi_arch_number;//变量machid 保存机器ID,如果不使用设备树的话这个机器ID 会被传递给Linux内核,Linux 内核会在自己的机器ID 列表里面查找是否存在与uboot 传递进来的machid 匹配的项目,如果存在就说明Linux 内核支持这个机器,那么Linux 就会启动!如果使用设备树的话这个machid 就无效了,设备树存有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。
char *s;
void (*kernel_entry)(int zero, int arch, uint params);//进入内核的函数指针,此函数有三个参数:zero,arch,params,第一个参数zero 同样为0;第二个参数为机器ID;第三个参数ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep;//获取kernel_entry 函数,函数kernel_entry 并不是uboot 定义的,而是Linux 内核定义的,Linux 内核镜像文件的第一行代码就是函数kernel_entry,而images->ep 保存着Linux内核镜像的起始地址,起始地址保存的正是Linux 内核第一行代码,所以images->ep 就是函数kernel_entry 的地址。
U-boot启动(bootm_run_states之前)
要分析uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。
程序的链接是由链接脚本(链接脚本是编译生成的)来决定的,所以通过链接脚本可以找到程序的入口。lds后缀的文件就是连接脚本文件。
本项目根目录下的u-boot.lds就是要找的。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
入口是_start
相关文件:
vector.s 中断向量表 能找到_start
u-boot.map 地址映射表
u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
|-->_start:(arch/arm/lib/vectors.S)
|-->reset(arch/arm/cpu/armv7/start.S)
|-->save_boot_params(arch/arm/cpu/armv7/start.S)/*将引导参数保存到内存中*/
|-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)
|-->cpu_init_cp15(arch/arm/cpu/armv7/start.S)/*初始化*/
|-->cpu_init_crit(arch/arm/cpu/armv7/start.S)
|-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
|-->_main(arch/arm/lib/crt0.S)
|-->board_init_f_alloc_reserve(common/init/board_init.c)/*为u-boot的gd结构体分配空间*/
|-->board_init_f_init_reserve(common/init/board_init.c) /*将gd结构体清零*/
|-->board_init_f(common/board_f.c)
DDR,即双倍速率同步动态随机存储器,是一种高速的、用于存储数据和程序的内存技术。
gd一般指global data
save_boot_params:保存引导参数到内存
save_boot_params_ret: 位置无关修复
dsb和isb,多处理器时的数据一致性
s_init 第一个C函数
_main
board_init_f_alloc_reserve :留出早期malloc区域和gd区域
board_init_f :
init_run_list:初始化序列函数、序列函数数组、串口IO配置、内核定时器等
这个函数用for循环调用一个函数队列,进行一系列初始化
relocate_code:用汇编实现的uboot重定位,执行此函数前r0赋值为GD_RELOCADDR。这里的重定位跟设备树、ramdisk一样,都是在低地址进行初始化完毕之后复制到高地址去
关键代码:
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
console_init_r(common/console.c) :初始化控制台,也定义了一系列的初始化函数
|-->interrupt_init(arch/arm/lib/interrupts.c) : 初始化中断
|-->relocate_code(arch/arm/lib/relocate.S) /*主要完成镜像拷贝和重定位*/
|-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/
|-->board_init_r(common/board_r.c)/*板级初始化*/
|-->initcall_run_list(include/initcall.h)/*初始化序列函数*/
|-->init_sequence_r[](common/board_f.c)/*序列函数*/
|-->initr_reloc(common/board_r.c) /*设置 gd->flags,标记重定位完成*/
|-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/
|-->serial_init(drivers/serial/serial-uclass.c) /*初始化串口*/
|-->initr_mmc(common/board_r.c) /*初始化emmc*/
|-->mmc_initialize(drivers/mmc/mmc.c)
|-->mmc_do_preinit(drivers/mmc/mmc.c)
|-->mmc_start_init(drivers/mmc/mmc.c)
|-->console_init_r(common/console.c) /*初始化控制台*/
|-->interrupt_init(arch/arm/lib/interrupts.c) /*初始化中断*/
|-->initr_net(common/board_r.c) /*初始化网络设备*/
run_main_loop:
初始化后死循环调用main_loop
main_loop:
初始化,每次检查TFTP这些的更新状态
调用cli_loop
cli_loop:
这个函数也是一个死循环,不断读取命令执行(简单)命令
调用run_command_repeatable -> cli_simple_run_command来执行命令
cli_simple_loop:
run_command_repeadable:
pharse_string_outer:
pharse_stream_outer:
pharse_stream
cli_simple__run_command
cmd_process
head.S进入start_kernel
arch/arm/boot/compress/head.s
arch/arm/kernel/head.s
- 首先保存uboot传过来的machid和args等参数地址
2、关中断、进入SVC模式
3、打开缓存加速解压
4、检查解压后是否与当前镜像相互覆盖
5、清理bss段、解压内核、关闭缓存、启动内核
对于arch/arm/kernel/head.s
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install
#endif
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
start_kernel加载根目录文件系统
output/init/main.c
start_kernel:
各种初始化
最后调用init_rest
init_rest:
启动RCU(Read-Copy-Update)调度器
使用kernel_thread创建两个线程(进程)
- kernel_init : 初始化内核,pid=1,创建进程执行文件系统的init程序
- kthreadd : 负责所有内核线程的调度和管理,pid = 2。
这里很关键的就是kernel_init需要wait_for_completion,也就是等待主线程创建完kthreadd之后才会进行后续的操作。那是因为kthreadd是负责管理内核线程调度的,如果他还没创建,就没有管理内核线程的程序了,会出错。
(使用rcu_read_lock把这两个进程锁在当前CPU)
kernel_init:
这个函数会等kthreadd执行完
调用kernel_init_freeable : 执行一系列初始化
从各个可能的地方找init程序,然后执行该程序
执行根目录文件系统init程序的方式:经过run_init_process调用kernel_execve 类似于 linux的execve
kernel_init_freeable :
这函数做了启动smp、初始化设备和驱动程序、打开标准输入输出、初始化文件系统(挂载根文件系统)等工作。
do_basic_setup:
driver_init:初始化devtmpfs,驱动核心设备将在这里添加设备节点
这个文件系统在挂载根文件系统之后挂载到/dev下面
调用prepare_namespace(),这个函数里面会执行根目录文件系统的挂载
选择调用mount_block_root进行挂载
prepare_namespace:
mount_block_root:
获取文件名,
调用do_mount_root:
分配页面,把数据复制到页面
init_mount:
kern_path:解析路径名
path_mount:进行挂载
处理魔术数、安全检查、标致处理这些
超级块(super_block)、inode
do_new_mount:
用parse_monolithic_mount_data解析挂载数据
do_now_mount_fc:
设置super_block
do_add_mount:将root合并到挂载树
put_path:释放路径资源
put_page:释放页面
mount的输出格式:
<device> on <mount_point> type <filesystem_type> (<mount_options>)
关于数据结构:
文件系统(以此处的EXT4为例)是基于B+树的
挂载树就是一种普通的树形数据结构记录各个挂载点(包含一些成员例如:super_block,inode)
kernel_thread 函数的作用
创建内核线程:kernel_thread 用于在内核中创建新的线程。这些线程运行在内核空间,不具有用户空间的特性。
返回值是 PID:尽管这些线程不是用户空间进程,但它们仍然被分配一个内核级的进程 ID(PID)。这个 PID 用于在内核中标识和管理线程。
为什么可以安全释放页面
挂载根文件系统的过程主要涉及将根文件系统的数据结构(如超级块、目录项等)加载到内核内存中。在挂载过程中:
临时页面用于传递数据:分配的页面只是临时存储传递给init_mount函数的数据。当init_mount函数执行完毕后,这个临时页面的数据已经被处理,不再需要保留。
挂载后的数据存储:挂载成功后,根文件系统的关键数据结构(如超级块、inode等)会被存储在内核管理的内存区域中,而不是之前分配的临时页面中。就是新创建了一个挂载树的节点,之前创建的页面只是作为数据分析传递的载体。
为什么 kernel_thread 的返回值是 PID
在内核中,线程(或任务)被视为一个内核级的进程。内核线程和用户空间进程在管理上有一些相似之处,例如它们都有 PID。虽然 kernel_thread 实际上创建的是内核线程,但这些线程仍然需要一个唯一的标识符(PID)来进行管理和调度。因此,kernel_thread 返回的是这个线程的 PID。
为什么要重定位
内存和设备树重定位的需求主要来源于引导加载过程的灵活性和实用性。以下是一些更详细的解释,说明为什么不直接定位到最终位置,而是需要重定位:
1. 引导过程的阶段性
初始加载位置
引导加载程序(如 U-Boot)在系统上电或复位后首先运行,它通常在系统固件(如 BIOS 或 UEFI)初始化后被加载到内存的特定区域。
在这个阶段,引导加载程序在有限的内存空间中运行,它必须首先加载操作系统内核和其他必要的映像(如设备树、ramdisk)到内存中以便进行引导。
最终位置
操作系统内核有特定的内存要求和布局,通常需要在内存的某个高地址范围内运行。
操作系统的不同部分(内核、ramdisk、设备树等)可能需要在不同的内存区域,以避免相互覆盖和冲突。
2. 内存空间管理
在引导加载程序阶段,内存管理机制较为简单,内存地址的分配可能不够灵活和高效。
一些嵌入式系统和 SoC 平台的内存布局复杂,内存的最终使用方式可能只有在操作系统启动后才能完全确定。
实际示例
以下是一个实际例子说明内存和设备树重定位的必要性:
初始加载
引导加载程序(U-Boot)加载操作系统内核到内存地址 0x8000,设备树加载到 0x100000,ramdisk 加载到 0x200000。
引导加载程序继续运行,并准备启动内核。
重定位
内核启动前,引导加载程序将内核重定位到内存高地址 0x8000000,设备树重定位到 0x8100000,ramdisk 重定位到 0x8200000。
内核从重定位后的地址开始运行,初始化系统并访问设备树和 ramdisk。
启动后
操作系统启动并使用重定位后的内存区域进行系统初始化,确保各部分内存地址不冲突,系统稳定运行。
设备树加载和重定位的详细过程
1. 初始加载
加载过程:设备树最初从存储介质(如闪存、SD 卡)加载到内存中的一个临时位置。这个位置通常是一个低地址区域。
内容一致:初始加载的设备树是完整的,它包含了系统引导和硬件初始化所需的所有信息。
2. 重定位过程
重定位原因:设备树被加载到高地址区域主要是为了避免与内核和其他关键数据冲突。在低地址区域,内核启动后需要用到的内存区域可能与设备树重叠,这会导致内存冲突。
重定位步骤:
内存复制:将设备树从低地址区域复制到高地址区域。整个设备树的数据都会被复制过去。
更新指针:更新设备树指针,使其指向新位置。
在 U-Boot 启动过程中,对设备树(FDT)和 RAMDISK 的重定位通常涉及将它们从一个物理内存地址复制到另一个物理内存地址。这主要是因为在嵌入式系统中,U-Boot 运行时使用的是物理地址,并且在操作系统内核启动之前,虚拟内存管理(如 MMU)尚未启用。因此,重定位的过程确实涉及大量存储单元的复制粘贴操作,而不仅仅是修改虚拟地址。
为什么不等到重定位在高地址之后再做设备树初始化和硬件检测,这样不就可以不需要多加载一次设备树了吗?
分阶段初始化
早期硬件初始化:设备树在低地址加载后,U-Boot 可以立即开始解析和初始化关键的硬件组件。例如,内存控制器、时钟、串口等。这些硬件初始化通常需要在系统的非常早期进行,以确保后续阶段的正常运行。
动态调整:早期的硬件初始化可能需要根据设备树中的信息对内存布局、引导参数等进行动态调整。这个过程中可能会调整高地址区域的内存使用情况。
性能考虑
启动阶段性能影响较小:在系统启动过程中,性能通常不是主要考虑因素。重定位设备树的操作虽然涉及一次内存复制,但这仅仅是启动过程中的一次性操作,对整体系统性能影响可以忽略不计。
代码简洁性和可维护性:通过将设备树加载和重定位分成两个阶段,U-Boot 的启动代码可以保持更简洁和模块化。这样也更容易维护和扩展
这样设计的主要目的是确保启动过程的稳定性和兼容性,同时最大限度地利用系统资源。
设备树
设备树(Device Tree)是一种用于描述硬件平台和系统设备的数据结构,它以一种可读性强的文本形式,将硬件的层次结构、设备的属性和资源配置等信息整合到一个统一的文档中。设备树在操作系统引导启动阶段进行设备初始化时,其描述硬件的信息会被检测到并传递给操作系统,使得操作系统能够正确地识别和管理硬件设备。
硬件描述:设备树详细描述了硬件平台的各个组成部分,包括CPU、内存、总线、外设等,以及它们之间的连接关系和配置信息。
dtb文件包含的设备信息包括但不限于以下几个方面:
CPU信息:包括CPU的数量、类型、主频等。这些信息对于操作系统内核在启动时正确配置和初始化CPU至关重要。
内存信息:包括内存的基地址、大小、类型等。操作系统需要这些信息来管理内存资源,确保应用程序能够正常运行。
外设信息:涵盖了所有连接到平台上的外设设备,如中断控制器、总线和桥、GPIO控制器、串口、I2C设备、SPI设备等。这些信息包括设备的类型、地址、中断号等,有助于操作系统为这些设备匹配合适的驱动程序。
总线信息:描述了系统中的总线结构,包括总线的类型、速度、连接的设备等。这对于操作系统在设备间进行数据传输和通信至关重要。
电源管理信息:包括电源管理控制器的配置信息,以及系统在不同状态下的电源管理策略。这些信息有助于操作系统实现节能和电源管理功能。
时钟信息:描述了系统中的时钟源和时钟分频器等设备的信息。时钟信息对于系统的同步和时序控制非常重要。
别名和特殊节点:dtb文件中还可能包含别名(aliases)节点和特殊节点(如chosen节点),这些节点用于定义节点的别名或传递特定的启动参数给操作系统内核。
在Linux系统中,dtb文件通常由bootloader(如U-Boot)加载到内存中,并将存放地址传递给Linux内核。内核启动后,会从该地址读取dtb文件,解析其中的设备信息,并据此配置和初始化系统硬件。
内核三件套DTB、kernel、ramdisk
什么是 RAMDISK
RAMDISK(内存盘)是一种将部分主存储器(RAM)模拟为磁盘驱动器的技术。它在系统启动时加载,并将一部分预定义的数据(通常是根文件系统或初始化文件系统)放入 RAM 中,以便系统能够快速访问这些数据。
RAMDISK 的作用
快速访问:由于 RAM 的速度远远快于传统的磁盘存储(如 HDD 或 SSD),将重要的启动文件系统加载到 RAM 中可以加快系统启动速度。
临时存储:RAMDISK 可以用作临时存储,提供高性能的 I/O 操作。它适用于需要频繁读写的小文件或缓存数据的场景。
简化初始化过程:在嵌入式系统中,RAMDISK 可以包含基本的文件系统和启动脚本,使系统在初始化阶段能够访问必要的文件和配置。
为什么 U-Boot 需要加载 RAMDISK
在 U-Boot 阶段,加载 RAMDISK 的主要原因有以下几点:
提供初始根文件系统:在内核启动之前,系统需要一个初始的根文件系统,以便执行启动脚本和加载必要的驱动程序。RAMDISK 可以包含这些基本的文件和脚本。
支持无盘启动:对于没有本地存储设备(如硬盘或闪存)的系统,通过网络引导或其他方式获取并加载 RAMDISK,可以实现无盘启动。
提升启动速度:将启动所需的文件加载到 RAM 中,避免了从慢速存储介质读取数据的时间,提升了启动速度。
重定位 RAMDISK
在 U-Boot 阶段,RAMDISK 通常首先加载到一个低地址的临时位置,然后再重定位到高地址。重定位的主要原因包括:
内存布局优化:在启动过程中,低地址区域可能被预留给启动引导加载程序或其他重要数据。将 RAMDISK 重定位到高地址,可以避免与这些数据发生冲突。
内核需求:一些内核期望 RAMDISK 位于特定的高地址区域,以便更方便地访问和使用。通过重定位,可以满足内核的这种需求。
重定位之后 RAMDISK 的作用
根文件系统:在内核启动之后,RAMDISK 通常被挂载为根文件系统或初始根文件系统(initramfs),提供系统启动所需的基本环境。
驱动加载:RAMDISK 中包含的模块和驱动程序可以在内核启动后立即加载,确保系统的硬件初始化和驱动程序配置正确。
引导过程支持:一些系统在启动完成后,会从 RAMDISK 中加载进一步的启动脚本或配置文件,完成最终的系统初始化。
总结
RAMDISK 在系统启动过程中扮演着关键角色,提供了快速访问、临时存储和简化初始化过程的功能。U-Boot 加载 RAMDISK 并进行重定位的过程,旨在优化内存布局和满足内核需求,确保系统能够快速稳定地启动并进入运行状态。
Linux内核从uImage启动到start_kernel函数,并加载根文件系统
了解Linux内核如何从uImage启动到start_kernel函数,并加载根文件系统,涉及多个阶段和组件。这里我将简要概述这些过程,并指出在您的内核文件夹中可以找到哪些相关信息。
1. 从uImage启动
uImage是U-Boot(一种流行的开源引导加载程序)使用的映像格式,它通常包含压缩的Linux内核映像。当系统启动时,U-Boot会加载并执行uImage中的内核映像。
U-Boot:虽然uImage是U-Boot相关的,但U-Boot的代码通常不在Linux内核源代码树中。您可以从U-Boot的官方网站或源代码仓库获取U-Boot的代码。
内核解压:U-Boot会将uImage中的压缩内核映像解压,并跳转到内核的入口点。
2. 内核启动过程
在内核的源代码中,启动过程通常从arch/arm/boot/compressed/head.S(对于ARM架构,其他架构的路径可能不同)开始,这是一个汇编文件,设置了基本的运行环境,并解压缩内核。
解压缩:内核映像被解压缩到内存中。
跳转到start_kernel:解压缩完成后,控制权会转移到C语言编写的start_kernel函数,该函数位于init/main.c中。
3. start_kernel函数
start_kernel是内核初始化过程的核心,它执行各种初始化任务,包括设置页表、初始化内存管理、设备驱动、文件系统等。
内核初始化:start_kernel函数调用一系列初始化函数,这些函数负责设置内核的各个方面。
挂载根文件系统:在初始化过程中,内核会尝试挂载根文件系统。这通常通过解析内核命令行参数(由U-Boot或其他引导加载程序提供)来找到根文件系统的位置,并使用适当的文件系统驱动挂载它。
4. 加载根文件系统
加载根文件系统的具体实现取决于文件系统的类型和存储介质。内核会查找并挂载根文件系统,然后启动/sbin/init或/etc/init(或其他配置的init系统)作为第一个用户空间进程。
文件系统驱动:内核源代码中的fs/目录包含了各种文件系统的驱动,如ext4、NFS等。
挂载过程:挂载过程在内核的VFS(虚拟文件系统)层处理,相关的代码可以在fs/namei.c、fs/super.c等文件中找到。
5. 哪里开始看
启动汇编代码:从arch/您的架构/boot/compressed/下的汇编文件开始。
start_kernel函数:查看init/main.c中的start_kernel函数。
文件系统挂载:查看fs/目录下的相关代码,特别是与您的根文件系统类型相关的代码。
内核命令行:了解如何通过内核命令行参数指定根文件系统的位置和其他启动选项。
希望这些信息能帮助您开始深入了解Linux内核的启动和根文件系统加载过程。如果需要更详细的信息,建议查阅Linux内核的官方文档和源代码注释。
linux移植三巨头:uboot、kernel、rootfs
irq:中断请求
tmpfs(临时文件系统)
tmpfs(临时文件系统)是一种内存文件系统,用于在内存中存储临时文件。它与传统的磁盘文件系统不同,tmpfs 文件系统的数据直接存储在系统的 RAM(随机存取存储器)中,因此具有一些独特的优点和用途。
tmpfs 的特点
- 速度快:因为 tmpfs 文件系统在 RAM 中操作,其读写速度比磁盘文件系统快很多,非常适合需要快速存取的临时数据。
- 自动清除:系统重新启动时,tmpfs 中的数据会被自动清除,这对于存储临时文件和会话数据非常有用。
- 可调整大小:tmpfs 的大小可以动态调整,最大可占用系统可用内存和交换空间之和。
- 不使用磁盘空间:由于数据存储在 RAM 中,tmpfs 不会占用磁盘空间,这对于减少磁盘 I/O 压力和延长磁盘寿命有帮助。
tmpfs 的用途
- 临时文件存储:tmpfs 非常适合用于存储临时文件,如 /tmp 目录。很多 Linux 发行版默认将 /tmp 挂载为 tmpfs。
- 缓存和会话数据:一些应用程序使用 tmpfs 来存储会话数据和缓存,以提高性能。例如,数据库和 Web 服务器可以利用 tmpfs 来存储频繁访问的缓存数据。
- 运行时数据:tmpfs 适合存储系统运行时数据,如 /run 目录中的数据。在某些系统中,/run 目录被挂载为 tmpfs,用于存储运行时信息。
- 编译和构建:开发人员可以使用 tmpfs 作为编译和构建目录,以加快编译速度,特别是在进行大规模的代码编译时。
为什么挂载根文件系统之前,进行设备初始化等任务?
设备管理的准备工作:
根文件系统挂载之前,需要确保内核已经准备好管理设备。这包括创建设备的 sysfs 目录结构,这样才能正确地识别和处理设备。
devices_init 函数创建的设备集合和相关对象允许内核和用户空间可以通过 sysfs 接口访问和管理设备。
初始化顺序:
内核启动的顺序是经过精心设计的,以确保所有依赖关系都得到满足。在挂载根文件系统之前,许多内核子系统(包括设备管理)都需要被初始化。这样,当根文件系统挂载时,所有必要的资源和接口都已准备就绪。
设备初始化是其中一部分,它在挂载根文件系统之前进行,以确保所有设备都能被正确地识别和使用。
内核与用户空间的交互:
当根文件系统挂载后,用户空间进程(例如 init 进程)会启动,这些进程可能需要访问和管理设备。如果在挂载根文件系统之前没有完成设备的初始化,用户空间将无法正确访问这些设备。
devices_init 函数创建的 sysfs 目录结构提供了设备的抽象表示,使得用户空间可以通过 sysfs 文件系统与设备进行交互。
错误处理和资源管理:
在挂载根文件系统之前进行设备初始化,可以在系统进入复杂状态之前捕获并处理初始化错误。如果设备初始化失败,系统可以采取适当的措施(例如重新尝试或输出错误信息)来解决问题。
通过提前初始化,可以确保所有资源都已正确分配和管理,有助于避免后续步骤中的资源冲突和管理问题。
总结来说,根文件系统挂载前进行设备初始化(如 devices_init 函数的操作)是为了确保设备管理子系统准备就绪,确保内核启动过程中所有依赖关系得到满足,并为后续的用户空间进程提供正确的设备访问和管理接口。
输出信息
=> bootm 0x60003000 - 0x60500000
lsj -- bootm_run_states is called.
lsj -- bootm_start is called.
lsj -- bootm_pre_load is called.
lsj -- bootm_find_os is called.
## Booting kernel from Legacy Image at 60003000 ...
Image Name: Linux-5.10.217
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 4930456 Bytes = 4.7 MiB
Load Address: 60003000
Entry Point: 60003000
Verifying Checksum ... OK
## Flattened Device Tree blob at 60500000
Booting using the fdt blob at 0x60500000
Working FDT set to 60500000
lsj -- bootm_load_os is called.
Loading Kernel Image to 60003000
ootm_os_get_boot_func is call. os = 5
lsj -- bootm_process_cmdline_env is called.
lsj -- do_bootm_linux is called.
lsj -- boot_prep_linux is called.
lsj -- image_setup_linux is called.
lsj -- boot_relocate_fdt is called.
Loading Device Tree to 7eb12000, end 7eb1875a ... OK
Working FDT set to 7eb12000
lsj -- image_setup_libfdt is called.
lsj -- boot_selected_os is called.
lsj --
state = 1024
lsj -- do_bootm_linux is called.
lsj -- boot_jump_linux is called.
Starting kernel ...
Booting Linux on physical CPU 0x0
Linux version 5.10.217 (lsj73@7-3-new) (arm-linux-gnueabihf-gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #7 SMP Thu Aug 1 12:10:38 CST 2024
CPU: ARMv7 Processor [410fc090] revision 0 (ARMv7), cr=10c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
OF: fdt: Machine model: V2P-CA9
OF: fdt: Ignoring memory block 0x80000000 - 0x80000004
Memory policy: Data cache writeback
Reserved memory: created DMA memory pool at 0x4c000000, size 8 MiB
OF: reserved mem: initialized node vram@4c000000, compatible id shared-dma-pool
cma: Reserved 16 MiB at 0x7f000000
Zone ranges:
Normal [mem 0x0000000060000000-0x000000007fffffff]
Movable zone start for each node
Early memory node ranges
node 0: [mem 0x0000000060000000-0x000000007fffffff]
Initmem setup node 0 [mem 0x0000000060000000-0x000000007fffffff]
CPU: All CPU(s) started in SVC mode.
percpu: Embedded 19 pages/cpu s46284 r8192 d23348 u77824
Built 1 zonelists, mobility grouping on. Total pages: 130048
Kernel command line: root=/dev/mmcblk0 console=ttyAMA0
printk: log_buf_len individual max cpu contribution: 4096 bytes
printk: log_buf_len total cpu_extra contributions: 12288 bytes
printk: log_buf_len min size: 16384 bytes
printk: log_buf_len: 32768 bytes
printk: early log buf free: 14744(89%)
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes, linear)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes, linear)
mem auto-init: stack:off, heap alloc:off, heap free:off
Memory: 490832K/524288K available (8192K kernel code, 582K rwdata, 1728K rodata, 1024K init, 181K bss, 17072K reserved, 16384K cma-reserved)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1
rcu: Hierarchical RCU implementation.
rcu: RCU event tracing is enabled.
rcu: RCU restricting CPUs from NR_CPUS=8 to nr_cpu_ids=4.
rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies.
rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=4
NR_IRQS: 16, nr_irqs: 16, preallocated irqs: 16
GIC CPU mask not found - kernel will fail to boot.
GIC CPU mask not found - kernel will fail to boot.
sched_clock: 32 bits at 24MHz, resolution 41ns, wraps every 89478484971ns
clocksource: arm,sp804: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275 ns
smp_twd: clock not found -2
Console: colour dummy device 80x30
Calibrating local timer... 93.99MHz.
Calibrating delay loop... 1758.00 BogoMIPS (lpj=8790016)
CPU: Testing write buffer coherency: ok
CPU0: Spectre v2: using BPIALL workaround
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
lsj -- rest_init is called.
lsj -- kernel_init is called.
lsj -- kernel_init_freeable is called.
CPU0: thread -1, cpu 0, socket 0, mpidr 80000000
Setting up static identity map for 0x60100000 - 0x60100060
rcu: Hierarchical SRCU implementation.
smp: Bringing up secondary CPUs ...
smp: Brought up 1 node, 1 CPU
SMP: Total of 1 processors activated (1758.00 BogoMIPS).
CPU: All CPU(s) started in SVC mode.
lsj -- driver_init is called.
devtmpfs: initialized
lsj -- init_irq_proc is called.It will creat irq folder.
VFP support v0.3: implementor 41 architecture 3 part 30 variant 9 rev 0
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
futex hash table entries: 1024 (order: 4, 65536 bytes, linear)
NET: Registered protocol family 16
DMA: preallocated 256 KiB pool for atomic coherent allocations
cpuidle: using governor ladder
hw-breakpoint: debug architecture 0x4 unsupported.
Serial: AMBA PL011 UART driver
SCSI subsystem initialized
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
Advanced Linux Sound Architecture Driver Initialized.
clocksource: Switched to clocksource arm,sp804
NET: Registered protocol family 2
IP idents hash table entries: 8192 (order: 4, 65536 bytes, linear)
tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 6144 bytes, linear)
TCP established hash table entries: 4096 (order: 2, 16384 bytes, linear)
TCP bind hash table entries: 4096 (order: 3, 32768 bytes, linear)
TCP: Hash tables configured (established 4096 bind 4096)
UDP hash table entries: 256 (order: 1, 8192 bytes, linear)
UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)
NET: Registered protocol family 1
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
RPC: Registered tcp NFSv4.1 backchannel transport module.
hw perfevents: enabled with armv7_cortex_a9 PMU driver, 5 counters available
workingset: timestamp_bits=30 max_order=17 bucket_order=0
squashfs: version 4.0 (2009/01/31) Phillip Lougher
jffs2: version 2.2. (NAND) © 2001-2006 Red Hat, Inc.
9p: Installing v9fs 9p2000 file system support
io scheduler mq-deadline registered
io scheduler kyber registered
sii902x 0-0039: supply iovcc not found, using dummy regulator
sii902x 0-0039: supply cvcc12 not found, using dummy regulator
i2c i2c-0: Added multiplexed i2c bus 2
sii902x 0-0060: supply iovcc not found, using dummy regulator
sii902x 0-0060: supply cvcc12 not found, using dummy regulator
physmap-flash 40000000.flash: physmap platform flash device: [mem 0x40000000-0x43ffffff]
40000000.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000
Intel/Sharp Extended Query Table at 0x0031
Using buffer write method
physmap-flash 40000000.flash: physmap platform flash device: [mem 0x44000000-0x47ffffff]
40000000.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000
Intel/Sharp Extended Query Table at 0x0031
Using buffer write method
Concatenating MTD devices:
(0): "40000000.flash"
(1): "40000000.flash"
into device "40000000.flash"
physmap-flash 48000000.psram: physmap platform flash device: [mem 0x48000000-0x49ffffff]
smsc911x 4e000000.ethernet eth0: MAC Address: 52:54:00:12:34:56
isp1760 4f000000.usb: bus width: 32, oc: digital
isp1760 4f000000.usb: NXP ISP1760 USB Host Controller
isp1760 4f000000.usb: new USB bus registered, assigned bus number 1
isp1760 4f000000.usb: Scratch test failed.
isp1760 4f000000.usb: can't setup: -19
isp1760 4f000000.usb: USB bus 1 deregistered
usbcore: registered new interface driver usb-storage
ledtrig-cpu: registered to indicate activity on CPUs
usbcore: registered new interface driver usbhid
usbhid: USB HID core driver
NET: Registered protocol family 17
9pnet: Installing 9P2000 support
oprofile: using arm/armv7-ca9
Registering SWP/SWPB emulation handler
aaci-pl041 10004000.aaci: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 28
aaci-pl041 10004000.aaci: FIFO 512 entries
mmci-pl18x 10005000.mmci: Got CD GPIO
mmci-pl18x 10005000.mmci: Got WP GPIO
10009000.uart: ttyAMA0 at MMIO 0x10009000 (irq = 33, base_baud = 0) is a PL011 rev1
mmci-pl18x 10005000.mmci: mmc0: PL181 manf 41 rev0 at 0x10005000 irq 29,30 (pio)
printk: console [ttyAMA0] enabled
1000a000.uart: ttyAMA1 at MMIO 0x1000a000 (irq = 34, base_baud = 0) is a PL011 rev1
1000b000.uart: ttyAMA2 at MMIO 0x1000b000 (irq = 35, base_baud = 0) is a PL011 rev1
1000c000.uart: ttyAMA3 at MMIO 0x1000c000 (irq = 36, base_baud = 0) is a PL011 rev1
rtc-pl031 10017000.rtc: registered as rtc0
rtc-pl031 10017000.rtc: setting system clock to 2024-08-01T04:12:09 UTC (1722485529)
drm-clcd-pl111 1001f000.clcd: assigned reserved memory node vram@4c000000
drm-clcd-pl111 1001f000.clcd: using device-specific reserved memory
drm-clcd-pl111 1001f000.clcd: core tile graphics present
drm-clcd-pl111 1001f000.clcd: this device will be deactivated
drm-clcd-pl111 1001f000.clcd: Versatile Express init failed - -19
drm-clcd-pl111 10020000.clcd: DVI muxed to daughterboard 1 (core tile) CLCD
drm-clcd-pl111 10020000.clcd: initializing Versatile Express PL111
drm-clcd-pl111 10020000.clcd: found bridge on endpoint 0
drm-clcd-pl111 10020000.clcd: Using non-panel bridge
[drm] Initialized pl111 1.0.0 20170317 for 10020000.clcd on minor 0
input: AT Raw Se 2 keyboard as /devices/platform/bus@40000000/bus@40000000:mtherboard/bus@40000000:motherboard:iofpga@7,00000000/10006000.kmi/serio0/input/input0
mmc0: new SD card at address 4567
mmcblk0: mmc0:4567 QEMU! 32.0 MiB
Console: switching to colour frame buffer device 128x48
drm-clcd-pl111 10020000.clcd: [drm] fb0: pl111drmfb frame buffer device
clk: Disabling unused clocks
ALSA device list:
#0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 28
lsj -- prepare_namespace is called.
iput: ImExPS/2 Generic Explorer Mouse as/devices/platform/bus@40000000/bus@ 40000000:motherboard/bus@40000000:motherboard:ofpga@7,00000000/10007000.kmi/serio1/input/input2
lsj -- mount_block_root is called.
EXT4-fs (mmcblk0): mounting ext3 file system using the ext4 subsystem
EXT4-fs (mmcblk0): mounted filesystem with ordered data mode. Opts: (null)
VFS: Mounted root (ext3 filesystem) readonly on device 179:0.
Freeing unused kernel memory: 1024K
lsj -- run_init_process is called. init_filename:/sbin/init
Run /sbin/init as init process
random: crng init done
mount: mounting debugfs on /sys/kernel/debug failed: No such file or directory
在u-boot项目中,各个功能模块的代码分别存放在不同的文件夹中。以下是各个功能模块的大致对应及其所在的目录:
启动代码(Bootstrap Code):通常存放在arch目录下,根据不同的处理器架构分为不同的子目录。启动代码主要负责初始化硬件、设置堆栈指针、加载内核等操作。
设备驱动(Device Drivers):存放在drivers目录下,包含了各种设备的驱动程序,如串口驱动、网卡驱动、存储设备驱动等。
网络功能(Network):存放在net目录下,包含了网络协议栈的实现,如TCP/IP协议实现、网络设备初始化等。
文件系统(File System):存放在fs目录下,包含了对不同文件系统的支持,如FAT32、EXT4等文件系统的读写操作。
命令行工具(Command Line Tools):存放在cmd目录下,包含了一些用户可以通过命令行操作的工具,如boot、env等命令。
通用功能(Common Utilities):存放在common目录下,包含了一些通用的工具函数和数据结构的实现,如字符串处理、排序算法等。
头文件(Header Files):存放在include目录下,包含了各个模块的头文件,用于在不同模块之间进行函数和数据结构的共享。
除了以上列出的一些关键功能模块外,还有一些其他的模块和功能,如与平台相关的代码(board目录)、设备树描述文件(dts目录)、一些系统配置文件(configs目录)等。
在阅读u-boot项目的代码时,可以根据以上的文件目录结构逐步查看各个功能模块的代码实现,以便更好地理解系统加载的过程和各个模块之间的交互关系。希望这些信息对您有所帮助。