Linux内核加载全流程

简介: 作者:gfree.wind@gmail.com 博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net  本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。
作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
无论是Linux还是Windows,在加电后的第一步都是先运行BIOS(Basic Input/Output System)程序——不知道是不是所以的电脑系统都是如此。BIOS保存在主板上的一个non-volatile(即非易失)存储器,如PROM,EPROM,Flash等。——以前的BIOS一般都是只读的,现代的系统中,允许刷新BIOS程序。它的任务就是简单的初始化和识别系统硬件设备,如CPU,内存,输入/输出设备,外部存储设备等。然后找到bootloader的位置,并加载bootloader,将PC的控制权交给bootloader,完成后面的复杂的系统初始化任务。

但是在系统启动之前,系统如何启动BIOS呢?所以系统启动的过程,也被称为自举。虽然没有“先有鸡还是先有蛋”那么复杂,但是这里也有一个矛盾。PC是这样解决这个问题的。将CPU设计成加电以后,就从一个特殊的固定的地址开始执行指令,那么BIOS的位置就放在这里,也就是存储BIOS的ROM的起始地址就是这个固定的地址,用以保证BIOS程序可以在加电时被直接执行。

这里有两个问题:
1. BIOS的存储器地址如何决定的?
2. 现在多处理器的情况下,BIOS是如何执行的?

下面以Intel CPU为例,简单说明一下流程:
Intel在初始化的时候将CPU分为两类,即BSP(Bootstrap Processor)与APs(Application Processors)。从名字上既可看出两类CPU的作用。在启动的时候,首先由硬件动态选择一个总线上的CPU为BSP,那么剩下的CPU则都为AP。由BSP执行BIOS程序,初始化环境以及APs,然后还是有BSP执行操作系统的初始化代码。 Intel CPU的第一条语句的固定地址为0xFFFF FFF0,然后BIOS的存储器被hard-map到这个内存地址。这样当CPU开始执行时,实际上执行的就是BIOS程序。

由于BIOS的存储器不会太大,所以程序一般不会太复杂,那么不大可能实现加载操作系统的操作,只能完成简单的初始化工作。这时,只能借助于外部存储器了。可是外部存储器的读取是依赖于文件系统的。而BIOS程序既然比较简单,那么是不可能去支持文件系统的,更何况有各种各样的文件系统,不可能去一一支持。这时,还是只能依赖于硬编码,必须定义一个固定的外部存储器的地址——硬盘的第一个扇区的512字节——被称为MBR(Master Boot Record)——为什么是512字节呢?按照我的理解,一般情况下一个扇区都被设置为512字节,而硬盘操作的最小单元即为一个扇区。虽然可以设置更大的扇区,但是作为一个统一的程序来说,使用惯例512是一个不错的选择。

BIOS的最后一项任务就是将MBR读入到内存中,且起始地址固定为0x7c00,然后对MBR的最后两个字节进行验证,必须为0x55和0xAA,以保证这512字节为MBR。验证通过后,则跳转到0x7c00处开始执行。这样MBR就开始执行。——这里,我有两个问题,为什么是7c00和0x55和0xAA呢?目前没有找到当初选择这两个值的解释。我依稀记得选择0x55,0xaa是因为这个值比较特殊,利于校验,但是为什么利于校验却不记得了。

MBR保存了分区表(MBR并不存在于任何一个分区中,而是处于分区之上),以及一个用于装载操作系统启动程序的小程序。MBR首先会确定活动分区,然后使用BIOS将这个活动分区的启动扇区——仍然是第一个扇区512字节,最后跳转到加载该启动扇区的内存地址处。这样就将PC的控制器转移到这个启动扇区的程序手中(即真正的bootloader)。一般来说,这个启动程序也要求被加载到0x7c00这个地址。可是这个地址之前已经加载了MBR,如果再加载这个启动程序,那么必然冲突。所以MBR实际上在开始的时候,先对自己做了relocate,将自己拷贝到另外一个地址,然后从那个地址开始执行,这样就避免了冲突。

下面就进入了真正的bootloader了,对于Linux来说,一般就是LILO和GRUB,下面以最常用的GRUB为例。

GRUB的启动分为三个阶段stage1,stage1.5和stage2,这三个阶段也被分为三个文件(在某些情况下,可以没有stage1和stage1.5)。其中stage1可以嵌入到MBR中,即MBR的头446个字节(后面为分区表64字节,0x55和0xAA两个校验字节),也可以存储在活动分区的第一个扇区512字节, 然后由MBR来加载。所以stage1最多为512字节,如果存储在MBR中,则只能最大为446字节。stage1中保存了stage1.5的地址,并负责加载stage1.5的前512字节。之所以stage1只能加载512字节,是为了遵循MBR的规则。

进入stage1.5,由于只加载了前512字节,所以stage1.5首先要负责把剩余部分代码,由自己加载到内存中。对于stage1.5来说,它可以识别和支持文件系统。可以查看/boot/grub目录下,有多个后缀为stage1.5文件,其前缀即为支持的文件系统,也就是说要支持一个文件系统,就有一个对应的stage1.5文件。至于加载哪个文件,已经硬编码在stage1中。这个文件系统为stage2所在的文件系统。stage2文件是真正保存在文件系统中的。这样通过对应的stage1.5文件,就可以正确加载stage2文件。为什么会有stage1.5这个阶段呢?主要是当stage2不连续或者需要在stage2前,对文件系统做些特殊处理。如果没有这样的需求,完全可以避免stage1.5。

stage2文件为最主要的加载代码,这时由于已经stage1.5已经支持文件系统了,所以stage2可以比较大。stage2来实现GRUB的各种功能,这里就不列举了,感兴趣的同学可以自己查看GRUB的手册。stage2首先需要找到GRUB的配置文件,来决定如何加载操作系统。对于GRUB的配置与本文的主题联系并不紧密,我个人也对其兴趣不大。

GRUB不仅要复杂加载kernel,还要负责加载Initial Ram Disk,又被成为initrd。其目的主要是为了保证一个小体积的内核。initrd为一个简单的文件系统,它包含了一些内核必要的文件和模块。这样,首先将initrd挂载为一个根系统,然后kernel利用这个基本的系统,来检测环境,加载更多的必要的模块。在完成所有的加载后,这时kernel已经完全准备就绪。那么initrd对于kernel来说,已经不需要了。这时,kernel会将initrd从根/上卸载,并挂载上真正的根系统,并执行正常的启动程序。




参考:
1.  Wiki
2. 《Linux内核分析及编程》——倪继利
3. 《Linux内核完全剖析》——赵炯
4. 《Linux内核源代码情景分析》——毛德操  胡希明
5. 《Intel 64 and IA32 Architectures Software Developer‘s Manual》 Volume 3A
6. 《Understanding The Linux Kernel》 Denial P. Bovet & Macro Cesati
7. GUN GRUB Manual

相关文章
|
23天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
61 4
|
27天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
43 6
|
12天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
38 1
|
17天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
25天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
58 9
|
24天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
39 6
|
25天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
39 5
|
25天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
26天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
25天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。