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超级块结构体
参考《3. Squashfs Filesystem Design》,squashfs可能按照如下格式组成:
--------------- | superblock | |---------------| | compression | | options | |---------------| | datablocks | | & fragments | |---------------| | inode table | |---------------| | directory | | table | |---------------| | fragment | | table | |---------------| | export | | table | |---------------| | uid/gid | | lookup table | |---------------| | xattr | | table | ---------------
squashfs超级块结构体:
struct squashfs_super_block { __le32 s_magic;--魔术数字,用于识别SquashFS文件系统。 __le32 inodes;--文件系统中inode的总数。 __le32 mkfs_time;--创建文件系统的时间戳(UNIX时间)。 __le32 block_size;--文件系统的块大小,单位为字节。 __le32 fragments;--文件系统中碎片的数量。 __le16 compression;--压缩算法的标识符。 __le16 block_log;--块大小的对数(以2为底)。 __le16 flags;--文件系统的属性或行为标志。 __le16 no_ids;--用户和组ID的数量。 __le16 s_major;--文件系统的主要版本号。 __le16 s_minor;--文件系统的次要版本号。 __le64 root_inode;--根目录的inode编号。 __le64 bytes_used;--文件系统中已使用的总字节数。 __le64 id_table_start;--用户和组ID查找表的起始位置。 __le64 xattr_id_table_start;--扩展属性ID查找表的起始位置。 __le64 inode_table_start;--inode表的起始位置。 __le64 directory_table_start;--目录表的起始位置。 __le64 fragment_table_start;--碎片表的起始位置。 __le64 lookup_table_start;--查找表的起始位置,用于快速查找inode。 };
其中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需要谨慎,以确保系统能够正常启动和运行。
3.4 ext4超级块
使用dumpe2fs查看超级块内容:
Filesystem volume name: data Last mounted on: /home/al/data Filesystem UUID: 229943d5-2614-42f3-8a50-e9cb9a332635 Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum Filesystem flags: signed_directory_hash Default mount options: user_xattr acl Filesystem state: clean Errors behavior: Continue Filesystem OS type: Linux Inode count: 55205888 Block count: 220821760 Reserved block count: 11041088 Overhead clusters: 3747479 Free blocks: 182111151 Free inodes: 51230408 First block: 0 Block size: 4096 Fragment size: 4096 Group descriptor size: 64 Reserved GDT blocks: 1024 Blocks per group: 32768 Fragments per group: 32768 Inodes per group: 8192 Inode blocks per group: 512 Flex block group size: 16 Filesystem created: Thu Aug 1 15:26:44 2024 Last mount time: Thu Oct 10 20:21:04 2024 Last write time: Wed Oct 30 15:37:20 2024 Mount count: 11 Maximum mount count: -1 Last checked: Thu Aug 1 15:26:44 2024 Check interval: 0 (<none>) Lifetime writes: 697 GB Reserved blocks uid: 0 (user root) Reserved blocks gid: 0 (group root) First inode: 11 Inode size: 256 Required extra isize: 32 Desired extra isize: 32 Journal inode: 8 Default directory hash: half_md4 Directory Hash Seed: 0e2b523a-2217-449b-89a7-de1ef0b01fac Journal backup: inode blocks Checksum type: crc32c Checksum: 0xd80760e6 Journal features: journal_incompat_revoke journal_64bit journal_checksum_v3 Total journal size: 1024M Total journal blocks: 262144 Max transaction length: 262144 Fast commit length: 0 Journal sequence: 0x00003da2 Journal start: 32454 Journal checksum type: crc32c Journal checksum: 0x7e39b6d3 Group 0: (Blocks 0-32767) csum 0x3b98 [ITABLE_ZEROED] ...
ext4的struct ext4_super_block定义如下:
struct ext4_super_block { /*00*/ __le32 s_inodes_count; /* Inodes count */--文件系统中inode的总数。 __le32 s_blocks_count_lo; /* Blocks count */--文件系统中块的总数的低32位。 __le32 s_r_blocks_count_lo; /* Reserved blocks count */--文件系统保留的块总数的低32位。 __le32 s_free_blocks_count_lo; /* Free blocks count */--文件系统中未分配的块总数的低32位。 /*10*/ __le32 s_free_inodes_count; /* Free inodes count */--文件系统中未分配的inode总数。 __le32 s_first_data_block; /* First Data Block */--文件系统的第一个数据块。 __le32 s_log_block_size; /* Block size */--块大小的对数值。 __le32 s_log_cluster_size; /* Allocation cluster size */--分配簇大小的对数值。 /*20*/ __le32 s_blocks_per_group; /* # Blocks per group */--每个块组包含的块数。 __le32 s_clusters_per_group; /* # Clusters per group */--每个块组包含的簇数。 __le32 s_inodes_per_group; /* # Inodes per group */--每个块组包含的inode数。 __le32 s_mtime; /* Mount time */--最后一次挂载时间。 /*30*/ __le32 s_wtime; /* Write time */--最后一次写入时间。 __le16 s_mnt_count; /* Mount count */--挂载次数。 __le16 s_max_mnt_count; /* Maximal mount count */--允许的最大挂载次数。 __le16 s_magic; /* Magic signature */--文件系统魔术数字,用于识别文件系统类型。 __le16 s_state; /* File system state */--文件系统状态,指示是否清洁。 __le16 s_errors; /* Behaviour when detecting errors */--检测到错误时的行为。 __le16 s_minor_rev_level; /* minor revision level */--次版本号。 /*40*/ __le32 s_lastcheck; /* time of last check */--上次检查的时间。 __le32 s_checkinterval; /* max. time between checks */--最大检查间隔时间。 __le32 s_creator_os; /* OS */--创建文件系统的操作系统。 __le32 s_rev_level; /* Revision level */--版本号。 /*50*/ __le16 s_def_resuid; /* Default uid for reserved blocks */--默认的保留块的用户ID。 __le16 s_def_resgid; /* Default gid for reserved blocks */--默认的保留块的组ID。 __le32 s_first_ino; /* First non-reserved inode */--第一个非保留inode。 __le16 s_inode_size; /* size of inode structure */--inode结构的大小。 __le16 s_block_group_nr; /* block group # of this superblock */--包含此超级块的块组编号。 __le32 s_feature_compat; /* compatible feature set */--兼容特性集。 /*60*/ __le32 s_feature_incompat; /* incompatible feature set */--不兼容特性集。 __le32 s_feature_ro_compat; /* readonly-compatible feature set */--只读兼容特性集。 /*68*/ __u8 s_uuid[16]; /* 128-bit uuid for volume */--卷的128位UUID。 /*78*/ char s_volume_name[EXT4_LABEL_MAX] __nonstring; /* volume name */--卷的名称。 /*88*/ char s_last_mounted[64] __nonstring; /* directory where last mounted */--最后一次挂载的目录。 /*C8*/ __le32 s_algorithm_usage_bitmap; /* For compression */--用于压缩的算法位图。 __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate */--尝试预分配的块数。 __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */--目录预分配的块数。 __le16 s_reserved_gdt_blocks; /* Per group desc for online growth */--每个组保留的GDT块数,用于在线增长。 /*D0*/ __u8 s_journal_uuid[16]; /* uuid of journal superblock */--日志超级块的UUID。 /*E0*/ __le32 s_journal_inum; /* inode number of journal file */--日志文件的inode编号。 __le32 s_journal_dev; /* device number of journal file */--日志文件的设备编号。 __le32 s_last_orphan; /* start of list of inodes to delete */--待删除的inode列表的开始。 __le32 s_hash_seed[4]; /* HTREE hash seed */--HTREE哈希种子。 __u8 s_def_hash_version; /* Default hash version to use */--默认使用的哈希版本。 __u8 s_jnl_backup_type; __le16 s_desc_size; /* size of group descriptor */--组描述符的大小。 /*100*/ __le32 s_default_mount_opts; __le32 s_first_meta_bg; /* First metablock block group */--第一个元数据块组。 __le32 s_mkfs_time; /* When the filesystem was created */--文件系统创建的时间。 __le32 s_jnl_blocks[17]; /* Backup of the journal inode */--日志inode的备份。 /*150*/ __le32 s_blocks_count_hi; /* Blocks count */--块的总数的高32位。 __le32 s_r_blocks_count_hi; /* Reserved blocks count */--保留块的总数的高32位。 __le32 s_free_blocks_count_hi; /* Free blocks count */--未分配块的总数的高32位。 __le16 s_min_extra_isize; /* All inodes have at least # bytes */--所有inode至少有这么多字节。 __le16 s_want_extra_isize; /* New inodes should reserve # bytes */--新inode应该保留这么多字节。 __le32 s_flags; /* Miscellaneous flags */--杂项标志。 __le16 s_raid_stride; /* RAID stride */--RAID步长。 __le16 s_mmp_update_interval; /* # seconds to wait in MMP checking */--MMP检查等待的时间(秒)。 __le64 s_mmp_block; /* Block for multi-mount protection */--多挂载保护的块。 __le32 s_raid_stripe_width; /* blocks on all data disks (N*stride)*/--所有数据盘上的块数(N*步长)。 __u8 s_log_groups_per_flex; /* FLEX_BG group size */--FLEX_BG组的大小。 __u8 s_checksum_type; /* metadata checksum algorithm used */--使用的元数据校验和算法。 __u8 s_encryption_level; /* versioning level for encryption */--加密的版本级别。 __u8 s_reserved_pad; /* Padding to next 32bits */--填充至下一个32位。 __le64 s_kbytes_written; /* nr of lifetime kilobytes written */--生命周期内写入的千字节数。 __le32 s_snapshot_inum; /* Inode number of active snapshot */--活动快照的inode编号。 __le32 s_snapshot_id; /* sequential ID of active snapshot */--活动快照的序列号。 __le64 s_snapshot_r_blocks_count; /* reserved blocks for active snapshot's future use */--为活动快照未来使用的保留块。 __le32 s_snapshot_list; /* inode number of the head of the on-disk snapshot list */--磁盘上快照列表头部的inode编号。 #define EXT4_S_ERR_START offsetof(struct ext4_super_block, s_error_count) __le32 s_error_count; /* number of fs errors */--文件系统错误数量。 __le32 s_first_error_time; /* first time an error happened */--第一次错误发生的时间。 __le32 s_first_error_ino; /* inode involved in first error */--第一次错误涉及的inode。 __le64 s_first_error_block; /* block involved of first error */--第一次错误涉及的块。 __u8 s_first_error_func[32] __nonstring; /* function where the error happened */--错误发生的函数。 __le32 s_first_error_line; /* line number where error happened */--错误发生的代码行号。 __le32 s_last_error_time; /* most recent time of an error */--最近一次错误的时间。 __le32 s_last_error_ino; /* inode involved in last error */--最近一次错误涉及的inode。 __le32 s_last_error_line; /* line number where error happened */--最近一次错误发生的代码行号。 __le64 s_last_error_block; /* block involved of last error */--最近一次错误涉及的块。 __u8 s_last_error_func[32] __nonstring; /* function where the error happened */--最近一次错误发生的函数。 #define EXT4_S_ERR_END offsetof(struct ext4_super_block, s_mount_opts) __u8 s_mount_opts[64]; __le32 s_usr_quota_inum; /* inode for tracking user quota */--用于跟踪用户配额的inode。 __le32 s_grp_quota_inum; /* inode for tracking group quota */--用于跟踪组配额的inode。 __le32 s_overhead_clusters; /* overhead blocks/clusters in fs */--文件系统中的开销块/簇。 __le32 s_backup_bgs[2]; /* groups with sparse_super2 SBs */--包含sparse_super2超级块的组。 __u8 s_encrypt_algos[4]; /* Encryption algorithms in use */--使用的加密算法。 __u8 s_encrypt_pw_salt[16]; /* Salt used for string2key algorithm */--string2key算法使用的盐值。 __le32 s_lpf_ino; /* Location of the lost+found inode */--lost+found目录的inode位置。 __le32 s_prj_quota_inum; /* inode for tracking project quota */--用于跟踪项目配额的inode。 __le32 s_checksum_seed; /* crc32c(uuid) if csum_seed set */--如果设置了csum_seed,则为crc32c(uuid)。 __u8 s_wtime_hi; __u8 s_mtime_hi; __u8 s_mkfs_time_hi; __u8 s_lastcheck_hi; __u8 s_first_error_time_hi; __u8 s_last_error_time_hi; __u8 s_first_error_errcode; __u8 s_last_error_errcode; __le16 s_encoding; /* Filename charset encoding */--文件名字符集编码。 __le16 s_encoding_flags; /* Filename charset encoding flags */--文件名字符集编码标志。 __le32 s_orphan_file_inum; /* Inode for tracking orphan inodes */--用于跟踪孤立inode的inode。 __le32 s_reserved[94]; /* Padding to the end of the block */--填充至块的末尾。 __le32 s_checksum; /* crc32c(superblock) */--超级块的crc32c校验和。 };
文件系统的块大小在超级块(ext4_super_block)中以s_log_block_size字段表示,其值是对数形式的,实际块大小计算公式为2^(s_log_block_size + 10)。例如,如果s_log_block_size的值为2,则块大小为2^(2+10) = 4096字节,即4KB。
此设备的Sector大小为512B:
Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: 26DB9C31-D3E1-4FF9-828E-15A8BD0E4E8F Device Start End Sectors Size Type /dev/nvme0n1p1 2048 1050623 1048576 512M EFI System /dev/nvme0n1p2 1050624 1767624703 1766574080 842.4G Linux filesystem /dev/nvme0n1p3 1767624704 2000406527 232781824 111G Linux filesystem
所以总大小为:1,766,574,080 * 512 = 904,485,928,960。
但是在系统中硬件block还是按照1K大小进行处理的:
major minor #blocks name
259 0 1000204632 nvme0n1
259 1 524288 nvme0n1p1
259 2 883287040 nvme0n1p2--883,287,040 * 1024 = 904,485,928,960。
259 3 116390912 nvme0n1p3
对于块大小为1KB的ext4文件系统,超级块位于第1个块。对于块大小为4KB的ext4文件系统,超级块位于第2个块(0x400)。
所以4KB块的ext4文件系统的超级块如下: