嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验

  这其实已经是在完成了 Hi3531D 的大部分软件开发任务后的复盘笔记。中途都是照着文档说明草草地学、草草地用,跳过了很多很多细节和原理性的东西。嵌入式 Linux 这个知识体系还是有亿点大的,想一口吃成胖子很难,但是其余的不说,基本的了解还是该有的。
  仅对接触过的知识点进行复盘和整理,实际上还有非常多的知识是在 Hi3531D 的项目中没有涉及到的,蛋四没有关西,做的项目多了,掌握的知识体系自然就会趋于完整。


目录

  • 一、初始开发环境搭建
    • 1.1 典型嵌入式 Linux 系统设置
    • 1.2 交叉开发环境
  • 二、嵌入式初体验
    • 2.1 BIOS、引导装入程序和 U-Boot
    • 2.2 启动目标板
    • 2.3 启动内核
    • 2.4 内核初始化概述
    • 2.5 虚拟内存与运行上下文
  • 三、U-Boot
    • 3.1 U-Boot 编译
    • 3.2 Nor 和 Nand
    • 3.3 网络操作
      • 3.3.1 TFTP 服务器
      • 3.3.2 NFS 服务器
  • 四、Linux 内核
    • 4.1 内核版本与源码库
    • 4.2 Linux 内核构造
    • 4.3 Linux 内核编译
      • 4.3.1 内核补丁
      • 4.3.2 .config 文件
      • 4.3.3 menuconfig
      • 4.3.4 vmlinux、zImage 和 uImage
  • 五、RootFs
    • 5.1 BusyBox 工具
      • 5.1.1 inittab
      • 5.1.2 fstab
      • 5.1.3 init.d/rcS
    • 5.2 文件系统概念
    • 5.3 文件系统类型(基于 Flash)
  • 六、杂

一、初始开发环境搭建

  本节复盘及知识点补充内容对应博客 Hi3531D调试手记(一):Linux开发环境搭建 第一节(以太网服务器搭建在后面补充)。

1.1 典型嵌入式 Linux 系统设置

  下图是一种典型的嵌入式 Linux 操作系统开发环境的系统设置。首先,系统包含一个主机开发系统,用来运行 Linux 发行版。Linux 发行版的作用是提供开发环境和开发平台,因此系统类型和版本号没有硬性需求,只是需要在主机开发系统中需要包含开发工具和实用工具,以及由嵌入式 Linux 发行版提供的目标文件。嵌入式 Linux 目标板通过 RS-232 串行通信电缆与主机开发系统相连,同时主机开发系统也通过以太网与本地以太网集线器连接。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第1张图片

1.2 交叉开发环境

  在为嵌入式系统开发应用程序或设备驱动程序之前,需要一套工具(编译器、二进制工具等)来为我们的目标系统生成可执行的二进制代码。考虑一下 PC 上的简单应用程序,例如比较典型的 “Hello World” 例子。当在 PC 上编完源码之后,需要调用编译器来生成可运行的二进制代码。这套二进制代码最终将运行在编译它的主机上,这称为本机编译,它的意思是使用本机的编译器进行编译,产生出的代码也运行于本机之上。
  在交叉开发环境中开发软件,其实就是要让本机运行的编译器产生出不兼容于本机的可执行二进制格式(也就是在主机开发系统上生成,在目标板上运行)。交叉开发工具存在主要原因是资源的限制(内存大小、CPU 性能),在嵌入式系统中做本地化的软件开发和编译是不现实的,也是不可能的。

PS:这里仅仅补充了交叉开发工具的概念。实际上还有 GDB、DDD 等许多非常实用的工具在 Linux 应用开发的调试中起着非常重要的作用。但是 GDB 这个东西确实不是两三句话就能说得清的,而且第一次项目也没用到 GDB 调试 看以后时机成熟了再单独开坑记录一下。

二、嵌入式初体验

  本节复盘及知识点补充内容对应博客 Hi3531D调试手记(Ex):U-Boot使用随笔 中的 U-Boot 信息记录(内核的启动与初始化为额外补充)。

2.1 BIOS、引导装入程序和 U-Boot

  在桌面计算机上电后,一种叫做 BIOS(Basic Input/Output Software) 的软件系统会立即接管对系统处理器的控制,完成系统的硬件检查与初始化任务,然后将操作系统从 PC 的硬盘驱动器中读出并加载。通常,BIOS 保存在闪存(Flash)内,以便之后能对 BIOS 进行必要的升级工作。
  引导装入程序(bootloader)就相当于是存在于典型嵌入式系统中的 BIOS,因此 bootloader 在嵌入式系统中的地位不言而喻。开发人员的一部分开发工作,就是要开发出针对特定开发板的引导装入程序。幸运的是,由于市面上可以购买到现成的开发板,如果是以此为基础来开发嵌入式系统的相关应用程序,就可以直接从店家手中获取到对应于开发板的固件程序(包括 bootloader 在内)。
  U-Boot 就是 bootloader 中使用最为广泛的一种,咱的 Hi3531D 使用的 bootloader 就是 U-Boot。

PS:上电即运行,有没有感觉 bootloader 就是个裸机程序?自信点,把感觉去掉,引导装入程序听起来是有点高大上的样子,但是通俗来说就是个用来做初始化工作裸机程序。

2.2 启动目标板

  以 Hi3531D 的启动为例,U-Boot 在完成系统启动准备工作的过程中会通过串行通信端口回传一些信息:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第2张图片
  可以看出在 Hi3531D 上电后,U-Boot 执行了一系列底层硬件的初始化工作,包括配置串行通信端口,之后通过串口输出了上图中的信息,比如 U-Boot 的版本信息以及开发板特性的文本信息。这些信息都是 U-Boot 的开发者在源代码中加入的。最后一行 “Hit any key to stop autoboot: 0” 是执行自动启动的倒计时显示。如果没有在计数自然归零之前主动打断,则会在那之后进入启动内核的阶段。
  如果打断了自启动倒计时,U-Boot 就会进入控制台模式,等待用户输入指令,此时可以看到命令提示符 hisilicon #。实际上控制台模式已经属于开发人员拉取出来的配置接口了,一般用户可以在 U-Boot 控制台模式下直接输入启动指令来启动 linux(其实自启动也是自动调用内核启动指令),也可以预先配置好一些启动参数,设置好网络环境,利用 tftp 预先将 linux 内核镜像与根文件系统下载到开发板上实现独立启动,又或者利用 nfs 直接将主机开发系统上的 linux 内核镜像与根文件系统挂载到开发板上,当然要是在这里就展开的话那就扯远了。

PS:U-Boot 控制台模式下提供了不少可用的命令。然而咱在使用 Hi3531D 的时候必须在 U-Boot 阶段配置的东西其实是比较有限的,主要工作也就是烧录内核镜像、根文件系统以及设定启动方式是独立启动还是网络挂载启动了,就连 U-Boot 本身也是基本不用进行版本更新的。因为是主做应用开发,这一块确实没有花太多时间,除非是硬件上的改动,否则即便真的需要修改开发环境的配置,那也多是在 linux 内核镜像那边配置。

2.3 启动内核

  自启动倒计时结束后,U-Boot 剩下的唯一工作就是加载并启动 Linux 操作系统内核。在 Loading Kernel Image … OK 信息之后的单独一行 OK 即宣告 U-Boot 使命的结束,Linux 操作系统内核从 U-Boot 手中接管所有的内存以及系统资源,此后的启动信息均由 Linux 内核产生。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第3张图片

2.4 内核初始化概述

  当 Linux 内核开始执行以后,它将向控制台输出大量状态信息,以便用户了解系统的启动过程。在 Hi3531D 开发板的启动过程中,Linux 内核至少输出了超过 100 行的信息,直到显示系统登陆提示符。这里只贴出 Linux 内核显示的最后几行信息,因为当前还不适合对内核初始化的细节进行详细讨论(内核的启动信息分析的内容足够再单独开一篇博客,这里牵扯到的东西太多了,先不说自己有没有接触到这方面的知识点,即便接触到了一篇博客肯定也写不完)。
在这里插入图片描述
  这里只需要注意,在控制台终端显示登陆提示符之前,Linux 操作系统内核挂载了根文件系统。根文件系统通常包含很多应用程序、系统库以及组成 GNU/Linux 的各种实用工具。
  Linux 挂载根文件系统的方法有好几种,其中最常见的方式就是直接在系统的硬盘空间中挂载根文件系统,就像常见的桌面 Linux 工作站那样;以及使用 NFS 服务挂载主机开发系统上存放的根文件系统。事实上,NFS 的挂载功能是调试的利器 —— 它使得开发人员彻底避免了微调内核或是根文件系统的配置后就必须重新烧录进开发板运行才能验证修改结果的麻烦,即是说,在主机开发系统上更改了内核或是根文件系统的配置,开发板上启动时加载的镜像文件会通过 NFS 直接同步,无需烧录。

