☞【干货】嵌入式驱动工程师学习路线 ☞【干货】Linux嵌入式知识点-思维导图-免费获取 ☞【就业】一个可以写到简历的基于Linux物联网综合项目 ☞【就业】简历模版
引导加载程序(常见的有GRUB、LILO、syslinux等)是连接硬件与内核的关键桥梁,核心作用是完成内核启动前的准备工作,为内核运行搭建基础环境。
加载内核映像:引导加载程序会从硬盘、U盘等存储设备中,将经过压缩的内核映像(如vmlinuz)读取并加载至内存。这类内核映像通常采用gzip等压缩格式封装,以二进制文件形式存在。
加载initrd/initramfs:若系统配置了初始RAM盘(initrd)或初始RAM文件系统(initramfs),引导加载程序会同步将这些文件加载到内存中。它们将为内核启动初期提供必要的临时文件系统支持。
在压缩内核映像的头部,嵌入了一段小型解压缩程序,其唯一职责是完成内核主体部分的解压操作。
执行内核解压:当内核映像被加载到内存后,这段内置的解压缩程序会自动运行,将压缩的内核主体解压到内存中预先分配的合适地址。
移交控制权至解压后内核:解压操作完成后,系统控制权会立即转移到解压后内核代码的入口地址,后续将由完整的内核代码主导启动流程。
解压后的内核代码会从一个固定入口点开始执行,该入口点与具体的硬件架构强相关。例如在x86架构下,通常为startup_32(32位系统)或startup_64(64位系统)函数。
架构相关初始化:内核会根据当前硬件架构,执行一系列针对性的初始化操作,包括设置CPU运行模式、初始化内存分页机制、构建基础内存映射表等,确保硬件环境适配内核运行。
初始化内核堆栈:堆栈是函数调用与数据临时存储的基础,内核会在此阶段完成自身堆栈的创建与配置,为后续的函数调用和数据操作提供支撑。
调用start_kernel函数:完成基础硬件环境初始化后,内核会触发start_kernel函数的调用,这一函数是内核整体初始化流程的核心枢纽。
start_kernel函数定义于init/main.c文件中,是内核初始化的核心函数,几乎所有关键的内核子系统初始化都在此完成。
初始化控制台:配置内核的打印输出机制,确保后续内核运行状态、错误信息等能够正常输出展示。
初始化内存管理子系统:构建初始的内存管理结构,初始化内存分配器,为内核及后续进程的内存使用提供管理能力。
硬件设备检测与初始化:遍历系统中的硬件设备,加载对应的驱动程序,完成硬件设备的激活与配置。
启动中断处理机制:配置中断控制器,注册中断处理函数,使内核能够响应并处理硬件设备发出的中断请求。
初始化内核调度器:初始化进程调度相关的数据结构与算法,为后续进程的调度执行做好准备。
加载初始进程:创建并启动系统中第一个用户空间进程(通常为/sbin/init),完成从内核态到用户态的过渡。
init进程是用户空间的首个进程,被誉为“用户空间进程之祖”,核心职责是完成系统后续的初始化工作,构建完整的用户空间运行环境。
init进程自身初始化:执行系统预设的初始化脚本,配置系统环境变量、设置系统时区、初始化网络参数等基础配置。
启动用户空间服务:按照系统配置,启动各类系统服务与守护进程(如网络服务、日志服务、图形界面服务等),最终完成整个系统的启动流程。
Linux内核映像默认采用压缩格式封装,这一设计是基于存储、加载效率及实用性等多方面的考量,具体原因如下:
节省存储资源:压缩后的内核映像体积大幅减小,能有效降低在存储设备上的占用空间。这对于存储资源有限的嵌入式设备、轻量型系统,以及需要频繁分发、更新内核的场景尤为重要。
提升加载效率:尽管压缩文件需要额外的解压步骤,但较小的文件体积意味着引导加载程序从存储设备读取数据到内存的时间显著缩短。现代处理器的解压速度极快,解压耗时远小于读取更大体积未压缩文件的耗时,整体上能加快启动流程。
优化传输效率:在通过网络传输内核映像(如远程OTA更新)时,压缩格式能大幅减少带宽占用,提升传输速度,降低更新过程中的网络开销。
便于管理分发:体积更小的内核映像更易于在光盘、U盘等移动介质上存储和分发,同时也简化了备份、迁移等管理操作。
适配标准流程:采用压缩格式已成为Linux内核的标准实现,主流引导加载程序(如GRUB)均已原生支持压缩内核映像的识别与处理,确保了启动流程的兼容性与可靠性。
vmlinux是内核编译链接阶段生成的完整内核映像文件,包含了内核的全部代码、数据结构及未加载的模块,是未经任何压缩和特殊处理的原始内核文件,通常位于内核源码根目录下,其核心特性如下:
未压缩特性:作为原始编译产物,vmlinux保留了内核的完整体积,未经过任何压缩算法处理。
ELF格式封装:vmlinux默认采用ELF(可执行与可链接格式),这是一种通用的二进制文件格式,支持存储可执行代码、目标文件、共享库等,便于编译链接与调试。
包含调试符号:文件中嵌入了完整的调试符号表与符号信息,这些信息是内核调试、性能分析及问题定位的关键依据。
无固定后缀:vmlinux通常不添加文件后缀,但可通过文件头信息直接识别其ELF格式属性。
vmlinux是内核编译流程中链接阶段的产物,其简化生成步骤如下:
编译源码文件:内核源码中的所有.c文件(C语言代码)和.S文件(汇编代码)会被分别编译为目标文件(.o文件)。
链接目标文件:链接器(如ld)会将所有生成的目标文件,结合内核链接脚本,最终链接生成一个完整的内核映像,即vmlinux。
链接命令举例:
ld -o vmlinux [object files] [linker scripts]
得到原始的vmlinux文件后,内核会通过后续处理生成可用于启动的压缩内核映像,主要包括vmlinuz和bzImage两种格式,生成流程如下:
生成vmlinuz:通过gzip等压缩工具对vmlinux进行压缩,生成压缩后的内核映像vmlinuz。 压缩命令示例:gzip -c vmlinux > vmlinuz
生成bzImage:针对x86架构,内核提供了bzImage(大内核映像)格式,通过make bzImage命令可将vmlinux压缩并封装为bzImage格式,解决了早期zImage格式的内存地址限制问题。
除了vmlinuz和bzImage,Linux内核还有多种适配不同场景的压缩映像格式,各自具备特定的应用场景:
zImage:早期的内核映像格式,适用于体积较小的内核,受限于传统x86架构的低内存地址空间,现已逐渐被bzImage替代。
uImage:专为U-Boot引导加载程序设计的内核映像格式,广泛应用于嵌入式系统。其包含U-Boot专属的头部信息,支持多种压缩算法,可被U-Boot直接识别并加载。
在Android系统中,内核映像格式通常为zImage或Image.gz,具体取决于设备所采用的引导加载程序及硬件配置:
zImage:在早期Android设备中较为常见,引导加载程序可直接加载zImage格式的内核映像,完成解压后启动内核。
Image.gz:本质上是经过gzip压缩的内核映像,与通用Linux系统中的vmlinuz功能类似,仅命名上有所差异。引导加载程序加载后需先解压,再启动内核。
内核从加载到完成解压的核心流程可概括为:引导加载程序(Bootloader)→ 跳转到内核入口点 → 执行解压缩代码 → 调用解压函数 → 解压内核映像 → 返回解压结果 → 跳转到内核启动流程(Kernel Startup)

