Skip to content

LV005-linux系统组件 ​

一个完整的 linux 系统,通常包含了 Uboot、kernel、**.dtb 以及根文件系统, 它们是系统运行需要的 4 个基础组件。

一、U-Boot ​

1. U-Boot 简介 ​

U-Boot,全称 Universal Boot Loader,是遵循 GPL 条款的开源项目,U-Boot 是 从 FADSROM、8xxROM、PPCBOOT 逐步发展演化而来的。

U-Boot 发展至今,已经可以 实现非常多的功能,在操作系统方面,它不仅支持嵌入式 Linux 系统的引导,还支持 NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, Android 等嵌入式操作系统的引导。在 CPU 架构方面 ,U-Boot 支持 PowerPC、MIPS、x86、ARM、NIOS、XScale 等诸多常用系列的处 理器。

U-Boot 的主要作用是用来启动操作系统内核,它分为两个阶段,即 boot + loader,boot 阶段启动系统,初始化硬件设备,建立内存空间映射图,将系统的 软硬件带到一个合适的状态,loader 阶段将操作系统内核文件加载至内存,之后跳转到内核所在地址运行。

当一个开发板上电的时候,即使是执行非常简单的程序,都需要进行很多初始化的 操作,如初始化时钟、初始化存储控制器、将代码拷贝到运行内存中等操作,大多数 处理器在上电的时候都会有默认的指令执行位置,如 ARM 架构的处理器会从 0x00000000 地址开始读取第一条指令。

一般来说 BootLoader 必须提供系统上电时的初始化代码,在系统上电时初始化相关 环境后,BootLoader 需要引导完整的操作系统,然后将控制器交给操作系统。 简单来说 BootLoader 是一段小程序,它在系统上电时执行,通过这段小程序可以将硬件 设备进行初始化,如 CPU、SDRAM、Flash、串口、网络等,初始化完毕后调用操作系统内核。 另外,某些 BootLoader 可能含有一些高级特性,如校验操作系统镜像,从多个操作系统镜像中选择引导合适的操作系统, 甚至可以添加网络功能,让系统自主从网上寻找合适的镜像并且进行引导等等。

2. 两种模式 ​

在开发的时候,我们可能需要利用一些命令去操作 BootLoader,让 BootLoader 完成一 些事情,我们可以称这种模式为 下载模式,比如从内存中的 ELF 镜像启动,从内存启动 应用程序镜像,使用 BOOTP/TFTP 协议通过网络启动镜像,打印控制台设备和信息,打印 有关文件系统的信息,列出目录中的文件等等。

当然,下载模式是对于开发人员才有意义,而对于用户则不需要这些操作,直接可以启动 操作系统运行,这种直接启动操作系统的模式可以称之为 启动模式。

U-Boot 的 功能十分强大,它可以同时支持下载模式与启动模式,并且可以切换模式,它可以在系统上电 时默认等待 N 秒(N 可以随意设置),在这 N 秒内没有命 令(如按下任意按键、串口发送任意数据等)去操作 BootLoader 时,U-Boot 将启动默认 的操作系统内核(如 Linux)。当有命令操作 BootLoader 时,系统将进入下载模式,在这 个模式下,BootLoader 将不会继续启动操作系统内核,而是由开发者去指定 BootLoader 的工作,如通过串口下 载操作系统镜像,通过网络启动镜像等操作,如下图所示。

image-20241030225020716

而当 N 秒内没有命令去操作 BootLoader 时,BootLoader 将启动默认的操作系统,并将 控制块交给操作系统,显然,在产品发布时 BootLoader 是工作在启动模式下的,这种模 式是不需要用户通过 BootLoader 的命令去控制 BootLoader,而是直接启动操作系统即可,具体见下图:

image-20241030225115944

3. 两个阶段 ​

U-Boot 启动内核的过程可以分为两个阶段,两个阶段的功能如下:

  • 第一阶段

(1)硬件设备初始化。

(2)加载 U-Boot 第二阶段代码到 RAM 空间。

(3)设置好栈。

(4)跳转到第二阶段代码入口。

  • 第二阶段

(1)初始化本阶段使用的硬件设备。

(2)检测系统内存映射。

(3)将内核从存储区域(Flash、SD Card、eMMC)读取到 RAM 中。

(4)为内核设置启动参数。

(5)调用内核。

二、Kernel ​

1. Linux 内核 ​

Kernel 即是 Linux 内核,Linux 内核采用宏内核架构,即 Linux 大部分功能都 会在内核中实现,如进程管理、内存管理、设备管理、文件管理以及网络管 理等功能,它们是运行在内核空间中(也可以称之为特权模式下运行)。

除此之外还有一种与宏内核相反的内核架构——微内核,它仅仅是将内核的基本功 能放入内核中 ,如进程管理、进程调度等,而其他的设备管理、文件管理等功能都放在内核 空间之外(即运行在非特权模式下),这种微内核的架构有很优越的扩展性,它 将系统分为各个小的功能模块,把设计难度大大降低。而宏内核的设计架构则没有 非常好的扩展性。

但 Linux 在发展的过程中,很早就引入了内核模块(Loadable Kernel Module,LKM)这一机制,弥补了这一不足之处,内核模块全称为动态可 加载内核模块,就是在内核运行时可以动态加载一组目标代码来实现某些特定的 功能,在这过程中不需要重新编译内核就可以实现动态扩展。模块是具有独立功能 的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核 的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组 函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功 能,关于内核模块我们后续会学习。

2. Linux 系统组成 ​

Linux 内核主要由 5 部分组成,分别为:进程管理子系统,内存管理子系统,文件 子系统,网络子系统,设备子系统:

从上图可以看出,整个内核是由 5 部分组 成,并且由系统调用层(系统调用子系统)进行统一管理,应用层通过系统调用层的函数接口与内核进 行交互,用户应用程序执行的地方是用户空间,用户空间之下则是 内核空间,Linux 内核正是位于内核空间中。

2.1 进程管理子系统 ​

进程管理的核心就是进程的调度。在 Linux 内核中,进程调 度的单元是进程,进程调度控制系统中的多个进程对 CPU 的访问,从宏观上看,系统 中的进程在 CPU 中是并发执行的。此外内核通过系统调用提供了应用程序 编程接口,例如:创建新进程(fork,exec),结束进程(kill, exit),并且提供了控制进程,同步进程和进程间通信的接口。

2.1.1 进程与程序有什么区别? ​
  • 程序:存放在磁盘上的一系列代码和数据的可执行映像,是一个静止的实体。
  • 进程: 是一个执行中的程序,它是动态的实体。
2.1.2 进程的四要素 ​

(1)有一段程序供其执行,这段程序不一定是某个进程所专有,可以与其他进程共用。

(2)有进程专有的系统堆栈空间(也可以称之为内核堆栈空间)。

(3)在内核中有一个 task_struct 数据结构,即进程控制块。有了这个数据结构,进程才能被内核调度器识别并参与内核调度,除此之外它还记录着 进程所占有的各项资源。

(4)除上述的专有的系统堆栈空间外,进程还需要有独立的用户堆栈空间,这 就是 mm_struct 数据结构,该数据结构位于 task_struct 结构中,字段名称为 mm。

每个进程都有自己的堆栈,内核在创建一个新的进程时,在创建进程控制块 task_struct 的同时,也为进程创建自己堆栈。一个进程有 2 个堆栈,即用户堆栈和系统堆栈;用户堆栈的空间指向用户地址空间,内核堆栈的空间指 向内核地址空间。当进程在用户态运行时,CPU 堆栈指针寄存器指向用户堆栈地址,使用用户堆栈,当进程运行在内核态时,CPU 堆栈指针寄存器指向的是内核栈空间地址,使用的是内核栈。

在 Linux 中进程与线程的区分也不是十分严格,一般在讲到进程时常常也包括了线程,以上的进程四个要素是必要条件,缺了其中任何一条都不能称其为”进程”,如果完全没有用户空间,即 mm_struct 为 NULL,那么可以称它为内核线程,如果有共享的用户空间,那么可以称之为用户线程,具体如下图所示。

2.1.3 进程调度 ​

进程调度是进程管理子系统中最重要的一个功能,它决定了哪个进程 可以运行,什么时候运行,什么时候停止,它实际上就是一个管理进程 之间使用 CPU 资源的管理程序,因此 Linux 内核必须提供一种方法方法,让各个 进程之间尽可能公平地共享 CPU 资源,并且还需要考虑各个进程的优先级。因此进程 调度器的设计就要解决如何有 效地为各个进程分配其使用 的 CPU 资源 时间,同时又要达到良好的用户体验效果,此外 调度器还需要解决一些互相冲突的情况,例如既要实现实时任务的最小化响应时间, 又 要最大限度地提高 CPU 的总体利用率等。

其实在 Linux2.6 版本之后,进程调度器使用新的进程调 度算法——Completely Fair Scheduler,简称 CFS,即完全公平调度算法,该算 法会按所需分配的计算能力,向系统中每个进程提供最大的公正性,或者从另 外一个角度上说,它试图确保没有进程被亏待。经过多年来的测试,完全公平调度算法在 高负载情况下依然有极其出色的性能,它负责将 CPU 资源,分配给正在执行的进 程,目标在于最大化程式互动效能,最小化整体 CPU 的运用,这个算法使用红黑树 来实现,算法效率为 O(log(n))。

2.1.4 进程状态 ​

每个进程都有状态属性,在整个 Linux 中运行着多个进程,某些进程可能 处于运行态,某些进程可能处于就绪态,某些进程可能处于睡眠态或者是深度 睡眠态,还有一些进程可能处于暂停态或者僵死态等,这些进程的状态都是 动态变化的并且是由进程调度器决定的,具体见下图:

  • 就绪态 R (TASK_RUNNING):就绪态也被称作可执行状态

只有在该状态的进程才能被允许参与调度器调度并且使用 CPU 资源,而同一时 刻可能有多个进程处于就绪态,这些进程的 task_struct 结构(进程控制块)被放入 对应 CPU 的可执行队列中(一个进程最多只能出现在一个 CPU 的可执行队列中)。进程调度器的任务就是从各个 CPU 的可执行队列中分别选择一个进程在该 CPU 上运行。

  • 运行态 R (TASK_RUNNING):此时进程正在使用 CPU 资源。

很多操作系统的书将正在 CPU 上执行的进程定义为 RUNNING 状态、而将可执行但是尚未被调度执行的进程定义为 READY 状态,这两种状态在 linux 下统一为 TASK_RUNNING 状态。

  • 暂停态 T (TASK_STOPPED or TASK_TRACED)

向进程发送一个 SIGSTOP 信号,它就会因响应该信号而进入 TASK_STOPPED 状态,向进程发送一个 SIGCONT 信号,可以让 其从 TASK_STOPPED 状态恢复到 TASK_RUNNING 状态,当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在 gdb 中对被跟踪的进程下一个断点,进程在断点处停下来的时 候就处于 TASK_TRACED 状态。

  • 可中断睡眠态 S (TASK_INTERRUPTIBLE)

对应上图的睡眠态,处于这个状 态的进程因为等待某些事件的发生而进入睡眠状态(比如等待 socket 连接、等待信号量等)。当这些事件发生的时候进程将被唤醒,如产生一个硬件中断、释放进程正在等待的系统资源或是传 递一个信号都可以是唤醒进程的 条件。一般情况下系统的大多数进程都是处于这个状态的,在终端可以通过 ps 命令查看系统进程状态。

  • 不可中断睡眠状态 D (TASK_UNINTERRUPTIBLE)

不可中断睡眠状态又被称作深度睡眠态,它与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠状态的进程不能改变它的状态,也就是说它不响应信号的唤醒,这种状态一般由 IO 引起,同步 IO 在做读或写操作 时(比如进程对某些硬件设备 进行操作,等待磁盘 IO,等待网络 IO),此时 CPU 不能做其它事情,只能处于这种状态 进行等待,这样一来就能保证进程执行期间不被外部信号打断,但这种状态时间非常 短,很难在终端中将它捕获到。

  • 僵死态 Z (TASK_DEAD - EXIT_ZOMBIE)

僵死态又被称之为僵尸态或者退出态,进程在退出的过程中,除了 task_struct 数据结构(以及少数资源)以外,进程所占有的资源将被系统回收,此时进程没法继续运行了,但它还有 task_struct 数据结构,所以被称为僵死态。之所以保留 task_struct 数据结构,是因为 task_struct 中保存了进程的退出码、以及一些其他的信息,而其父进程很可能会关心这些信息,因此会暂时被保留下来。

可以使用 ps –aux 命令查看当前系统中进程的状态,因为当前系统觉得大部分进程是处于可中断睡眠态的,如运行态、不可中断睡眠态、僵死态等进程状态是很难被终端捕获到的,因此下面的图是经过处理的,能更直观显示一些任务状态:

2.2 内存管理子系统 ​

内存管理的主要作用是保证系统安全访问内存区域,且绝大部 分 CPU 都是支持内存管理单元的(Memory Management Unit,MMU),那么在 Linux 中,内存管理子系统就负责管理每个进程完成从虚拟 内存到物理内存的转换,以及系统可用内存空间,此外还顺便 提一点,Linux 的 2.6 版本后引入了对无 MMU CPU 的支持。

内存管理的硬件按照分页方式管理内存,分页就是把系统的物 理内存按照相同大小等分,每个内存分片称作内存页,通常内存页大小 是 4KB。内存管理子系统要管理的不仅是 4KB 缓冲区,它提供了对 4KB 缓冲区 的抽象,例如 slab 分配器。这种内存管理模式使用 4KB 缓冲区为基数,然 后从中分配管理结构,并跟踪内存页使用情况 ,系统中哪些内存页是满的,哪些内存页面为空,哪些内存页没有完全 使用。这样一来,系统就支持动态调整内存使用情况。除此之外,Linux 还 支持内存交换,因为 Linux 中使用的是虚拟内存,当物理内存不足时,内存管理子系 统会将内存暂时移到磁盘中,在物理内存充裕时又将内存页从磁盘移到物理内存中,这就是 内存交换。

一般而言,在 32 位的系统上,每个进程都最大享有 4GB 的内存空间,因为由于 32 位的 系统寻址空间只有 4G,当然这是虚拟内存,0~3GB 是属于用户内存空间,3~4GB 是属于 系统内存空间,实际上用户的程序几乎使用不完那么大的用户空间,一旦超出将无法正常运行,当然系统内存空间与用户内存空间是可以调整的。

2.3 文件管理子系统 ​

在 Linux 系统中有一个重要的概念:一切皆文件,它把一切资源都看 作是文件,包括硬件设备,通常称为设备文件。Linux 的文件管理子系 统主要实现了虚拟文件系统(Virtual File System,VFS),虚拟文件系统屏蔽了各种硬件上的差异以及具体实现的细节,为所有的硬件设备提供统一的接口,这样子也就实现了设备无关性,同时文 件管理系统还为应用层提供统一的 API 接口。

总的来说,Linux 的文件系统体系结构是对一个对复杂系统进行了抽象化,通 过使用一组通用的 API 函数,Linux 可以在许多种存储设备上支持多种文件系 统,如 NTFS、EXT2、EXT3、EXT4 、FAT 等等;而用户空间包含一些应用程序和 GNUC 库(glibc),它们使用的 API 接口是由系统调用层提供(如打开、读、写和关闭等),其框架如下图:

2.4 网络子系统 ​

在 Linux 内核中,与网络相关的代码被 Linux 独立开,形成一个相对独立的子系统,称为网络子系统,网络子系统是一个层次化的结构,可分为以下几个层次:

(1)Socket 层(也可以称之为协议无关层):Linux 在发展过程中,采 用 BSD Socket API 作为自己的网络相关的 API 接口。同时, Linux 的目标又要能 支持各种不同的协议族,而且这些协议族都可以使用 BSD Socket API 作为应用层的编程接口,这样一来将 Socket 层抽象出来就能屏蔽不同协议族之间的差异,不会对应用层的使用产生影响。

(2)协议层:Linux 网络子系统功能上相当完备,它不仅支持 INET 协议 族(也就是通常所说的 TCP/IP 协议族),而且还支持其它很多种协议族,如 INET6、DECnet,ROSE,NETBEUI 等,对于 INET 、 INET6 协议族来说,又会进一步将协议族划分为传输层和网络层以及 链路层等,此处就不深入了。

(3)网络设备层:网络设备其实是设备驱动层的内容了,它抽象了网卡 数据结构,在一个系统中可能存在多种网卡,屏蔽了不同硬件上的差 异,这一层提供了一组通用函数供底层网络设备驱动程序使用。

网络子系统框架如下图所示:

2.5 设备子系统 ​

设备子系统又被称之为设备驱动,在日常生活中,嵌入式中使用的设备多种多样,如 LCD、摄像头、USB、音频等都是属于设备,且设备的厂 商不同其驱动程序也是不同的,但是对于 Linux 来说,不可能去将每个设备都包含到内核,它只能抽象去描述某种设备。

从前面的几个子系统的框图中可以了解到,系统调用层是 Linux 内核与应用程序之间的接口,而设备驱动则是 Linux 内核与硬件之间的接口,设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作(打开、读、写和关闭)。设备驱动程序是内核的一部分,它主要完成以下的功能:

(1)对设备初始化和释放

(2)把数据从内核传送到硬件和从硬件读取数据

(3)读取应用程序传送给设备文件的数据和回送应用程序请求的数据

(4)检测和处理设备出现的错误

设备驱动框架如下图所示

其实 Linux 在发展的时候就根据设备的共性将设备分层 3 大类,分别为:

  • 字符设备

字符设备是以字节为单位传输的 IO 设备,可以提供连续的数据流,应用程 序可以顺序读取,通常不支持随机存取。这种字符传输的效率通常是比较低 的,如鼠标、键盘、串口等都是字符设备,也是一种比较常见的设备。

  • 块设备

块设备是以块为单位进行传输的设备,应用程序可以随机访问块设备中的 数据,程序可以指定读取数据的位置。我们的磁盘就是一种常见的块设备,应用 程序可以寻址磁盘上的任何位置,并在这个位置读取数据。不过需要注意的是,块设 备读取的数据只能以块为单位的倍数进行(通常是 512Byte 的整数倍),而不能与字符设备一样以 字节为单位读取,因此通常来说块设备的传输速度是比较高的。

  • 网络设备

网络设备,它其实就是我们上一章网络子系统中描述的网络 设备层,它其实也是一个抽象,统一描述了不同的网卡设备,如 WIFI、以 太网等。因为网络设备存在协议栈(协议族),它涉及了网络层协议,所 以 Linux 将网络设备单独分层一类设备,网络设备的传输速率通常是很高的。

三、dtb ​

1. 设备树简介 ​

在 2011 年之前,ARM Linux 中存在大量描述芯片平台以及板级差 异的垃圾代码,它们位于 kernel/arch/arm/plat-xxx 目录 和 kernel/arch/arm/mach- xxx 目录下,用代码描述硬件,如注册 platform 设备,声明设备的 resource 等,但这些代码对于 Linux 内核来说都是垃圾,因此 Linux 创始人 Linus Torvalds 在 2011 年 3 月份向 Linux 社区发送一封邮件,他表达了对 ARM 架构配置方式的不满, 并宣称 "Gaah. Guys, this whole ARM thing is a f*cking pain in the ass"。 这引起了广泛的讨论和反思。 他提出 ARM 架构平台应该参考其他平台如 PowerPC 的设备树机(Device Tree)制描述硬件,这样子在 Linux 内核中就不再需要进行大量的冗余编码,许多硬件及芯片平台的细节可以通过设备树传递给 Linux 内核。

其实设备树是一种 描述硬件的数据结构,它把这些硬件设备的信 息,用一个非 C 语言的脚本语言来描述,而这个脚本文件,就是传说中的 Device Tree(设备树),设备树包括设备树源码(Device Tree Source,DTS)文件、设 备树编译工具(Device Tree Compiler,DTC)与二进制格式设备树(Device Tree Blob,DTB)。DTS 包含的头 文件格式为 DTSI,DTS 文件是一种人类可以看懂的编码格式,由节点(Node)和属 性(Property)组成,节点中又可能会包含子节点,而属性则是可以简单理解为 成对出现的名称与值,如下面的示例。

c
node1 {
    a-string-property = “A string”;
    a-string-list-property = “first string”, “second string”;
    a-byte-data-property = [0x01 0x23 0x34 0x56];
    child-node1 {
        first-child-property;
        second-child-property = <1>;
        a-string-property = “Hello, world”;
    };
};

因为 Uboot 和 Linux 不能直接识别 DTS 文件,它们只能识别二进制 文件,所以需要把 DTS 文件编译成 DTB 文件,而 DTC 就是将设备树源 码文件(.dts / .dtsi)编译成二进制格式设备树文件(.dtb)的编译工具,它位于 Linux 内 核源码的 scripts/dtc 目录下,在 Linux 配置中使能了设备树情况下,内核会 自动编译对应的设备树,当然用户也可以单独编译设备树。

DTB 可以被内核与 BootLoader 识别解析,通常在制作 NAND Flash、SD Card 启动 镜像时,通常会为 DTB 文件留下一部分存储区域以存储 DTB,在 BootLoader 启 动内核时,会先读取 DTB 到内存。

Linux 的设备树中,可描述的硬件信息包括以下几类:

(1)CPU 的数量和类别

(2)内存基地址和大小

(3)总线和桥

(4)外设连接

(5)中断控制器和中断使用情况

(6)GPIO 控制器和 GPIO 使用情况

(7)Clock 控制器和 Clock 使用情况

2. DTS 的加载过程 ​

使用 DTS 描述了硬件的信息,那么怎么让内核识别这些信息呢?这就涉及到 DTS 的加载过程:当我们要使用 Device Tree 时,我们首先要了解自己的硬件配置和系统运行参数,并把这些信息 组织成 Device Tree Source 文件,然后通过 DTC 将这些适合人类阅读的 DTS/DTSI 文件编译变成适合机器处理的 DTB 文件。

在编译生成 DTB 文件后,还需要将它加载到 Linux 内核中,让内核来识 别这些信息。在系统启动时,BootLoader 会将保存在 flash 中的 DTB 拷贝到内存,并把 DTB 在内存中的起始地址传递给内核。

3. 设备树插件 ​

Linux4.4 以后引入了动态设备树(Dynamic DeviceTree) 。 设备树插件(Device Tree Overlay)是一种用于设备树(Device Tree) 的扩展机制。 设备树是一种用于描述硬件设备的数据结构,广泛应用于嵌入式系统中, 特别是基于 Linux 内核的系统中。

设备树插件允许在运行时动态修改设备树的内容, 以便添加, 修改或删除设备节点和属性。它提供了一种灵活的方式来配置和管理硬件设备, 而无需重新编译整个设备树。 通过使用设备树插件, 开发人员可以在不重新启动系统的情况下对硬件进行配置更改。

设备树插件(Dynamic DeviceTree) 通常以一种文本格式定义, 称为设备树源文件(Device Tree Source, DTS) 。 DTS 文件描述了设备树的结构和属性, 包括设备节点, 寄存器地址, 中断信息等。 设备树插件可以通过加载和解析设备树文件, 并将其合并到现有的设备树中, 从而实现对设备树的动态修改。

使用设备树插件, 可以实现一些常见的配置变化, 比如添加外部设备, 禁用不需要的设备,修改设备属性等。 这对于嵌入式系统的开发和调试非常有用, 特别是面对多种硬件配置或需要频繁更改硬件配置的情况下。

四、根文件系统 ​

此处学习的文件系统是 根文件系统(rootfs),它是 linux 在初始化时 加载的第一个文件系统,而非我们在上面文件管理子系统小节所说 的文件系统,这两个是不同的概念。

根文件系统包括根目录和真实文件系统,根文 件系统之所以在前面加一个”根”,说明它是加载其它文件系统的”根”,如果没有这个”根”的 话,其它的文件系统也就没有办法进行加载的。因为它包含系统引导和使 其他文件系统得以挂载(mount)所必要的文件。根文件系统包含 Linux 启动时 所必须的目录和关键性的文件,例如 Linux 启动时必要的初始化文件,它在 init 目录下,此 外根文件系统中还包括了许多的应用程序 bin 目录等,任何包括这些 Linu x 系统启动所必须的文件都可以称为根文件系统。

在 Linux 内核启动的初始阶段,首先内核会初始化一个基于内存的文件系统,如 initramfs,initrd 等,然后以只读的方式去加载根文件系统(load rootfs),读取并 且运行/sbin/init 初始化文件,根据/etc/inittab 配置文件完成系统的初始化工作(提示:/sbin/init 是 一个二进制可执行文件,为系统的初始化程序,而/etc/inittab 是它的配置文件),在初始化的过程中,还会以读写的方式重新挂载根文件系统,在系统启动后,根文件系统就可用于存储数据了,存在根文件系统是 Linux 启动时的必要条件。

在 Windows 的目录结构中,可能会包括 C: 或者 D: 盘,它们一般就被称之为特定逻辑磁 盘的根目录。从文件系统的层面来说,每一个分区都包含了一个根目录,即 Windows 系统中可 以存在多个根目录。

但是,在 Linux 系统中的文件系统目录结构与 Windows 上有较大的不同。系统中只有 一个根目录,路径是”/”,而其它的分区只是挂载在根目录中的一个文件夹,如”/home”和”/sys”等,这 里的”/”就是 Linux 中的根目录,因此 Linux 中只存在一个根目录,在 Linux 启动后,根目录就位于真实的文件系统中。

Linux 中的文件系统多种多样,同时在 Linux 中一切皆是文件,普通文件、目录、字符 设备、块设备、套接字等都以文件方式被抽象化;且它们需要向上层提供统一的操作接口。虚拟文 件系统 VFS 就是 Linux 内核中的一个软件层,向上给用户空间程序提供文件系统操作接口;向下 允许不同的文件系统共存,所以,所有实际文件系统都必须实现 VFS 的结构封装。 因为无论是访问设备还是需要通过文件系统来访问它的挂载点。

参考资料

doc.embedfire.com/linux/imx6/base/zh/latest/building_image/image_composition.html