2.5 虚拟内存与运行上下文

  在前一节的演示图中的最后一行输出信息:Freeing unused kernel memory: 232K (c0596000 - c05d0000) 释放了未使用的内核虚拟内存(虚拟内存的概念参考 如何理解虚拟内存 - 知乎)。或许现在还看不出什么,但是补充完接下来的知识点,或许这一行信息所表现出来的意义就不一样了。

  • Linux 内核依靠 MMU(Memory Management Unit,内存管理单元)的优势实现了支持虚拟内核的操作系统。虚拟内存技术能够带来的最大好处是,可以更加有效地利用物理内存,并给用户提供远远大于实际物理内存的更大的可用空间。另一个好处是,内核可以为分配给某个任务或进程的地址空间设置访问权限,以阻止一个进程由于误操作而非法访问其他进程或整个操作系统的地址和资源。
  • 在 Linux 启动运行的最初阶段,必须要做的一项工作,就是要配置好处理器的 MMU 并初始化与之配套的数据结构,以支持虚拟地址到物理地址的转换。当这一步完成之后,内核就运行在它自己的虚拟地址空间中了。内核开发人员规定的内核虚拟地址默认为 0xC0000000(可以改,但一般不改)。如果我们看一下内核符号表,就会发现所有的内核符号都以 0xC0xxxxxx 来编址。由此可见,当内核在内核空间执行代码时,处理器的 IP 指针都将指向这个地址范围中。
  • 在 Linux 中,根据指定线程的运行环境,可以把它分为两个独立的运行上下文。当线程完全运行在内核空间时,则称之为内核空间上下文,而应用程序则运行在用户空间上下文。一个用户空间的进程仅能访问属于它的地址空间,而要访问文件或者设备 I/O 等特权资源则要通过系统调用。
    假设一个应用程序打开了一个文件并且发出了读取请求。读文件的函数开始于 C 库中的 read() 函数,它处于用户空间,接着它将向内核发送读请求。这个读文件的操作将引起用户空间上下文与内核空间上下文的切换,以保证完成文件数据的读请求。在内核内部,这个读文件的请求最终将产生对数据所在磁盘扇区的物理访问。
    嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第4张图片

  现在应该不难看出,本节开头提及的最后一行输出信息预示着系统从内核态到用户态的切换:内核完成了所有内部初始化工作并且挂载了根文件系统后,释放掉了空闲的内核虚拟内存,开始运行在用户空间上下文中。此后再有输出的信息,就是由“其他东西”产生的了。

PS:这里的“其他东西”在后面还会提及,到时候一并说了,就不在这里拆解说明。

三、U-Boot

  本节复盘及知识点补充内容对应博客 Hi3531D调试手记(二):U-Boot、Linux内核与RootFS镜像制作及烧录 中的 U-Boot 相关内容以及博客 Hi3531D调试手记(一):Linux开发环境搭建 、博客 Hi3531D调试手记(Ex):U-Boot使用随笔 中的网络环境配置。

3.1 U-Boot 编译

  在编译 U-Boot 之前,首先会对 U-Boot 进行一些预先的配置,使用命令:

make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3531d_xxx_config

在这里插入图片描述
  实际上这两种参数是有对应的配置文件的,就存放在 ./include/configs 子目录中:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第5张图片
  打开 hi3531d.h,能够发现里面主要都是两种形式的配置变量:使用 CONFIG_XXX 形式的宏选择配置项和使用 CFG_XXX 形式的宏选择配置项。一般来说,CONFIG_XXX 形式的配置选项是用户可以根据具体需求自行配置的;CFG_XXX 形式的配置选项通常和硬件相关,需要理解处理器和硬件平台的内部细节才能做出合理的配置,不清楚的话不去动它就完了。下图就是预设 U-Boot 控制台模式的环境变量的部分配置内容:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第6张图片
  通过使用这种机制,可以配置 U-Boot 的很多方面,包括编译哪些功能,比如支持 DHCP、内存测试、调试等(没错,甚至可以配置 U-Boot 控制台下能使用哪些命令)。这种机制也可以用来告知 U-Boot,开发板上使用了哪种内存,以及内存大小和内存映射地址。多的这里就不展开了,点到为止,各个配置选项的详细情况可以去阅读 U-Boot 的 README 文件。


