Skip to content

Latest commit

 

History

History
489 lines (335 loc) · 37.3 KB

File metadata and controls

489 lines (335 loc) · 37.3 KB

五、Linux 根文件系统

在本章中,您将了解根文件系统及其结构。 您还将看到有关根文件系统的内容、各种可用的设备驱动程序以及它与 Linux 内核的通信的信息。 我们将慢慢过渡到 Yocto 项目和用于定义 Linux 根文件系统内容的方法。 将提供必要的信息,以确保用户还能够根据需要定制rootfs文件系统。

将介绍根文件系统的特殊要求。 您将获得有关其内容、子目录、定义的用途、各种可用的文件系统选项、BusyBox 替代方案以及许多有趣特性的信息。

在与嵌入式环境交互时,许多开发人员都会从发行版提供商(如 Debian)提供的最小根文件系统开始,使用跨工具链将通过各种包、工具和实用程序对其进行增强。 如果要添加的包数量很大,可能会非常麻烦。 白手起家将是一场更大的噩梦。 在约克托项目内部,这项工作是自动化的,不需要人工工作。 开发是从头开始的,它在根文件系统中提供了大量的包,使工作变得有趣。 因此,让我们继续前进,看看本章的内容,以便从总体上更多地了解根文件系统。

与根文件系统交互

根文件系统由目录和文件层次结构组成。 在此文件层次结构中,可以挂载各种文件系统,从而显示特定存储设备的内容。 使用 mount 命令完成挂载,操作完成后,使用存储设备上的可用内容填充挂载点。 反向操作称为umount,用于清空其内容的挂载点。

上述命令对于应用与各种可用文件的交互非常有用,无论其位置和格式如何。 例如,mount命令的标准格式为mount –t type device directory。 该命令要求内核从命令行中提到的type格式的设备以及同一命令中提到的目录连接文件系统。 在删除设备之前需要给出umount命令,以确保内核缓存已写入存储点。

根文件系统在根层次结构中可用,也称为/。 它是第一个可用的文件系统,也是不使用mount命令的文件系统,因为它是由内核通过root= argument直接挂载的。 以下是加载根文件系统的多个选项:

  • 从记忆中
  • 使用 NFS 从网络
  • 来自 NAND 芯片
  • 从 SD 卡分区
  • 从 USB 分区
  • 从硬盘分区

这些选项由硬件和系统架构师选择。 要利用这些功能,需要相应地配置内核和引导加载程序。

除了需要与板的内部存储器或存储设备交互的选项外,最常用的加载根文件系统的方法之一由 NFS 选项表示,这意味着根文件系统在本地计算机上可用,并通过目标上的网络导出。 此选项具有以下优势:

  • 根文件系统的大小不是问题,因为开发计算机上的存储空间比目标计算机上的可用存储空间大得多
  • 更新过程要容易得多,无需重新启动即可完成
  • 对于具有小型甚至不存在内部或外部存储设备的设备,通过网络访问存储是最佳解决方案

网络存储的缺点是需要服务器客户端体系结构。 因此,对于 NFS,需要在开发计算机上提供 NFS 服务器功能。 对于 Ubuntu 主机,所需的配置包括安装nfs-kernel–serversudo apt-get install nfs-kernel-server。 安装程序包后,需要指定和配置导出的目录位置。 这是使用/etc/exports文件完成的;在这里,会出现类似于/nfs/rootfs <client-IP-address> (rw,no_root_squash,no_subtree_check)的配置行,其中每一行都定义了通过网络与 NFS 客户端共享的位置。 配置完成后,需要按如下方式重启 NFS 服务器:sudo /etc/init.d/nfs-kernel-server restart

对于目标上可用的客户端端,需要相应地配置 Linux 内核,以确保启用了 NFS 支持,并且在引导时有一个 IP 地址可用。 这些配置为CONFIG_NFS_FS=yCONFIG_IP_PNP=yCONFIG_ROOT_NFS=y。 内核还需要配置root=/dev/nfs参数、目标的 IP 地址和 NFS 服务器nfsroot=192.168.1.110:/nfs/rootfs信息。 以下是两个组件之间的通信示例:

Interacting with the root filesystem