引导加载程序的核心任务之一是将内核映像及相关文件加载到内存,并完成控制权的移交,具体步骤如下:
加载核心文件:引导加载程序(如GRUB)会读取配置文件(通常为grub.cfg),根据配置信息加载压缩内核映像(vmlinuz等)及可选的initrd/initramfs文件至内存。
设置内核参数:通过命令行方式向内核传递启动参数(如根文件系统路径、调试模式开关等),为内核启动提供定制化配置。
移交控制权:完成加载与参数设置后,引导加载程序将系统控制权转移到内核映像的入口点,结束自身工作。
CPU重置后,会自动执行内存地址0xfffffff0处的指令,该指令为跳转指令,直接指向BIOS的入口地址。
BIOS启动后,会执行硬件自检(POST)和基础硬件初始化,随后根据预设的启动顺序(如硬盘、U盘、光盘)选择启动设备。
BIOS将控制权移交至启动设备的主引导扇区(MBR,占用512字节),由主引导扇区代码继续引导流程。
主引导扇区代码执行后,由于其体积有限(仅512字节),会进一步跳转到更复杂的引导加载程序核心映像(如GRUB的core image)。
核心映像启动后,会加载引导加载程序的扩展模块和配置文件(grub.cfg),解析其中的内核加载配置。
根据grub.cfg的配置,引导加载程序将压缩内核映像(vmlinuz)和initrd/initramfs文件加载到指定内存地址。
加载完成后,引导加载程序将控制权移交至内核映像的入口点,完成从BIOS到内核的控制权转移。
arch/x86/boot/header.S:内核启动的汇编代码入口,定义了内核映像的入口点地址及基础启动环境配置。
arch/x86/boot/compressed/head_64.S、arch/x86/boot/compressed/misc.c:内核解压相关的核心代码,分别负责解压前的环境准备和具体解压逻辑实现。
arch/x86/kernel/head_64.S、arch/x86/kernel/head.c:解压后内核的启动代码,负责完成内核启动的后续初始化。
引导加载程序根据配置加载内核映像(vmlinuz)至内存后,会直接跳转到内核映像的入口点地址,启动内核代码的执行流程。
ENTRY(startup_32)// 设置 CPU 状态和内存环境jmp decompress_kernel // 跳转到解压缩代码
ENTRY(decompress_kernel)// 设置硬件环境// 调用解压缩函数入口方法jmp decompress_kernel_method
在arch/x86/boot/compressed/misc.c文件中,decompress_kernel函数会根据内核映像的压缩格式,选择对应的解压算法(如gzip的inflate函数),并执行内核映像的解压操作。
void decompress_kernel(...) {// 选择解压算法// 调用相应的解压函数decompress_method(); // 调用特定的解压算法,如 inflate()}
jmp *%eax// 跳转到解压后内核的入口地址从技术实现角度,跳转入口点与控制权转移的核心逻辑一致,均通过修改程序计数器(PC,32位架构)或指令指针(IP,64位架构)的寄存器值实现。
程序计数器/指令指针是CPU的专用寄存器,用于存储当前正在执行指令的内存地址。CPU执行完当前指令后,会自动将该寄存器值更新为下一条指令的地址,从而维持程序的连续执行。当需要跳转到新的入口点时,只需将新入口地址写入该寄存器,CPU就会从新地址开始执行指令,实现执行流程的转移,即控制权的移交。
两者的表述侧重不同:“跳转到入口点”更强调执行流程的起始位置切换,“控制权转移”更侧重执行权限从一个代码上下文(如引导加载程序)到另一个上下文(如内核)的移交。在内核启动流程中,两者通常可互换使用。
加载压缩内核映像(vmlinuz)时,引导加载程序通常会同步加载initrd(初始RAM盘)或initramfs(初始RAM文件系统)。它们的核心作用是为内核启动初期提供临时根文件系统,协助内核完成最终根文件系统的挂载与初始化。
核心特性:
驱动支持:内核启动初期可能需要加载特定硬件驱动(如SCSI磁盘驱动、RAID控制器驱动)才能访问根文件系统,而这些驱动可能未内置在内核中。initrd/initramfs提供了临时文件系统,内核可从中加载所需驱动。
复杂根文件系统适配:对于LVM(逻辑卷管理)、RAID、加密文件系统等复杂存储配置,内核需在挂载实际根文件系统前执行额外初始化(如解密、逻辑卷激活),这些操作可通过initrd/initramfs中的脚本完成。
通用内核适配:Linux发行版通常提供通用内核以适配多种硬件配置,通过initrd/initramfs可在启动时动态加载对应硬件的驱动模块,无需为每种硬件单独编译内核。
加载与使用流程:
引导加载程序将内核映像与initrd/initramfs文件一同加载到内存,并将控制权移交内核。
内核启动后,识别并挂载initrd/initramfs作为临时根文件系统。
内核从临时根文件系统加载必要的驱动模块,执行初始化脚本。
初始化脚本完成硬件配置后,挂载实际的根文件系统(如/dev/sda1)。
切换至实际根文件系统,释放initrd/initramfs占用的内存资源。
start_kernel是Linux内核初始化流程的核心函数,被誉为“内核的起点”。它负责统筹内核各子系统、驱动程序及关键组件的初始化,最终完成从内核态到用户态的过渡,将控制权移交至用户空间进程。
初始化内核基础环境:包括内存管理、进程调度等核心子系统的基础配置。
初始化各功能子系统:涵盖文件系统、网络子系统、设备驱动框架等关键组件的初始化。
启动首个用户进程:创建并启动系统中第一个用户空间进程(init进程),完成内核态到用户态的切换。
asmlinkage __visible void __initstart_kernel(void){char *command_line;extern const struct kernel_param __start___param[], __stop___param[];/* ... 其他初始化代码 ... *//* 设置页表和内存管理 */paging_init();mem_init();kmem_cache_init();/* 设备和驱动程序初始化 */driver_init();init_irq_proc();softirq_init();time_init();console_init();/* 文件系统初始化 */vfs_caches_init_early();mnt_init();init_rootfs();init_mount_tree();/* 初始化进程 */pid_cache_init();proc_caches_init();/* 启动 init 进程 */rest_init();/* ... 其他初始化代码 ... *//* 调用内核参数解析函数 */kernel_param_init(karg_strings, num_args);/* ... 其他初始化代码 ... *//* 永远不会返回 */cpu_idle();}
内核进程是由内核直接创建和调度的线程,运行于内核态(特权态),主要用于处理内核内部的后台任务。与用户进程不同,内核进程不直接与用户空间交互,其生命周期完全由内核管理。
运行空间:位于内核地址空间,可直接访问内核所有数据结构与硬件资源。
权限等级:拥有最高特权级,无需通过系统调用即可操作内核资源。
核心作用:执行内核内部任务,如磁盘I/O调度、网络数据包处理、中断后续处理等。
创建方式:通过内核提供的kernel_thread函数创建。
注意:内核进程是独立的任务实体,与后续提到的0号、1号、2号进程无直接衍生关系。
用户进程是在用户空间中执行的进程,用户通过编写和执行应用程序来创建用户进程。用户进程通过系统调用与内核交互,进行资源分配、文件操作、网络通信等。
运行空间:用户进程运行在用户态,受限于用户空间的权限,不能直接访问硬件和内核数据结构。
权限:内核线程运行在内核态,具有更高的权限,能够直接操作内核资源。
Linux系统中有三个核心进程,它们构成了整个系统进程树的根基,各自承担不同的核心职责:
0号进程:内核进程,又称swapper进程、idle进程(空闲进程),是系统中第一个进程,运行于内核态,负责在系统无其他可运行进程时占用CPU,避免CPU闲置。
1号进程:用户进程,又称init进程,是系统中第一个用户空间进程,由0号进程衍生而来,负责系统初始化及管理用户空间所有进程。
2号进程:内核进程,又称kthreadd进程,由0号进程衍生而来,负责创建和管理其他内核线程,是内核线程的“父进程”。
0号进程是Linux系统启动过程中由内核初始化代码直接创建的首个进程,其task_struct结构体的comm字段为“swapper”。它的核心职责是维护系统空闲时的CPU运行状态,当系统中无其他可调度进程时,调度器会选择0号进程执行,其本质是一个无限循环的空闲任务。
0号进程的创建过程发生在内核引导的汇编阶段(如x86架构的arch/x86/kernel/head.S文件),该阶段完成CPU与内存的基础配置后,跳转到start_kernel函数,后续通过rest_init函数衍生出1号和2号进程。
1号进程和2号进程都是在rest_init函数中创建的
1号进程通过kernel_init创建
2号进程通过kthreadd创建
rest_init函数-初始化入口static noinline void __ref rest_init(void){// 通知RCU(Read-Copy Update)子系统,调度器即将开始。这是确保RCU在调度器开始运行前正确初始化的关键步骤。rcu_scheduler_starting();// 创建pid=1的1号进程pid = kernel_thread(kernel_init, NULL, CLONE_FS);/** 处理1号进程相关代码 **/// 创建pid=2的2号进程pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);/** 处理2号进程相关代码 **//** 其他初始化代码 **/}
4.2.1、kernel_thread函数int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags){return do_fork(flags | CLONE_VM | CLONE_UNTRACED, (unsigned long)fn,(unsigned long)arg, NULL, NULL, 0);}
kernel_init函数 - 初始化1号进程(init)kernel_init是1号进程的入口函数,核心职责是完成用户空间的初始化,启动系统首个用户空间进程(/sbin/init等),代码实现如下:
static int __refkernel_init(void *unused){/** 其他初始化代码 **/// 启动用户空间进程if (ramdisk_execute_command) {run_init_process(ramdisk_execute_command);} else if (execute_command) {run_init_process(execute_command);} else {run_init_process("/sbin/init");}return 0;}
init进程会读取系统预设的初始化配置文件,执行对应的初始化脚本:传统系统通常读取/etc/inittab配置文件及/etc/init.d/目录下的启动脚本;采用systemd的现代系统则读取/lib/systemd/system/、/etc/systemd/system/目录下的单元文件(unit files)。
这些初始化操作包括:设置系统环境变量、挂载各类文件系统(如/tmp、/proc、/sys)、配置网络参数、启动日志服务等基础系统服务。
完成基础系统服务启动后,init进程会根据系统配置,启动用户交互界面相关服务,具体流程如下:
启动图形登录管理器:若系统配置为图形界面模式,init进程会启动图形登录管理器(如GDM、LightDM、SDDM)。登录管理器负责提供图形化登录界面,接收用户输入的用户名和密码,完成身份验证。
启动桌面环境:用户身份验证通过后,登录管理器会启动用户配置的桌面环境(如GNOME、KDE、Xfce)。桌面环境提供图形化的用户交互界面,包括桌面图标、任务栏、文件管理器等组件,用户可通过桌面环境运行应用程序、管理系统资源。
采用systemd作为init进程的现代Linux系统,图形界面启动流程如下:
systemd启动后,读取自身配置文件,初始化核心系统服务(如系统时钟、内存管理、设备管理)。
根据系统默认目标(target)配置,启动graphical.target(图形界面目标),该目标包含了启动图形界面所需的所有依赖服务。
systemd通过单元文件启动图形登录管理器服务(如gdm.service)。
登录管理器启动后,在显示器上展示图形登录界面,等待用户登录。
用户输入用户名和密码并通过验证后,登录管理器启动用户会话,加载用户配置的桌面环境。
桌面环境启动完成后,用户进入图形化交互界面,系统启动流程正式结束。
为清晰梳理内核启动的完整链路,以下用简化流程图概括核心流程:
电脑通电 → 加载BIOS(硬件自检与初始化) → 引导加载程序(Bootloader)启动 → 加载压缩内核映像(vmlinuz)与initrd/initramfs → 解压vmlinuz → 执行start_kernel函数(内核初始化) → 启动init进程(1号进程) → 执行系统初始化脚本 → 启动用户服务与图形界面 → 进入登录界面 → 系统启动完成

作者:潇湘居士吃火锅
原文:https://www.cnblogs.com/anywherego/p/18217546
end
一口Linux
关注,回复【1024】海量Linux资料赠送
精彩文章合集
文章推荐