LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

OpenWRT(19):根文件系统挂载mount_root

参考《[OpenWrt Wiki] The OpenWrt Flash Layout》、《[OpenWrt Wiki] Extroot configuration》。

1 mount_root概要

  • mount_root函数在系统启动的早期阶段被调用,以确保系统能够访问一个可写的根文件系统。
  • mount_root处理不同存储介质文件系统:ramfs、EXT4、SQUASHFS、UBIFS等。
  • mount_root支持overlay文件系统,来组合只读的系统文件(如内核和初始 ramdisk)与可写的用户数据。
  • 如果设备配置了外置根文件系统,mount_root 需要能够识别并切换到这个外置存储,以便于系统从更大的存储设备启动。
  • mount_root确保文件系统的一致性和完整性,防止因文件系统错误导致的启动失败。

2 mount_root执行时机

mount_root在/lib/preinit/80_mount_root中开始,在/etc/init.d/done中结束:

参考《OpenWRT(4):启动流程以及添加自己的服务》,do_mount_root在执行boot_run_hook preinit_main时执行。

do_mount_root() {
        mount_root
        boot_run_hook preinit_mount_root
...
}

在/etc/init.d/done中结束:

START=95
boot() {
        mount_root done
        rm -f /sysupgrade.tgz && sync
...
}

3 mount_root细节

mount_root根据rootdev文件系统类型进行root挂载处理,这里以SQUASHFS+FS_EXT4为例:

  • 获取根目录对应设备,读取非mtdblock或ubiblock的rootdev设备的第一个squashfs超级块,判断是否为squashfs。进而获取到rootfs_data rootdev_volume。
  • 在rootdev剩余空间创建rootfs_data,ext4格式文件系统。
  • 挂载rootfs_data区域。
  • 将rootfs_data区域overlay到rootfs上,形成新的rootfs,切换到新的rootfs运行。

最终形成rootdev只读squashfs部分,加上剩余ext4可读写部分组合成的rootfs。在ext4损坏的情况下,squashfs部分任然可运行,进行异常处理。

main
  start
    volume_find--找到rootfs_data对应的驱动。
      rootdisk_volume_find
        get_rootdev--获取根目录对应的设备。
        get_squashfs--读取rootdev对应的设备,读取一个struct squashfs_super_block结构体。
        --hsqs是suqshfs的魔术字,比较squashfs_super_block->s_magic是否为hsqs,否则异常。
        --获取squashfs使用的空间大小,作为rootfs_data的offset;填充struct rootdev_volume。
    mount_extroot
    volume_init--
      rootdisk_volume_init
        rootdisk_create_loop--打开/dev/loop0设备,指向rootdev - rootfs区域。
          open--打开rootdev和/dev/loop0
          LOOP_GET_STATUS64--获取循环设备的状态信息。
          LOOP_SET_FD--将/dev/loop0句柄指向rootdev,对loop的操作转向rootdev。原来是空设备,现在变成rootdev大小。
          LOOP_SET_STATUS64--加上rootfs.squash大小作为偏移,/dev/loop0对应的存储大小 = rootdev - rootfs.squash。
          LOOP_CLR_FD--清除循环设备的文件描述符。
        rootdisk_volume_identity
        system(mkfs.ext4 -q -L rootfs_data %s)--在/dev/loop0上创建ext4文件系统。
    volume_identify--返回文件系统类型。
      rootdisk_volume_identify
    FS_NONE
      ramoverlay
    FS_EXT4/FS_F2FS/FS_JFFS2/FS_UBIFS
      mount_overlay
        find_mount_point--查找当前设备/dev/loop0是否已经mount。
        overlay_mount_fs
          mkdir--创建/tmp/overlay目录。
          mount--将/dev/loop0以ext4格式挂载到/tmp/overlay。
      mount_extroot--检查/tmp/overlay中是否有extroot。
      fs_state_get--查看链接/tmp/overlay/.fs_state,查看挂载点/tmp/overlay的文件系统状态。
        FS_STATE_UNKNOWN
        FS_STATE_PENDING
          overlay_delete--删除/tmp/overlay下所有不包含sysupgrade.tgz的目录。
        FS_STATE_READY--说明FS已经就绪。
      overlay_fs_name--获取volume的文件系统类型。
      mount_move
        mount--将/tmp/overlay挂载点移动到/overlay。此时/overlay挂载点对应/dev/loop0设备。
      fopivot
        mkdir--创建/overlay/upper和/overlay/work两个目录。
        mount--将/overlay/upper目录overlay到/目录,挂载点为/mnt。即将/dev/loop0设备的upper目录,overlay到rootdev的/目录。overlay结果为/mnt。
        pivot
          mount_move--将/proc目录修改为/mnt/proc。
          pivot_root--将/mnt作为新的根文件系统,将原根文件系统移动到/mnt/rom。
          mount_move--将/rom/tmp移动到/tmp。
          mount_move--将/rom/sys移动到/sys。
          mount_move--将/rom/overlay移动到/overlay。
    FS_SNAPSHOT
      mount_snapshot
  done
    fs_state_set
      symlink--将/overlay/.fs_state连接到FS_STATE_READY表示的值。

3.1 /dev/loop设备

loop设备ioctl命令:

  • LOOP_GET_STATUS64--获取循环设备的状态信息。
  • LOOP_SET_FD--设置循环设备的文件描述符。可以将一个文件描述符与循环设备关联起来。这意味着循环设备将使用这个文件描述符指向的文件作为其数据源。
  • LOOP_SET_STATUS64--设置循环设备的状态信息。用于设置循环设备的状态,包括关联的文件描述符、偏移量和设备长度。
  • LOOP_CLR_FD--清除循环设备的文件描述符。

3.2 squashfs超级块结构体

squashfs超级块结构体:

struct squashfs_super_block {
__le32 s_magic; // 文件系统的魔术数字
__le32 inodes; // 文件系统中的 inode 数量
__le32 blocks; // 文件系统中的块数量
__le32 block_size; // 块大小
__le32 flags; // 文件系统标志
// 其他字段...
};

其中s_magic应为:#define SQUASHFS_MAGIC 0x73717368,即hsqs。

3.3 pivot_root

pivot_root是Linux内核提供的一个系统调用,用于改变当前进程的根文件系统。在启动过程中,为了切换到一个新的文件系统环境而将当前根文件系统移动到其他位置。

函数原型如下:

int pivot_root(const char* new_root, const char* put_old);

参数说明:

1. new_root:新的根文件系统路径。这个路径应该是当前根文件系统下的一个目录,pivot_root调用将会把这个目录变为新的根目录。

2. put_old:原来的根文件系统将被移动到这个目录下。这个参数指定了一个新的路径,用于存放旧的根文件系统。

返回值:如果操作成功,返回0;如果操作失败,返回-1,并设置errno以指示错误原因。

使用pivot_root的典型场景是在系统启动时,初始化系统需要切换到一个全新的文件系统环境,例如从初始RAM磁盘(initrd)切换到真实的磁盘文件系统。

一个简单的例子:

#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <errno.h>

int main()
{   
if(syscall(SYS_pivot_root, "/newroot","/oldroot"))
  {     perror(
"pivot_root failed");     return 1;   }
//成功切换根目录后的操作 return 0; }

在这个例子中,/oldroot是当前根目录下的一个目录,它将被移动到/成为新的根目录,而原来的根目录/将被移动到/oldroot下。注意,这个操作需要root权限,并且通常只在系统启动的早期阶段由初始化系统调用。

请注意,pivot_root调用后,旧的根文件系统将不再被使用,所有对文件系统的访问都将相对于新的根目录进行。因此,调用pivot_root需要谨慎,以确保系统能够正常启动和运行。

posted on 2024-10-18 23:59  ArnoldLu  阅读(8)  评论(0编辑  收藏  举报

导航