问题记录:
  实际上,真正完整的流程还包括 U-Boot 编译之前的 U-Boot 移植。不过因为博主的项目采用的方案是直接在 Hi3531D 开发板上做软件开发工作,完了在硬件上按开发板裁剪并仿制出自己的板子,再把之前做好的平台与应用整套搬过来,姑且算是取了个巧(因为从店家手上拿到的其实已经算是移植好了的),所以对于 U-Boot 移植的具体情况还是模糊的。如果以后再接手到不能如此取巧的开发项目,再回来仔细研究移植吧。
  另外,对于利用编译得到的 u-boot.bin 文件和 mkboot.sh 来生成最终可用的 U-Boot 镜像这一步自认为没弄明白,也只是简单地照做了。目前能找到的资料有限 (其实就是完全没找到) ,配合使用的 Excel 表格也只能大致看出来是关于寄存器级别的参数设置,mkboot.sh 将包含这些寄存器配置信息的 reg_info.bin 文件内容整合到 u-boot.bin 文件中,从而得到完整的 U-Boot 镜像文件。不过也不确定这是否为海思 SDK 包独有的机制,只能先作为一个疑点记录下来。


3.2 Nor 和 Nand

  现在回到对 U-Boot 进行预配置的指令中。可选参数:使用 SPI Nor Flash 或 SPI Nand Flash 以及 使用 Nand Flash,这两种参数对应了不同的配置。虽说当时因为板载资源只有 SPI Nor Flash 和 SPI Nand Flash,莫得选择,只能用第一种,但是这块的知识点是空白的,现在补上。
  首先,Flash 本身是一种非易失性存储,在没有电流供应的条件下也能够长久地保持数据,其存储特性相当于硬盘,这项特性正是闪存得以成为各类便携型数字设备的存储介质的基础。Flash按照内部存储结构的不同,可以分为两种:Nor Flash 和 Nand Flash。

  • Nor Flash 和 Nand Flash:Nor Flash 更像内存,有独立的地址线和数据线,但价格比较贵,容量比较小;而 Nand Flash 更像硬盘,地址线和数据线是共用的 I/O 线,类似硬盘的所有信息都通过一条硬盘线传送一般,而且 Nand Flash 的成本较 Nor Flash 来说很低,而容量却大很多。
    因此,Nor Flash 比较适合频繁随机读写的场合,通常用于存储程序代码并直接在闪存内运行,手机就是使用 Nor Flash 的大户,所以手机的“内存”容量通常不大;Nand Flash 主要用来存储资料,我们常用的闪存产品,如闪存盘、数码存储卡都是用 Nand Flash。
  • SPI Nor Flash 和 SPI Nand Flash:SPI 是一种通信接口协议,那么 SPI Flash 也就是使用 SPI 通信接口的 Flash,可以是 Nor Flash 也可以是 Nand Flash。不过现在大部分情况下人们说的 SPI Flash 指的是 SPI Nor Flash。

  其次,在 Hi3531D 开发板中已经有 U-Boot 且网络配置完成的情况下,可以使用网口将新的 U-Boot 、Linux 内核映像以及根文件系统烧写到 Flash 中。开发板默认为从 SPI Flash 启动。下图中并没有记录关于 U-Boot 烧录的指令内容,但是完全可以根据另外两个烧写的命令反推得到。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第7张图片
  上图中的指令是 SPI Nor Flash 的烧写方法,但是咱板子上 SPI Nor Flash 和 SPI Nand Flash 又是同时存在的!如果要用 SPI Nand FLash 的话,烧录指令就不一样了:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第8张图片

PS:烧录的文件系统的类型也不一样了,但是这个现在不是当前重点,之后会展开。另外,只要处理器支持,将启动介质设置为 SPI Nor Flash/SPI Nand Flash/并口 Nand Flash 都是可行的,但涉及到硬件电路上的设置以及 U-Boot 配置上的修改。

  最后,还有一点关于博客 Hi3531D调试手记(二):U-Boot、Linux内核与RootFS镜像制作及烧录 中 U-Boot 启动参数设置的说明补充:

setenv bootargs 'mem=64M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 rw mtdparts=hi_sfc:1M(boot),4M(kernel),27M(rootfs);hinand:256M(nand)'

  这里是将 U-Boot、Linux 内核以及根文件系统镜像都烧写到了 SPI Nor Flash 里,所以前三个分区都是使用 hi_sfc 来进行空间划分。然而之后又跟了一句 hinand:256M(nand),将 SPI Nand Flash 也用上了,个人认为或许和嵌入式系统中的闪存结构图有关。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第9张图片
  通常情况下,U-Boot 会安排在闪存阵列的最顶端或者最底端,Linux 内核映像以及文件系统映像紧随其后,其中,Linux 内核映像以及文件系统映像都是压缩过的,U-Boot 在启动过程中负责对它们进行解压。
  为了存储在重启或重新上电情况下需要保存的动态数据,需要专门分配一小块闪存空间,或者提供另外一种非易失存储介质。对于需要保存配置信息的嵌入式系统来说,这是很典型的配置形式。Hi3531D 的 SPI Nor Flash 有 32M,已经全部分配给 U-Boot、Kernel 和 RootFS 这前三个分区,那么 256M 的 SPI Nand Flash 作为另一种非易失存储介质,在这里应该就是作为更新空间使用了。

3.3 网络操作

  许多 bootloader 都包括了对以太网接口的支持(U-Boot 也不例外)。在开发环境中,这会极大地节省时间。通过串口载入一个中等规模的内核镜像可能需要数分钟之久,而使用 10Mbit/s 的以太网连接器仅需要几秒钟。此外,使用功能较弱的串行终端的串口连接更容易产生错误。
  Hi3531D 开发板对应的 U-Boot 支持 TFTP(Trivial File Transfer Protocol)协议和 NFS(Network File System)服务,这就使得 U-Boot 能通过 TFTP/NFS 服务器下载文件(例如 Linux 内核镜像)/直接挂载文件系统。
  U-Boot 阶段的网络环境变量配置的知识点就不补充了,计算机网络基础知识。

3.3.1 TFTP 服务器

  在 Linux 开发主机上配置 TFTP 服务并不难。当然,根据开发工作站选择的 Linux 发行版的不同,其配置细节也有差异。这里就以 Ubuntu 为例进行记录。
  TFTP 是一种 TCP/IP 服务,因此工作站必须开启 TCP/IP 服务。要开启 TFTP 服务,必须指示服务器响应即将到来的 TFTP 数据包,并创建 TFTP 服务器。在使用 apt-get 安装好指定软件包以后,原博客中一共记录了三处修改:/etc/xinetd.conf 、/etc/default/tftpd-hpa 以及 /etc/xinetd.d/tftp,接下来对逐个文件的修改进行说明。

  • /etc/xinetd.conf:xinetd 即 extended internet daemon,是新一代的网络守护进程服务程序,又叫超级 Internet 服务器,xinetd.conf 自然就是 xinetd 的配置文件。
    嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第10张图片
    defaults{ } 里面是啥也没加的,只有最底下的一句 includedir /etc/xinetd.d 表明:相关设定全都在 /etc/xinetd.d 那个目录内。根据网上的资料,xinetd 负责启动大部分网络服务,旧版本中是在 xinetd.conf 中配置,新版本才加的 xinetd.d。在 xinetd.d 目录中,每一个服务都有一个相应的配置文件,因此可以对所有的服务器进行单独配置。
  • /etc/default/tftpd-hpa:tftpd-hpa 这个东西没有在网上找到概念性的介绍,全是贴个配置就完事了 Orz
    在这里插入图片描述
    TFTP_DIRECTORY 是自行指定的 tftp 服务的共享文件夹所在路径;
    TFTP_ADDRESS 中的 69 表示 tftp 协议的端口号;
    TFTP_OPTIONS 部分表示其操作权限,“-l”是在独立运行服务器(听)模式,可以自己启动,不依赖于 xinetd 或者 /etc/init.d/tftpd-hpa 脚本。
  • /etc/xinetd.d/tftp:这就是 xinetd.d 目录下关于 tftp 服务器的单独配置文件了。
    嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第11张图片
    socket_type = dgram :socket的链接类型是 dgram(UDP);
    wait = yes :等待到启动完成;
    disable = no :yes 表示 TFTP 服务器是关闭的;no 表示启动 TFTP 服务器;
    user = root :指定 root 用户启动服务进程;
    protocol = udp : UDP协议;
    server = /usr/sbin/in.tftpd :指定服务进程是/usr/sbin/in.tftpd;
    server_args = -s /home/USERNAME/linux/tftpboot :指定传给该进程的参数;
    per_source = 11 :表示每一个 ip 地址上最多可以建立的实例数目;
    cps = 100 2 :表示每秒 100 个入站链接,如果超过限制,则等待 2 秒。

