一、嵌入式启动流程
1,运行厂家固化在芯片内部irom的代码
初始化基本的硬件设备时钟等
判断启动方式(emmc,SD卡,USB)
把Bootloader 第一阶段的代码加载到IRAM(芯片内运行内存)(不做内存初始化)
2,运行Bootloader第一阶段代码
SPL:u-boot-spl.bin
初始化整个系统的时钟,以及必要的配置;
初始化板子内存
把完整的Bootloader搬移到DDR中
分两部分是因为芯片内存小,需要分一小部分初始化基本的配置以及内存,以在内存加载完整的Bootloader。
3,在内存中加载Bootloader第二阶段代码
初始化具体的硬件配置(根据板子定义,如网卡,串口,USB等);
4、加载操作系统 5、挂载文件系统 6、启动应用程序
二、U-boot和Linux 内核之间的参数传递
当开发板上电后,Bootloader启动,初始化对应的配置,而Linux内核是被动被u-boot拉起来的,不知道相应的硬件配置信息(内存大小、根文件系统位置、启动参数等),所以需要对Linux内核传递参数才能正确启动硬件系统。
| 寄存器 | |
| r0 | 规定为0 |
| r1 | 开发板的ID |
| r2 | 其他参数在内存中开始地址 |
u_boot和Linux之间在传递参数的时候,一般按照双方约定的格式传递,比如1.struct parm__struct结构体,2.tag列表,3.设备树.
U-Boot 环境变量 bootcmd 与 bootargs 详解
bootcmd 决定 U-Boot 自己如何加载并启动内核,bootargs 决定 U-Boot 启动内核时给内核传入哪些启动参数。
1. bootcmd:U-Boot 自身的启动命令
bootcmd 是 U-Boot 在启动倒计时结束后自动执行的一组命令,相当于 U-Boot 的开机自启动脚本。它通常负责从网络、EMMC 或 SD 卡把内核镜像和设备树加载到内存,再通过 bootm 命令启动内核。
bootcmd=tftp 41000000 uImage;tftp 42000000 exynos4412-fs4412.dtb;bootm 41000000 - 420000002. bootargs:传给 Linux 内核的启动参数
bootargs 是 U-Boot 告诉内核根文件系统在哪里、控制台用哪个串口、网络如何配置等。
bootargs=root=/dev/nfs nfsroot=192.168.2.128:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3,nolock rw console=ttySAC2,115200 init=/linuxrc ip=192.168.2.152:192.168.2.128:192.168.2.1:255.255.255.0::eth0:off| 参数 | 含义 |
|---|---|
| root=/dev/nfs | 根文件系统通过 NFS 网络文件系统挂载 |
| nfsroot=192.168.2.128:/home/hqyj/fs4412/rootfs | NFS 服务器 IP 为 192.168.2.128,共享目录为 /home/hqyj/fs4412/rootfs |
| proto=tcp,nfsvers=3,nolock | NFS 使用 TCP 协议、版本 3,并且不加文件锁 |
| rw | 根文件系统以可读写方式挂载 |
| console=ttySAC2,115200 | 内核控制台使用串口 ttySAC2,波特率为 115200 |
| init=/linuxrc | 内核启动后运行的第一个用户态进程是 /linuxrc |
| ip=192.168.2.152:192.168.2.128:192.168.2.1:255.255.255.0::eth0:off | 静态配置网络:开发板 IP 192.168.2.152,服务器 192.168.2.128,网关 192.168.2.1,掩码 255.255.255.0,网卡 eth0,off 表示不自动配置 |
三、Linux 内核编译驱动原理讲解(Kconfig Makefile .config menuconfig之间的关系)
1. Kconfig:内核编译前,menuconfig 会递归读取各级 Kconfig 生成配置菜单。
Kconfig生效需要在上一级目录source包含(source "drivers/char/test/Kconfig")
2. .config:保存最终配置结果
make menuconfig 进行配置后,系统会把用户的选择写入内核源码根目录下的 .config 文件中。
3. Makefile:根据 .config 决定编译内容
obj-$(CONFIG_MY_DRIVER) += my_driver.o4. menuconfig:Linux内核提供的可视化菜单配置,显示内核支持的功能
四、u-boot启动流程
U-Boot 的启动流程整体上可以分为两个阶段:第一阶段主要完成 CPU 级初始化和内存初始化;第二阶段完成板级外设初始化、环境变量加载以及内核引导。
由于芯片内部 RAM 容量有限,通常先由固化在 IROM 中的代码加载 SPL,由 SPL 初始化时钟和 DDR,再把完整的 U-Boot 搬移到 DDR 中运行。
board_init_f 阶段准备重定位环境,relocate_code 进行代码重定位,board_init_r 阶段完成网卡、串口、USB 等外设初始化,最后进入 main_loop 主循环。
进入 main_loop 后,U-Boot 会等待倒计时;若超时,则自动执行 bootcmd 中配置的命令,把内核镜像和设备树加载到内存,并按照 bootargs 把根文件系统、控制台和网络参数传递给 Linux 内核,最终通过 bootm 命令完成内核启动。
所以简述U-Boot 启动流程:一是 SPL 让内存可用,加载u-boot到内存;二是 U-Boot 完成自身重定位;三是 bootcmd 与 bootargs 协作,把控制权交给 Linux 内核。
五、Linux启动流程分析
Linux 内核被 U-Boot 加载到内存后,启动流程大致分为以下几个阶段:
- 内核自解压与入口执行:内核镜像被加载到内存后,首先执行入口代码,完成内核压缩镜像的解压,为后续运行准备环境。
- 汇编阶段初始化:关闭中断、初始化页表、使能 MMU,并根据启动参数完成基本的硬件相关初始化。
- C 语言阶段初始化:进入
start_kernel后,依次完成进程管理、内存管理、文件系统、设备驱动等子系统初始化。 - 挂载根文件系统:根据 U-Boot 传入的
bootargs参数,挂载真实的根文件系统。 - 启动用户态 init 进程:内核启动第一个用户态进程,通常为
init或linuxrc,由它继续完成系统服务和应用程序的启动。
六、busybox init启动流程
挂载根文件系统: 让 Linux 能访问根目录下的程序、配置文件和系统工具。
启动 init 进程: 启动用户空间的第一个进程(PID 1),负责组织系统初始化。
读取
/etc/inittab: 根据配置确定系统启动时启动脚本程序(id编号:权限等级):动作:进程
::sysinit:/etc/init.d/rcS # 系统初始化最先执行rcS脚本(最关键) ::askfirst:-/bin/sh # 串口控制台拉起一个shell交互终端 ::restart:/sbin/init # init异常重启的处理 ::ctrlaltdel:/sbin/reboot # 按键重启系统执行
/etc/init.d/rcS: 执行系统初始化脚本,完成基础环境#!/bin/sh /bin/mount -a # 根据 /etc/fstab 挂载所有文件系统 mkdir /dev/pts # 创建伪终端设备目录 mount -t devpts devpts /dev/pts # 挂载 devpts 文件系统(支持 SSH 等需要终端的程序) echo /sbin/mdev>/proc/sys/kernel/hotplug # 告诉内核:有热插拔事件时调用 mdev mdev -s # 扫描 /sys 创建所有设备节点(/dev/ttySAC2 等)挂载
/etc/fstab指定的文件系统: 挂载各种文件系统等,为系统提供内核信息接口和运行空间。/proc/sys/tmp/proc:内核运行信息虚拟文件系统
/sys:硬件设备树、驱动信息文件系统
/tmp:临时目录(内存文件系统)
/dev:设备节点目录启动 Shell 程序: 提供命令行交互环境,让用户能够输入命令、运行程序。
执行
/etc/profile: 设置 、、命令提示符等环境变量,准备 Shell 的使用环境。PATHHOME
库是什么
库(Library)是一组预先编译好的、可复用的代码集合,把常用的函数、类或数据封装起来,供多个程序共享调用。
动态库和静态库的区别
| 对比项 | 静态库 | 动态库 |
|---|---|---|
| 常见后缀 | .a(Linux)、.lib(Windows) | .so(Linux)、.dll(Windows) |
| 链接时机 | 编译链接时,把库代码直接复制进可执行文件 | 程序运行时才加载,可执行文件只记录库的引用 |
| 可执行文件大小 | 较大,因为包含了库代码 | 较小,库代码不打包进程序 |
| 部署与更新 | 更新库需要重新编译整个程序 | 替换库文件即可生效,无需重新编译程序 |
| 内存占用 | 每个程序各持有一份库代码,内存占用高 | 多个程序可共享同一份库,内存占用低 |
| 依赖风险 | 无运行时依赖,程序独立性强 | 运行时依赖库文件,缺失或版本不匹配会导致程序无法启动 |
如何链接库
-L:指定库的搜索路径(去哪里找.a/.so)
-lxxx:指定链接libxxx.so或者libxxx.a gcc 优先找动态库.so;想强制静态链接加‑static。
操作系统如何知道加载库的路径
编译时通过
-Wl,-rpath指定的路径(优先级最高);环境变量
LD_LIBRARY_PATH指定的路径;/etc/ld.so.cache中缓存的路径,该缓存由/etc/ld.so.conf配置生成,修改配置后需运行ldconfig更新缓存;系统默认目录
/lib和/usr/lib
程序被杀死后,如何自动重启
1.init进程启动的过程中,读取/etc/inittab文件,并且将这个文件指定的程序启动起来。
::respawn:(程序杀死会被拉起)
2.shell 脚本监控(/etc/init.d/rcS)或者/etc/profile文件添加