还有一种可能是将根文件系统集成到内核映像中,也就是最小的根文件系统,其目的是启动功能齐全的根文件系统。 这个根文件系统称为initramfs。 对于对较小根文件系统的快速引导选项感兴趣的人来说,这种类型的文件系统非常有用,这些根文件系统只包含一些有用的功能,需要更早启动。 它对于在引导时快速加载系统很有用,但也可以作为启动其中一个可用存储位置上可用的实际根文件系统之前的中间步骤。 根文件系统首先在内核引导过程之后启动,因此它与 Linux 内核一起使用是有意义的,因为它位于 RAM 内存中靠近内核的位置。 下图说明了这一点:

Interacting with the root filesystem

要创建initramfs,需要使配置可用。 这可以通过定义根文件系统目录的路径、cpio存档的路径,甚至是描述CONFIG_INITRAMFS_SOURCE内的initramfs内容的文本文件来实现。 当内核构建开始时,将读取CONFIG_INITRAMFS_SOURCE的内容,并将根文件系统集成到内核映像中。

备注

有关initramfs文件系统选项的更多信息可以在Documentation/filesystems/ramfs-rootfs-initramfs.txtDocumentation/early-userspace/README处的内核文档文件中找到。

初始 RAM 磁盘或initrd是挂载早期根文件系统的另一种机制。 它还需要在 Linux 内核中启用的支持,并作为内核的一个组件加载。 它包含一小部分可执行文件和目录,并代表到功能齐全的根文件系统的过渡阶段。 对于没有能够容纳更大根文件系统的存储设备的嵌入式设备,它仅代表最后阶段。

在传统系统上,initrd是使用mkinitrd工具创建的,实际上,该工具是一个 shell 脚本,可以自动执行创建initrd所需的步骤。 以下是其功能的示例:

#!/bin/bash

# Housekeeping...
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz

# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024

# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE

# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE

# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0

# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc

# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd

# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev

# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd

# Create the init file
cat >> /mnt/initrd/linuxrc << EOF
#!/bin/ash
echo
echo "Simple initrd is active"
echo
mount -t proc /proc /proc
mount -t sysfs none /sys
/bin/ash --login
EOF

chmod +x /mnt/initrd/linuxrc

# Finish up...
umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz

备注

有关initrd的更多信息,请参见Documentation/initrd.txt

使用initrd不像initramfs那么简单。 在这种情况下,需要以类似于内核映像的方式复制存档,引导加载程序需要将其位置和大小传递给内核,以确保它已经启动。 因此,在这种情况下,引导加载器还需要initrd的支持。 initrd的中心点由linuxrc文件构成,该文件是启动的第一个脚本,通常用于提供对系统引导的最后阶段(即真正的根文件系统)的访问。 在linuxrc完成执行之后,内核将卸载它并继续使用真正的根文件系统。

深入研究文件系统

无论它们的出处是什么,大多数可用的根文件系统都具有相同的目录组织,正如通常所说的那样,由文件系统层次结构标准(FHS)定义。 这种组织对开发人员和用户都有很大帮助,因为它不仅提到了目录层次结构,还提到了目录的用途和内容最值得注意的是:

  • /bin:指大多数程序的位置
  • /sbin:系统程序的位置
  • /boot:引导选项的位置,如kernel imagekernel configinitrdsystem maps等信息
  • /home:指用户主目录
  • /root:指 root 用户主位置的位置
  • /usr:这指的是特定于用户的程序和库,并模仿根文件系统的部分内容
  • /lib:指图书馆的位置
  • /etc:指系统范围的配置
  • /dev:指设备文件的位置
  • /media:移动设备挂载点的位置
  • /mnt:静态介质挂载位置
  • /proc:指的是proc虚拟文件系统的挂载点
  • /sys:指的是sysfs虚拟文件系统的挂载点
  • /tmp:指临时文件的位置
  • /var:指数据文件,如记录数据、管理信息或临时数据的位置

FHS 会随着时间的推移改变,但变化不大。 前面提到的大多数目录由于各种原因保持不变-最简单的一个原因是它们需要确保向后兼容。

备注

有关 FHS 的最新可用信息,请访问http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.pdf

根文件系统由内核启动,这是内核在结束引导阶段之前完成的最后一步。 下面是执行此操作的确切代码:

/*
  * We try each of these until one succeeds.
  *
  * The Bourne shell can be used instead of init if we are
  * trying to recover a really broken machine.
  */
  if (execute_command) {
    ret = run_init_process(execute_command);
    if (!ret)
      return 0;
    pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",execute_command, ret);
  }
  if (!try_to_run_init_process("/sbin/init") ||
    !try_to_run_init_process("/etc/init") ||
    !try_to_run_init_process("/bin/init") ||
    !try_to_run_init_process("/bin/sh"))
      return 0;

  panic("No working init found.  Try passing init= option to kernel." "See Linux Documentation/init.txt for guidance.");

在这段代码中,可以很容易地识别出,在退出 Linux 内核引导执行之前,有许多位置用于搜索需要启动的init进程。 run_init_process()函数是execve()函数的包装器,如果调用过程中没有遇到错误,它将不会有返回值。 被调用的程序覆盖执行进程的内存空间,替换调用线程并继承其PID

这个初始化阶段太老了,所以 Linux 1.0 版本中也有类似的结构。 这表示用户空间处理开始。 如果内核无法在预定义的位置执行前面四个功能中的一个,则内核将暂停,并在控制台上提示一条紧急消息,发出警报,指出无法启动任何初始化进程。 因此,在内核空间处理完成之前,用户空间处理不会开始。

对于大多数可用的 Linux 系统,/sbin/init是内核产生 init 进程的位置;Yocto 项目生成的根文件系统也是如此。 它是第一个在用户空间上下文中运行的应用,但不是根文件系统的唯一必需功能。 在根文件系统内运行任何进程之前,需要解决几个依赖项。 有一些依赖项用于解析先前未解析的动态链接依赖项引用,还有一些依赖项需要外部配置。 对于第一类依赖关系,可以使用ldd工具来识别动态链接的依赖关系,但对于第二类依赖关系,没有通用的解决方案。 例如,对于init进程,配置文件为inittab,,该文件在/etc目录中可用。

对于对运行另一个init进程不感兴趣的开发人员,可以使用带 Availableinit=参数的内核命令行访问该选项,其中应提供到执行的二进制文件的路径。 在前面的代码中也提供了此信息。 定制init流程不是开发人员常用的方法,但这是因为init流程非常灵活,这使得许多启动脚本可用。

init之后启动的每个进程都使用父子关系,其中init充当在用户空间上下文中运行的所有进程的父进程,也是环境参数的提供者。 最初,init 进程根据定义运行级概念的/etc/inittab配置文件中的可用信息生成进程。 运行级代表系统的状态,并定义已启动的程序和服务。 有 8 个运行级别可用,编号从06,还有一个特殊的级别标记为S。 它们的用途如下所述:

|

运行级别值

|

运行级目的

| | --- | --- | | 0 | 指的是整个系统的关机断电命令 | | 1 | 它是单用户管理模式,具有标准登录访问权限 | | 2 | 它是无 TCP/IP 连接的多用户 | | 3 | 它指的是通用多用户 | | 4 | 它由系统的所有者定义 | | 5 | 它指的是图形界面和 TCP/IP 连接的多用户系统 | | 6 | 它指的是系统重新启动 | | s | 它是单用户模式,提供对最小根外壳的访问 |

每个运行级启动和终止多个服务。 启动的服务以S开头,取消的服务以K开头。 实际上,每个服务都是一个 shell 脚本,它定义了它定义的提供的行为。

/etc/inittab配置脚本定义了运行级和应用于所有运行级的指令。 对于 Yocto 项目,/etc/inittab如下所示:

# /etc/inittab: init(8) configuration.
# $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $

# The default runlevel.
id:5:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin

# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.

l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
# Normally not reached, but fallthrough in case of emergency.
z6:6:respawn:/sbin/sulogin
S0:12345:respawn:/sbin/getty 115200 ttyS0
# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
#  <id>:<runlevels>:<action>:<process>
#

1:2345:respawn:/sbin/getty 38400 tty1

init解析inittab文件前面的时,执行的第一个脚本是通过sysinit标记标识的si::sysinit:/etc/init.d/rcS行。 然后,进入runlevel 5,并且指令的处理一直持续到最后一级,直到最后使用/sbin/getty symlink产生外壳。 在控制台中运行man initman inittab可以找到关于initinittab的更多信息。

任何 Linux 系统的最后阶段都由 POWER OFF 或 SHUTDOWN 命令表示。 这一点非常重要,因为如果操作不当,可能会通过损坏数据来影响系统。 当然,实现关闭方案有多种选择,但最方便的仍然是实用程序的形式,例如shutdownhaltreboot。 也有可能使用init 0停止系统,但实际上,它们的共同点是使用SIGTERMSIGKILL信号。 SIGTERM最初用于通知您关闭系统的决定,为系统提供执行必要操作的机会。 完成此操作后,将发送SIGKILL信号以终止所有进程。

设备驱动程序

对于 Linux 系统来说,最重要的挑战之一是允许访问各种硬件设备上的应用。 虚拟内存、内核空间和用户空间等概念无助于简化工作,反而给这些信息增加了另一层复杂性。

设备驱动程序唯一的目的是将硬件设备和内核数据结构与用户空间应用隔离。 用户不需要知道要将数据写入硬盘,他或她将被要求使用各种大小的扇区。 用户只打开要在其中写入的文件,完成后关闭。 设备驱动程序负责所有底层工作,例如隔离复杂性。

在用户空间内,所有设备驱动程序都有关联的设备节点,这些节点实际上是代表设备的特殊文件。 所有设备文件都位于/dev目录中,与它们的交互是通过mknod实用程序完成的。 设备节点在两个抽象下可用:

  • 块设备:这些块由固定大小的块组成,通常在与硬盘、SD 卡、U 盘等交互时使用
  • 字符设备:这些是没有大小、开头或结尾的字符流;它们大多不是块设备的形式,如终端、串行端口、声卡等

每台设备都有一个提供相关信息的结构:

  • Type标识设备节点是字符还是块
  • Major标识设备的类别
  • Minor保存设备节点的标识符

创建设备节点的mknod实用程序使用三元组信息,如mknod /dev/testdev c 234 0。 执行该命令后,会出现一个new /dev/testdev文件。 它应该将自己绑定到已安装并已定义其属性的设备驱动程序。 如果发出open命令,内核将查找使用与设备节点相同的主编号注册的设备驱动程序。 次要编号用于处理具有相同设备驱动程序的多个设备或一系列设备。 它被传递给设备驱动程序,以便它可以使用它。 次要设备的使用没有标准方法,但通常,它定义共享相同主设备编号的设备系列中的特定设备。

使用mknod实用程序需要手动交互和 root 权限,并让开发人员完成识别设备节点及其对应设备驱动程序的属性所需的所有繁重工作。 最新的 Linux 系统提供了自动执行此过程的可能性,并在每次检测到设备或设备消失时完成这些操作。 此操作如下所示:

  • devfs:这个指的是被设计为文件系统的设备管理器,它也可以在内核空间和用户空间上访问。
  • devtmpfs:这个指的是自 2.6.32 内核版本发布以来可用的虚拟文件系统,它是对用于引导时间优化的devfs的改进。 它只为本地系统上可用的硬件创建设备节点。
  • udev:本指的是服务器和桌面 Linux 系统上使用的守护进程。 有关这方面的更多信息,请访问https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html。 Yocto 项目还将其用作默认设备管理器。
  • mdev:这个提供了比udev更简单的解决方案;实际上,它是 udev 的派生。

由于 System 对象也表示为文件,因此它简化了应用与它们交互的方法。 如果没有设备节点的使用,这是不可能的,设备节点实际上是可以应用正常文件交互功能的文件,例如open()read()write()close()

文件系统选项

根文件系统可以部署在非常广泛的文件系统类型下,并且每个文件系统都比其他文件系统更好地完成特定的任务。 如果某些文件系统针对性能进行了优化,则其他文件系统在节省空间甚至恢复数据方面做得更好。 这里将介绍一些最常用和最有趣的方法。

物理设备(如硬盘或 SD 卡)的逻辑分区称为分区。 物理设备可以有一个或多个分区来覆盖其可用存储空间。 可以将其视为具有可供用户使用的文件系统的逻辑磁盘。 Linux 中的分区管理是使用fdisk实用程序完成的。 可用于createlistdestroy等一般交互,分区类型超过 100 种。 更准确地说,在我的 Ubuntu14.04 开发机器上提供了 128 种分区类型。

最常用和众所周知的文件系统分区格式之一是ext2。 它也被称为第二扩展文件系统,它是由法国软件开发商 Rémy Card 于 1993 年推出的。 它被用作大量 Linux 发行版的默认文件系统,比如 Debian 和 Red Hat Linux,直到它被它的弟弟ext3ext4取代。 它仍然是许多嵌入式 Linux 发行版和闪存设备的选择。

ext2文件系统将数据分割成块,这些块被排列成个块组。 每个块组维护超级块的副本和该块组的描述符表。 超级块用于存储配置信息,并保存引导过程所需的信息,尽管有多个副本可用;通常,位于文件系统的第一个块中的第一个副本是所使用的副本。 文件的所有数据通常保存在单个块中,以便更快地进行搜索。 每个块组除了其包含的数据外,还具有关于超级块的信息、块组的描述符表、索引节点位图和表信息以及块位图。 超级块是保存对引导过程重要的信息的块。 它的第一个块用于引导过程。 最后一个概念以inodes或索引节点的形式出现,它们通过文件和目录的权限、大小、磁盘位置和所有权来表示它们。

有多个应用用于与ext2文件系统格式交互。 其中之一是mke2fs,用于在mke2fs /deb/sdb1 –L分区(ext2标签分区)上创建ext2文件系统。 是e2fsck命令,用于验证文件系统的完整性。 如果没有发现错误,这些工具会提供有关分区文件系统配置的信息e2fsck /dev/sdb1。 此实用程序还可以修复设备使用不当后出现的一些错误,但不能在所有情况下使用。

Ext3 是另一个功能强大且广为人知的文件系统。 它取代了ext2,成为 Linux 发行版上使用最多的文件系统之一。 它实际上类似于ext2;不同之处在于它可以将可用的信息记入日志。 可以使用tune2fs –j /dev/sdb1命令以ext3文件格式更改ext2文件格式。 它基本上被视为ext2文件系统格式的扩展,添加了日志功能。 之所以会出现这种情况,是因为它被设计成既能向前兼容又能向后兼容。

日志记录是一种通过使恢复功能成为可能来记录对文件系统表单所做的所有更改的方法。 除了已经提到的特性之外,ext3还添加了其他特性;在这里,我指的是不检查文件系统中一致性的可能性,这主要是因为日志记录可以颠倒。 另一个重要特性是可以在不检查是否正确执行关机的情况下挂载它。 之所以会发生这种情况,是因为系统不需要在断电时执行一致性检查。

Ext4 是ext3的继任者,其构建理念是改进的性能和ext3中的存储限制。 它还向后兼容ext3ext2文件系统,并添加了许多功能:

  • 持久性预分配:它定义了可用于预分配空间的fallocate()系统调用,很可能是以连续的形式;它对于数据库和媒体流非常有用
  • 延迟分配:也称为刷新时分配;它用于从刷新磁盘数据的那一刻起延迟分配块,以减少碎片并提高性能
  • 多块分配:这是延迟分配的副作用,因为它允许数据缓冲,同时允许分配多个块。
  • 增加子目录限制:这个ext3有 32000 个子目录的限制,ext4没有这个限制,也就是说子目录的数量是不受限制的
  • 日志校验和:用于提高可靠性

日志闪存文件系统版本 2(JFFS2)是为 NAND 和 NOR 闪存设计的文件系统。 它在 2001 年被包括在 Linux 主线内核中,与ext3文件系统同年,尽管在不同的月份。 它是在 11 月份发布的 Linux 版本 2.4.15,JFFS2 文件系统是在 9 月份发布的 2.4.10 内核版本。 由于它特别用于支持闪存设备,因此它会考虑某些因素,例如处理小文件的需要,以及这些设备具有与之相关的磨损级别这一事实,这通过其设计解决并降低了这些问题。 虽然 JFFS2 是闪存的标准,但也有一些替代方案试图取代它,例如 LogFS、另一个闪存文件系统(YAFFS)和未排序块映像文件系统(UBIFS)。

除了前面提到的文件系统之外,还有一些伪文件系统可用,包括procsysfstmpfs。 在下一节中,我们将描述其中的前两个,最后一个将留给您自己去了解。

proc文件系统是 Linux 的第一个版本提供的虚拟文件系统。 它被定义为允许内核向用户提供有关正在运行的进程的信息,但随着时间的推移,它已经发展,现在不仅能够提供有关正在运行的进程的统计信息,而且还提供了调整有关内存、进程、中断等管理的各种参数的可能性。