3.3.2 NFS 服务器

  使用 NFS 挂载目标板 Linux 内核镜像和根文件系统是非常有效的开发手段。不知道是不是因为使用到的 NFS 功能很有限,感觉 Ubuntu 下搭建 NFS 服务器似乎异常地简单,只需要修改一处:/etc/exports。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第12张图片

四、Linux 内核

  本节复盘及知识点补充内容对应博客 Hi3531D调试手记(一):Linux开发环境搭建、Hi3531D调试手记(二):U-Boot、Linux内核与RootFS镜像制作及烧录 中的 Linux 内核相关内容。这里补充得比较多,毕竟之前让内核启动了就直接开始开发软件了,对于内核这一块的细节是真的没关注太多。

4.1 内核版本与源码库

  在很多地方都可以轻而易举地得到 Linux 内核源代码以及其他组件。书店所售 Linux 图书中附带的光盘就有很多种不同的版本。当然也可以从 Linux 内核的官方主页 www.kernel.org 下载不同版本的 Linux 内核源码以及各种补丁。
  Hi3531D 项目的开发使用的 Linux 内核版本为 3.18。在很早以前的开发者队伍中,从事开发工作的工程师们为了能够区分 Linux 内核源码树,使用数字来对其进行编号。Linux 内核可以分为两大类:一类是专门用于开发的试验版本,另一类是稳定的产品级版本。Linux 操作系统的版本号由主版本号和次版本号组成,最后还有相应的序列号。如果小版本号是偶数,则表明此版本的 Linux 操作系统是稳定的产品级产品;如果是奇数,则表明此版本的 Linux 操作系统是用于开发的试验产品。
  想要了解正在使用的 Linux 内核版本信息是很容易的。在内核源码的最顶层 Makefile 前几行中就包含了当前操作系统的内核版本,例如下面的几行信息,从这几行信息中可以看到,Linux 是 3.18.20 版本的产品级内核。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第13张图片
  EXTRAVERSION 这一栏是空的,用于留给用户自定义字段实现在开发过程中对内核版本的快速跟踪与定位。

4.2 Linux 内核构造

  当前主机开发系统上有两个版本的 Linux 内核:一个是要通过交叉编译最终运行在 Hi3531D 开发板上的 3.18.20 版本(图示为已完成编译的状态);另一个则是主机开发系统上使用的 Linux 发行版的内核,相应的内核源码在 /usr/src/linux-x.y.z 路径下,其中 x.y.z 表示当前 Linux 内核的版本号。
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第14张图片
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第15张图片
  可以看到,Linux 内核的顶层目录包含了众多的子目录,而通常这些子目录还包含了其他几层子目录,里面包含了源代码、makefile 以及配置文件。所有 Linux 内核源码树中最大的就是 driver 路径下的内容,这里面包含了支持以太网卡、USB 控制器以及 Linux 内核所支持的所有硬件驱动是程序以及源代码。第二大分支自然就是 arch 子目录,它包含了至少 20 多种不同类型的处理器体系结构的支持文件。
  除了这些子目录,顶层 makefile、.config 文件以及 System.map 文件和 vmlinux 文件都是非常重要的文件,具体情况在补充说明 Linux 内核编译过程的时候展开。

4.3 Linux 内核编译

4.3.1 内核补丁

  在完成 Linxu 内核代码的解压后,并未立即开始编译,而是先使用 patch 命令打了个补丁:
在这里插入图片描述
  这里其实只完成了打补丁的后半部分工作(补丁文件已经放在 SDK 包里了)。完整的打补丁过程,是先将源文件复制出一个副本,在副本里面修改,修改完成后使用 diff 命令比较两个文件之间的差异,得到补丁文件,再用 patch 命令使补丁生效,则源文件就会更改成副本的样子。当然,也可以把补丁去掉,变成原来的样子。

PS:补丁文件里的内容很多,而且以目前的水平来看是理解不能的,这个只能放一放。

4.3.2 .config 文件

  .config 文件包含了编译 Linux 内核映像的一系列配置脚本,因此被放置于 Linux 操作系统资源路径的顶层,用于驱动整个系统内核的构建工作。它是进行操作系统内核编译的起点,特别是针对某种嵌入式平台完成 Linux 内核编译的工作。
  这里直接将对应处理器型号的 defconfig 文件直接拷贝过来作为初始的 .config 文件:
在这里插入图片描述
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第16张图片

  • 参数说明中出现了 PCIE(Peripheral Component Interconnect Express,外围设备互连),这是一种通用的总线接口标准,在目前的计算机系统中得到了广泛的应用,而在 Hi3531D 开发板上的主要应用是级联,但是项目中只需要用到一块 Hi3531D,所以实际上选择主片模式还是从片模式都无所谓;
    嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第17张图片
  • Linux 是一个单一体系结构的操作系统,整个操作系统的内核在一次编译链接过程中完成,并且会生成一个静态可执行系统文件。但是,系统也可以逐步编译并且增量链接一组源文件而生成单一的目标模块,这样才能够动态地将一些特性引入到运行的内核系统中。在进行系统驱动开发的时候经常采用这种方法,以便能够支持更多的系统硬件平台。在 LInux 操作系统中,这些目标模块称为 “可加载模块(loadable module)”。内核启动后,特定的应用程序把这些可加载模块依次插入到正在运行的内核中(这在之后会有直观的体现)。
    嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第18张图片
      上图是从 .config 文件中截取的部分内容,里面全是各种 CONFIG_xxx=y(编译进内核) 和 CONFIG_xxx=m(编译成可加载模块)。所以,.config 文件其实就是在设置哪些设备驱动要编译成可加载模块,哪些设备驱动直接整合到内核中,成为 Linux 内核的一部分。

4.3.3 menuconfig

  因为当前的 .config 文件只能算是 Hi3531D 的默认配置,所以如果需要对内核进行自定义功能的选择,就可以通过接下来的指令完成:
在这里插入图片描述
  接下来就会进入到配置界面:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第19张图片
  如果需要使用 NFS 挂载根文件系统,也需要在这里设置内核支持才行。


补充:
  menuconfig 只是更新 .config 文件的其中一种图形界面化工具。实际上还可以使用 xconfig、gconfig、oldconfig、randconfig 等多种方式来修改 .config 中的内核配置信息。如果需要更详细的说明,可以直接使用 make help 指令获取,当然这样的话实际输出的信息量可能远远超出原本使用这条指令的预期:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第20张图片

PS:你以为这就完了?其实只是贴了前面的一小部分截图而已。指令中加上了 ARCH=arm 来声明 make 的目标归属于 ARM 架构。如果有兴趣可以试试不加声明,看看最后输出的信息有什么不同。


4.3.4 vmlinux、zImage 和 uImage

  在使用指令编译完 Linux 内核以后,可以看到在末尾处生成了几个文件,分别是 vmlinux、zImage 和对应的 dtb、uImage:
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第21张图片
  这些文件的意义可以通过 make help 来查看:
在这里插入图片描述
嵌入式Linux基础学习笔记(一):U-Boot、Kernel、RootFS初体验_第22张图片
  因此,vmlinux 实际上才是严格意义上的 Linux 内核,它是一个完全独立运行的单一操作系统内核映像,所有的外部引用信息都保存在 vmlinuix 二进制文件中(因此理解 vmlinux 的二进制组件构成也是理解 linux 启动流程的关键);zImage 是一般情况下经过压缩的内核映像;uImage 则是配合 U-Boot 使用的 zImage,二者并没有本质上的区别。

五、RootFs

  本节复盘及知识点补充内容对应博客 Hi3531D调试手记(二):U-Boot、Linux内核与RootFS镜像制作及烧录 中的根文件系统相关内容。

5.1 BusyBox 工具

  这是个在嵌入式 Linux 中非常有用的一个强力工具。但是在当前阶段博主接触到的直接原因是用来制作根文件系统,所以还是决定把它给放在这里简单地介绍一下。
  BusyBox 提供一些紧凑的程序,可以替代那些大多数桌面和嵌入式 Linux 发行版中使用的成熟实用程序,例如 ls、cat、dir、head 和 tail 等文件工具;dmesg、kill、halt、mount、umount 等常用实用程序,不一而足。
  BusyBox 具有模块化和高度可配置的特点,可以进行裁剪以满足项目的特定需求。BusyBox 提供的实用配置程序与配置 lInux 内核所用的实用程序类似,因此 BusyBox 的配置非常容易上手。
  与对应的全功能版本相比,BusyBox 里的命令实现一般更为简单。在某些情况下,BusyBox 仅支持一部分常用的命令行选项。但是,实际上 BusyBox 提供的命令功能子集足以满足绝大多数嵌入式系统的常见需求。