随着时间的推移,proc虚拟文件系统成为 Linux 系统用户的必需品,因为它聚集了大量的用户空间功能。 没有它,命令(如toppsmount)将无法工作。 例如,给出的不带参数的mount示例将在/proc type proc (rw,noexec,nosuid,nodev)上以proc的形式呈现安装在/proc上的proc。 这是因为需要将proc挂载到与/etc/home等用作/proc文件系统目的地的目录相同的root文件系统上。 要挂载proc文件系统,可以使用与其他可用的文件系统类似的 mount–t proc nodev/procmount 命令。 有关这方面的更多信息,可以在Documentation/filesystems/proc.txt的内核源代码文档中找到。

proc文件系统具有以下结构:

  • 对于每个运行的进程,在/proc/<pid>内都有一个可用的目录,它包含有关打开的文件、已使用的内存、CPU 使用情况以及其他进程特定信息的信息。
  • 有关一般设备的信息可在/proc/devices/proc/interrupts/proc/ioports/proc/iomem中找到。
  • 内核命令行在/proc/cmdline内部可用。
  • 用于更改内核参数的文件在/proc/sys中可用。 Documentation/sysctl中还提供了更多信息。

sysfs文件系统用于表示物理设备。 它自 2.6 Linux 内核版本引入以来就可用,并提供将物理设备表示为内核对象并将设备驱动程序与相应设备相关联的可能性。 它对于udev和其他设备管理器等工具非常有用。

sysfs目录结构的每个主要系统设备类都有一个子目录,它还有一个系统总线子目录。 还有systool可用于浏览sysfs目录结构。 与 proc 文件系统类似,如果控制台上提供了sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)``mount命令,也可以看到systool。 可以使用mount -t sysfs nodev /sys命令挂载它。

备注

有关可用文件系统的更多信息,请参阅http://en.wikipedia.org/wiki/List_of_file_systems

了解 BusyBox

BusyBox 是 Bruce Perens 在 1999 年开发的,目的是将可用的 Linux 工具集成到单个可执行文件中。 它已经作为大量 Linux 命令行实用程序的替代品获得了巨大的成功。 正因为如此,以及它能够适用于小型嵌入式 Linux 发行版的事实,它在嵌入式环境中获得了极大的普及。 它提供来自文件交互的实用程序,如cpmkdirtouchlscat,以及通用实用程序,如dmesgkillfdiskmountumount等。

它不仅非常容易配置和编译,而且非常容易使用。 事实上,它是非常模块化的,并且提供高度的配置,这使得它成为使用的完美选择。 它可能不包括在您的主机 PC 上可用的成熟 Linux 发行版中可用的所有命令,但它包含的命令已经足够了。 此外,这些命令只是在实现级别使用的完整命令的简化版本,并且都集成在/bin/busybox中可用的单个可执行文件中,作为该可执行文件的符号链接。

开发人员与 BusyBox 源代码包的交互非常简单:只需配置、编译和安装它,就可以了。 以下是解释以下内容的一些详细步骤:

  • 运行配置工具并选择要提供的功能
  • 执行 makedep以构造依赖关系树
  • 使用make命令构建包

提示

在目标系统上安装可执行文件和符号链接。 有兴趣在其工作站上与该工具交互的用户应注意,如果该工具是为主机系统安装的,则安装应在不覆盖任何实用程序并启动主机可用的脚本的位置进行。

BusyBox 包的配置也有一个menuconfig选项,类似于内核和 U-Boot 可用的选项,即make``menuconfig。 它用于显示可用于更快配置和配置搜索的文本菜单。 要使此菜单可用,首先需要在调用 Makemenuconfig命令的系统上提供ncurses包。

在该过程结束时,BusyBox 可执行文件可用。 如果在不带参数的情况下调用它,它将显示与以下内容非常类似的输出:

Usage: busybox [function] [arguments]...
 or: [function] [arguments]...

 BusyBox is a multi-call binary that combines many common Unix
 utilities into a single executable.  Most people will create a
 link to busybox for each function they wish to use and BusyBox
 will act like whatever it was invoked as!

Currently defined functions:
 [, [[, arping, ash, awk, basename, bunzip2, busybox, bzcat, cat,
 chgrp, chmod, chown, chroot, clear, cp, crond, crontab, cut, date,
 dd, df, dirname, dmesg, du, echo, egrep, env, expr, false, fgrep,
 find, free, grep, gunzip, gzip, halt, head, hexdump, hostid, hostname,
 id, ifconfig, init, insmod, ipcalc, ipkg, kill, killall, killall5,
 klogd, length, ln, lock, logger, logread, ls, lsmod, md5sum, mesg,
 mkdir, mkfifo, mktemp, more, mount, mv, nc, "netmsg", netstat,
 nslookup, passwd, pidof, ping, pivot_root, poweroff, printf, ps,
 pwd, rdate, reboot, reset, rm, rmdir, rmmod, route, sed, seq,
 sh, sleep, sort, strings, switch_root, sync, sysctl, syslogd,
 tail, tar, tee, telnet, test, time, top, touch, tr, traceroute,
 true, udhcpc, umount, uname, uniq, uptime, vi, wc, wget, which,
 xargs, yes, zcat

它显示在配置阶段启用的实用程序列表。 要调用上述实用程序之一,有两个选项。 第一个选项需要使用 BusyBox 二进制文件和调用的实用程序数量(表示为./busybox ls),而第二个选项涉及使用目录中已有的符号链接,如/bin, /sbin, /usr/bin,等等。

除了已有的实用程序外,BusyBox 还提供了init程序的实现替代方案。 在这种情况下,init不知道运行级别及其在/etc/inittab文件中可用的所有配置。 与标准/etc/inittab文件不同的另一个因素是,这个文件也有其特殊的语法。 要了解更多信息,可以参考 BusyBox 内部提供的examples/inittab。 BusyBox 包中还实现了其他工具和实用程序,例如vi的轻量级版本,但我将让您自己了解它们。

最小根文件系统

现在,与root文件系统相关的所有信息都已经呈现给您,现在来描述最小的root文件系统的必备组件,这将是一个很好的练习。 这不仅有助于您更好地理解rootfs结构及其依赖项,还有助于满足引导时间和root文件系统的大小优化所需的要求。

描述组件的起点是/sbin/init;在这里,通过使用ldd命令可以找到运行时依赖项。 对于 Yocto 项目,ldd /sbin/init命令返回:

linux-gate.so.1 (0xb7785000)
libc.so.6 => /lib/libc.so.6 (0x4273b000)
/lib/ld-linux.so.2 (0x42716000)

根据该信息,定义/lib目录结构。 其最小形式为:

lib
|-- ld-2.3.2.so
|-- ld-linux.so.2 -> ld-2.3.2.so
|-- libc-2.3.2.so
'-- libc.so.6 -> libc-2.3.2.so

以下符号链接可确保库的向后兼容性和版本豁免性。 前面代码中的linux-gate.so.1文件是一个虚拟动态链接共享对象(vDSO),由内核在一个良好的位置公开。 可以找到它的地址因机器架构的不同而不同。

在此之后,必须定义init及其运行级别。 BusyBox 包中提供了这方面的最小形式,因此它也可以在/bin目录中使用。 此外,还需要一个用于 shell 交互的符号链接,因此 bin 目录的最小值如下所示:

bin
|-- busybox
'-- sh -> busybox

接下来,需要定义运行级别。 在最小的root文件系统中只使用一个,这不是因为它是一个严格的要求,而是因为它可以抑制一些 BusyBox 警告。 /etc目录的外观如下所示:

etc
'-- init.d
 '-- rcS

最后,控制台设备需要可供用户进行输入和输出操作,因此root文件系统的最后一部分位于/dev目录中:

dev
'-- console

在提到所有这些之后,最小的root文件系统似乎只有 5 个目录和 8 个文件。 它的最小大小不到 2MB,大约 80%的大小要归功于 C 库包。 还可以使用库优化器工具将其大小降至最小。 您可以在http://libraryopt.sourceforge.net/上找到有关这方面的更多信息。

约克托项目

转到 Yocto 项目,我们可以看看 core-Image-Minimal 来确定它的内容和最低要求,就像在 Yocto 项目中定义的那样。 core-image-minimal.bb图像位于meta/recipes-core/images目录中,如下所示:

SUMMARY = "A small image just capable of allowing a device to boot."

IMAGE_INSTALL = "packagegroup-core-boot ${ROOTFS_PKGMANAGE_BOOTSTRAP} ${CORE_IMAGE_EXTRA_INSTALL} ldd"

IMAGE_LINGUAS = " "

LICENSE = "MIT"

inherit core-image

IMAGE_ROOTFS_SIZE ?= "8192"

您可以在这里看到,这与任何其他食谱相似。 该图像定义LICENSE字段并继承定义其任务的bbclass文件。 用一个简短的总结来描述它,它与普通的包装食谱有很大的不同。 它没有LIC_FILES_CHKSUM来检查许可证或SRC_URI 字段,主要是因为它不需要它们。 反过来,该文件定义了应该包含在root文件系统中的确切包,并且其中一些包被分组到packagegroup中以便于处理。 此外,core-image bbclass文件定义了许多其他任务,例如do_rootfs,它仅特定于图像食谱。

构建root文件系统对任何人来说都不是一件容易的任务,但 Yocto 做得更成功一些。 它从用于根据根据文件系统层次结构标准(FHS)制定目录结构的基本文件配方开始,并与之一起放置了许多其他配方。 此信息在./meta/recipes-core/packagegroups/packagegroup-core-boot.bb配方中提供。 从前面的示例中可以看到,它还继承了一种不同的类,如packagegroup.bbclass,这是所有可用的包组的要求。 然而,最重要的因素是,它清楚地定义了构成packagegroup的包。 在我们的示例中,核心引导包组包含包,如base-filesbase-passwd(包含基本系统主密码和组文件)、udevbusyboxsysvinit(类似于 init 的系统 V)。

从前面显示的文件中可以看到,BusyBox 包是 Yocto 项目生成的发行版的核心组件。 尽管有关于 BusyBox 可以提供 init 替代方案的信息,但是默认的 Yocto 生成的发行版不使用它。 取而代之的是,他们选择使用类似 system V 的 init,这与基于 Debian 的发行版类似。 尽管如此,通过meta/recipes-core/busybox位置内的 BusyBox 配方提供了许多 shell 交互工具。 对于对增强或删除busybox包提供的某些特性感兴趣的用户,将使用与 Linux 内核配置相同的概念。 busybox包使用一个defconfig文件,在该文件上应用了许多配置片段。 这些片段可以添加或删除功能,最终获得最终的配置文件。 这标识了root文件系统中可用的最终功能。

在 Yocto 项目中,可以通过使用poky-tiny.conf 分发策略来最小化root文件系统的大小,这些策略在meta-yocto/conf/distro目录中可用。 使用这些策略时,不仅可以减少引导大小,还可以缩短引导时间。 最简单的例子是使用qemux86机器。 在这里,更改是可见的,但它们与最小根文件系统部分中已经提到的更改略有不同。 在qemux86上所做的最小化工作的目的是围绕核心图像最小图像进行的。 它的目标是将生成的rootfs的大小减少到 4MB 以下,引导时间减少到 2 秒以下。

现在,移动到选定的 Atmel SAMA5D3 XPlaed 机器,生成另一个rootfs,其内容相当大。 它不仅包括packagegroup-core-boot.bb包组,还包括其他包组和单独的包。 一个这样的例子是recipes-core/images目录中的meta-atmel层内的atmel-xplained-demo-image.bb图像:

DESCRIPTION = "An image for network and communication."
LICENSE = "MIT"
PR = "r1"

require atmel-demo-image.inc

IMAGE_INSTALL += "\
    packagegroup-base-3g \
    packagegroup-base-usbhost \
    "

在此图像中,还继承了另一个更通用的图像定义。 这里,我指的是atmel-demo-image.inc文件,当打开时,您可以看到它包含所有meta-atmel层图像的核心。 当然,如果所有可用的包都不够用,开发人员可以决定添加他们自己的包。 开发人员面前有两种可能性:创建一个新的映像,或者将包添加到已有的映像中。 最终结果是使用bitbake atmel-xplained-demo-image命令构建的。 输出有多种形式,它们高度依赖于所定义的机器的要求。 在构建过程结束时,输出将用于引导实际主板上的根文件系统。

摘要

在本章中,您了解了 Linuxrootfs的一般知识,还了解了与 Linux 内核组织的通信、Linuxrootfs、其原理、内容和设备驱动程序。 由于通信往往会随着时间的推移而变得更大,因此我们还向您介绍了有关最小文件系统应该是什么样子的信息。

除了这些信息,在下一章中,您还将对 Yocto 项目的可用组件进行概述,因为它们中的大多数都在 POKY 之外。 还将向您介绍每个组件,并简要介绍它们的要点。 在本章之后,我们将向您介绍并详细说明其中的一些内容。