5.1.1 inittab

  inittab 是 Linux 系统初始化过程中需要使用到的一个文件,内核启动后就需要读取其内容来决定在最开始要创建哪些进程。基于 BusyBox 构建跟文件系统,也就是由 BusyBox 来处理系统初始化,也需要这样一个文件。但是,BusyBox 初始化系统使用到的 inittab 与 System V 初始化系统使用到的 inittab 文件在语法上有区别的,二者并不能混为一谈。

5.1.2 fstab

  fstab 是用来存放文件系统的静态信息的文件,位于 /etc/ 目录下,可以用命令 less /etc/fstab 来查看。如果要修改的话,则用命令 vi /etc/fstab 来修改。当系统启动的时候,系统会自动地从这个文件读取信息,并且会自动将此文件中指定的文件系统挂载到指定的目录。

5.1.3 init.d/rcS

  在 BusyBox 产生一个交互 Shell 之前(也就是用户看见命令提示符之前),会执行名为 /etc/init.d/rcS 脚本文件中的命令。在一个基于 BusyBox 的系统中,用户的应用程序就是在这个脚本文件中得以实现,正如博客 Hi3531D调试手记(四):使用ffmpeg实时封装H264视频为MP4 的第五节:杂记(个人向)中所描述的那样。

5.2 文件系统概念

  文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于 NAND Flash 的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:文件系统的接口,对对象操作和管理的软件集合,对象及属性。从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。简单来说,内核是 linux 的核心,而文件系统则是用户与操作系统交互所采用的主要工具。
  根文件系统是文件系统中的一种特殊形式,它是内核启动时所挂载的第一个文件系统,是后续挂载的所有文件系统的 “根”。内核代码映像文件也保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行(比如之前的 rcS 文件)。
  Linux 启动时,第一个必须挂载的是根文件系统。若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。根文件系统挂载成功之后,用户也可以设置自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。在 Linux 中将一个文件系统与一个存储设备关联起来的过程称为挂载(mount)。使用 mount 命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。根文件系统被挂载到根目录下“/”上后,在根目录下就有根文件系统的各个目录,文件:/bin /sbin /mnt 等,再将其他分区挂接到 /mnt 目录上,/mnt 目录下就有这个分区的各个目录和文件。

5.3 文件系统类型(基于 Flash)

  这个东西可能直接上表来看会更为直观:

文件系统类型 优点 缺点
cramfs 将文件数据以压缩形式存储,在需要运行时进行解压缩,能节省 Flash 存储空间。 由于其存储的文件是压缩的格式,所以文件系统不能直接在 Flash 上运行。同时,文件系统运行时需要解压数据并拷贝至内存中,在一定程度上降低了读取效率。另外 cramfs 文件系统是只读的。
jffs2 使用了压缩的文件格式。最重要的特性是可读写操作。 jffs2 文件系统挂载时需要扫描整个文件系统,因此当 jffs2 文件系统分区增大时,挂载时间也会相应的变长。使用 jffs2 格式可能带来少量的 Flash 空间的浪费。这主要是由于日志文件的过度开销和用于回收系统的无用存储单元,浪费的空间大小大致是若干个数据段。jffs2 的另一缺点是当文件系统已满或接近满时,其运行速度会由于垃圾收集问题而迅速降低。
yaffs2 专门针对 Nand Flash,软件结构得到优化,速度快;使用硬件的 spare area 区域存储文件组织信息,启动时只需扫描组织信息,启动比较快;采用多策略垃圾回收算法,能够提高垃圾回收的效率和公平性,达到损耗平衡的目的。 没有采用压缩的文件格式。当包含的内容相同时,yaffs2 镜像文件要比 jffs2 镜像文件大。

六、杂

  这篇文章就到这里吧,目前的篇幅已经很长了,然而这里记录的不过是些皮毛而已 QAQ … 整篇看下来可能会有些杂乱无章的感觉,但这就是个人学过来最真实的写照:大概知道分成几大块,再从这些大块发散式地学习与补充相关知识点。
  驱动开发和 Makefile 的部分放在下一篇博客中记录。

你可能感兴趣的:(Hi3531D调试手记,linux,嵌入式)