【UEFI实战】EXT2读驱动

总体描述

从EXT2文件系统的角度来看磁盘——在BIOS下,不应该认为是磁盘,而是通过Block IO表示的一个磁盘分区——的构成是这样的:

启动块块组0块组1块组N

关于块的有两种不同的含义:

  1. 硬件上表示的是磁盘本身的块,它的大小可通过BlockIo->Media->BlockSize得到;
  2. EXT2文件系统中有一个软件上的块的概念:EXT2文件系统是基于块的文件系统,它将硬盘划分为若干块,每个块的长度都相同,按块管理元数据和文件系统。

EXT2的(软件)块会受到底层磁盘的块大小的影响,可以的取值有1024字节、2048字节和4096字节,这跟磁盘的大小并不对应,如果磁盘块大小是512字节,那么EXT2的块大小通常会取值1024字节,当然这并不是强制的,构建EXT2文件系统时可以根据实际的情况修改软件块大小,毕竟,跟磁盘块大小这种硬件相关的值不同,EXT2的块大小是软件的概念,后续如果没有特别说明,指的都是EXT2软件块。EXT2的块大小还会影响到文件系统中的最大文件长度:

块大小最大文件长度
102416GiB
2048256GiB
40962TiB

另外,一个块(后续实现中都按1024个字节(1KB)为准)最多只会存放一个文件的数据,如果一个文件的数据大于一个块,最终文件数据占据的块也会向上取整。

启动块属于磁盘分区结构的一部分,而块组0~块组N占据分区的其它部分,它们是这里研究的重点,块组的大小也是可以设定,一般是8092个块构成一个块组。块组的分布如下所示:

超级块组描述符数据块位图Inode位图Inode表数据块

总体上来看一个Block IO表示的磁盘分区的结构如下图所示:

在这里插入图片描述

块组中各个结构的作用如下:

  • 超级块:用于存储文件系统自身元数据的核心结构,超级块理论上在所有的块组中都存在,不过只使用第一个块组中的超级块即可,之所以这样冗余设计,是为了一个超级块被破坏时文件系统不至于穗槐。超级块通常位于分区的第二个块(因为第一块是启动块)。
  • 组描述符:每个块组都有一个组描述符的集合,紧随超级块之后,其中保存的信息反映了文件系统中每个块组的内容,因此不仅关系到当前块组的数据块,还与其它块组的数据块和Inode块相关。占据的块跟块大小,块组个数和组描述符长度有关。
  • 数据块位图Inode位图:用于保存一长串的比特位,每个比特位都对应于一个数据块或者Inode,用于表示对应的数据块或Inode是否空闲。分别占据1个块。
  • Inode表:Inode用于保存文件系统中与各个文件和目录相关的元数据,每个文件或目录都有且只有一个对应的Inode,其中包含元数据(如访问权限、上次修改的日志,等等,但不包含文件名)和指向文件数据的指针;占据若干个块。
  • 数据块:包含了文件系统中的文件的有用数据,占据剩余的块。

创建测试磁盘

通过上述的数据结构信息,可以编写代码来读取EXT2文件系统的信息,不过首先需要创建一个EXT2的文件系统,可以使用DiskGenius来完成:

在这里插入图片描述

通过上图中文件系统的详细信息,结合BIOS的基础,可以确定以下信息:

  • BIOS启动后会对磁盘安装Block IO,也会对磁盘上的分区安装Block IO,编写代码时需要关注的是分区对应的Block IO,它相对于磁盘的起始位置通过“起始扇区号”可以确定是2048,再加上”扇区大小“是512字节,所以分区所在的Block IO描述的存储空间对于硬盘来说偏移是2048 * 512 = 1048576 = 0x100000字节,从DiskGenius可以查看该位置的信息:

在这里插入图片描述

  • 分区存储空间的开始就是第一个块是启动块,如上图所示暂时还是0,对于UEFI BIOS来说,并不需要这个内容。
  • 启动分区占据一个块,而之后就是块组,块组中的第一个块就是超级块,启动块的大小是一个快(1024字节),所以超级块的起始位置是0x100400:

在这里插入图片描述

关于这两个地址可以通过宏来表示:

#define BBSIZE      1024
#define SBSIZE      1024
#define BBOFF       ((UINTN)(0))
#define SBOFF       ((UINTN)(BBOFF + BBSIZE))

于是为了判断某个Block IO中是否存在EXT2文件系统,第一步需要做的就是从上述位置获取1024个字节的数据,并判断它是否是一个超级块。

不过DiskGenius不能写文件,所以如果要往里面存放文件,则需要使用Linux创建EXT2版本的disk.img文件,具体操作如下:

# dd if=/dev/zero of=disk.img count=200 bs=1M
200+0 records in
200+0 records out
209715200 bytes (210 MB, 200 MiB) copied, 0.113229 s, 1.9 GB/s
# mkfs.ext4 disk.img 
mke2fs 1.44.1 (24-Mar-2018)
Discarding device blocks: done                            
Creating filesystem with 204800 1k blocks and 51200 inodes
Filesystem UUID: 513c5206-7726-4dba-8bb1-fbf7b68c449c
Superblock backups stored on blocks: 
	8193, 24577, 40961, 57345, 73729

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done 
# mkdir loop
# mount -o loop disk.img loop/
# cd loop
# ls
lost+found
# mkdir tmp1
# mkdir tmp2
# echo hello1 >> tmp1.log
# echo hello2 >> tmp2.log
# cd ..
# umount loop

这里创建了一个大小是200MB的虚拟磁盘disk.img,格式化成EXT4文件系统,并增加了tmp1和tmp2两个目录,tmp1.log和tmp2.log两个文件。

超级块

一个正常超级块包含的数据如下,首先是EXT2需要支持的:

起始结束大小描述
034Total number of inodes in file system.
474Total number of blocks in file system.
8114Number of reserved blocks.
12154Total number of unallocated blocks.
16194Total number of unallocated inodes.
20234Block number of the block containing the superblock. This is 1 on 1024 byte block size filesystems, and 0 for all others.
24274log2 (block size) - 10 (In other words, the number to shift 1,024 to the left by to obtain the block size).
28314log2 (fragment size) - 10 (In other words, the number to shift 1,024 to the left by to obtain the fragment size).
32354Number of blocks in each block group.
36394Number of fragments in each block group.
40434Number of inodes in each block group.
44474Last mount time (in POSIX time).
48514Last written time (in POSIX time).
52532Number of times the volume has been mounted since its last consistency check (fsck).
54552Number of mounts allowed before a consistency check (fsck) must be done.
56572Magic signature (0xef53), used to help confirm the presence of Ext4 on a volume.
58592File system state.
60612What to do when an error is detected.
62632Minor portion of version (combine with Major portion below to construct full version field).
64674POSIX time of last consistency check (fsck).
68714Interval (in POSIX time) between forced consistency checks (fsck).
72754Operating system ID from which the filesystem on this volume was created (see below).
76794Major portion of version (combine with Minor portion above to construct full version field).
80812User ID that can use reserved blocks.
82832Group ID that can use reserved blocks.

其次是如果要支持EXT4动态超级块的话,则还有额外的内容:

起始结束大小描述
84874First non-reserved inode in file system.
88892Size of each inode structure in bytes.
90912Block group that this superblock is part of for backup copies.
92954Optional features present.
96994Required features present.
1001034Features that if not supported the volume must be mounted read-only.
10411916File system UUID.
12013516Volume name.
13619964Path Volume was last mounted to.
2002034Compression algorithm used.
2042041Amount of blocks to preallocate for files
2052051Amount of blocks to preallocate for directories.
2062072Amount of reserved GDT entries for filesystem expansion.
20822316Journal UUID.
2242274Journal Inode.
2282314Journal Device number.
2322354Head of orphan inode list.
23625116HTREE hash seed in an array of 32 bit integers.
2522521Hash algorithm to use for directories.
2532531Journal blocks field contains a copy of the inode’s block array and size.
2542552Size of group descriptors in bytes, for 64 bit mode.
2562594Mount options.
2602634First metablock block group, if enabled.
2642674Filesystem Creation Time.
26833568Journal Inode Backup in an array of 32 bit integers.

此外如果开启了64位支持,则超级块的内容还要更多,结构体数据最大占据1024个字节,一般代码中直接设定超级块的大小是1024个字节,后面的数据:

起始结束大小描述
3363394High 32-bits of the total number of blocks.
3403434High 32-bits of the total number of reserved blocks.
3443474High 32-bits of the total number of unallocated blocks.
3483492Minimum inode size.
3503512Minimum inode reservation size.
3523554Misc flags, such as sign of directory hash or development status.
3563572Amount logical blocks read or written per disk in a RAID array.
3583592Amount of seconds to wait in Multi-mount prevention checking.
3603678Block to multi-mount prevent.
3683714Amount of blocks to read or write before returning to the current disk in a RAID array. Amount of disks * stride.
3723721log2 (groups per flex) - 10. (In other words, the number to shift 1,024 to the left by to obtain the groups per flex block group)
3733731Metadata checksum algorithm used. Linux only supports crc32.
3743741Encryption version level.
3753751Reserved padding.
3763838Amount of kilobytes written over the filesystem’s lifetime.
3843874Inode number of the active snapshot.
3883914Sequential ID of active snapshot.
3923998Number of blocks reserved for active snapshot.
4004034Inode number of the head of the disk snapshot list.
4044074Amount of errors detected.
4084114First time an error occurred in POSIX time.
4124154Inode number in the first error.
4164238Block number in the first error.
42445532Function where the first error occurred.
4564594Line number where the first error occurred.
4604634Most recent time an error occurred in POSIX time.
4644674Inode number in the last error.
4684758Block number in the last error.
47650732Function where the most recent error occurred.
5085114Line number where the most recent error occurred.
51257564Mount options. (C-style string: characters terminated by a 0 byte)
5765794Inode number for user quota file.
5805834Inode number for group quota file.
5845874Overhead blocks/clusters in filesystem. Zero means the kernel calculates it at runtime.
5885958Block groups with backup Superblocks, if the sparse superblock flag is set.
5965994Encryption algorithms used, as a array of unsigned char.
60061516Salt for the string2key algorithm.
6166194Inode number of the lost+found directory.
6206234Inode number of the project quota tracker.
6246274Checksum of the UUID, used for the checksum seed. (crc32c(~0, UUID))
6286281High 8-bits of the last written time field.
6296291High 8-bits of the last mount time field.
6306301High 8-bits of the Filesystem creation time field.
6316311High 8-bits of the last consistency check time field.
6326321High 8-bits of the first time an error occurred time field.
6336331High 8-bits of the latest time an error occurred time field.
6346341Error code of the first error.
6356351Error code of the latest error.
6366372Filename charset encoding.
6386392Filename charset encoding flags.
6401019380Padding.
102010234Checksum of the superblock.

不过这里暂时不管64位特性,所以对应的超级块结构体代码如下:

//
// EXT2文件系统中的超级块,大小是1024个字节
//
typedef struct {
  UINT32   Ext2FsInodeCount;        //   0: Inode数据
  UINT32   Ext2FsBlockCount;        //   4: 块数据
  UINT32   Ext2FsRsvdBlockCount;    //   8: 已分配块的数据
  UINT32   Ext2FsFreeBlockCount;    //  12: 空闲块数目
  UINT32   Ext2FsFreeInodeCount;    //  16: 空闲Inode数据
  UINT32   Ext2FsFirstDataBlock;    //  20: 第一个数据块
  UINT32   Ext2FsLogBlockSize;      //  24: 块长度
  UINT32   Ext2FsFragmentSize;      //  28: 碎片长度
  UINT32   Ext2FsBlocksPerGroup;    //  32: 每个块组包含的块数
  UINT32   Ext2FsFragsPerGroup;     //  36: 每个块组包含的碎片
  UINT32   Ext2FsInodesPerGroup;    //  40: 每个块组的Inode数据
  UINT32   Ext2FsMountTime;         //  44: 装载时间
  UINT32   Ext2FsWriteTime;         //  48: 写入时间
  UINT16   Ext2FsMountCount;        //  52: 装载计数
  UINT16   Ext2FsMaxMountCount;     //  54: 最大装载计数
  UINT16   Ext2FsMagic;             //  56: 魔数,标记文件系统类型
  UINT16   Ext2FsState;             //  58: 文件系统状态
  UINT16   Ext2FsBehavior;          //  60: 检测到错误时的行为
  UINT16   Ext2FsMinorRev;          //  62: 副修订号
  UINT32   Ext2FsLastFsck;          //  64: 上一次检查的时间
  UINT32   Ext2FsFsckInterval;      //  68: 两次检查允许间隔的最长时间
  UINT32   Ext2FsCreator;           //  72: 创建文件系统的操作系统
  UINT32   Ext2FsRev;               //  76: 修订号
  UINT16   Ext2FsRsvdUid;           //  80: 能够使用保留块的默认UID
  UINT16   Ext2FsRsvdGid;           //  82: 能够使用保留块的默认GID
  //
  // 修订号大于等于1的版本才有下述的数据
  //
  UINT32   Ext2FsFirstInode;        //  84: 第一个非保留的Inode
  UINT16   Ext2FsInodeSize;         //  88: Inode结构的长度
  UINT16   Ext2FsBlockGrpNum;       //  90: 当前超级块所在的块组编号
  UINT32   Ext2FsFeaturesCompat;    //  92: 兼容特性集
  UINT32   Ext2FsFeaturesIncompat;  //  96: 不兼容特性集
  UINT32   Ext2FsFeaturesROCompat;  // 100: 只读兼容特性集
  UINT8    Ext2FsUuid[16];          // 104: 卷的128位UID
  CHAR8    Ext2FsVolumeName[16];    // 120: 卷名
  CHAR8    Ext2FsFSMnt[64];         // 136: 上一次装载的目录
  UINT32   Ext2FsAlgorithm;         // 200: 用于压缩
  UINT8    Ext2FsPreAlloc;          // 204: 试图预分配的块数
  UINT8    Ext2FsDirPreAlloc;       // 205: 试图为目录预分配的块数
  UINT16   Ext2FsRsvdGDBlock;       // 206: 为块描述符保留的块
  UINT32   Rsvd2[11];               // 208: 保留
  UINT16   Rsvd3;                   // 252: 保留
  UINT16   Ext2FsGDSize;            // 254: 开启64位模式时组描述符的大小(字节为单位)
  UINT32   Rsvd4[192];              // 256: 保留
} EXT2FS;

BIOS下获取到的Block IO可能是磁盘本身的,也可能是分区的,对于超级块,一般是在分区上的,占据1个物理块或者2个物理块(因为磁盘的物理块可能只有512个字节,而超级块最大可以是1024个字节),所以获取超级块的代码大致如下:

  //
  // 从磁盘读取超级块,读取方式需要根据硬盘的物理块来确定
  // 如果物理块大小是512字节,则指定Buffer大小是1024,即1个超级块的大小,然后读第2个物理块开始的2个物理块
  // 如果物理块大小是4096字节,则指定Buffer大小是4096,直接读第1个物理块即可
  //
  BufferSize = (BlockSize > SBSIZE) ? BlockSize : SBSIZE;
  Buffer = AllocatePool (BufferSize);
  if (NULL == Buffer) {
    Status = EFI_OUT_OF_RESOURCES;
    goto DONE;
  }
  Status = MediaReadBlocks (
            Volume->BlockIo,
            SBOFF / BlockSize,
            BufferSize,
            Buffer
            );
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "%a MediaReadBlocks failed. - %r\n", __FUNCTION__, Status));
    goto DONE;
  }
  //
  // 读取数据之后需要根据读的Buffer来确定超级块的位置
  // 如果读取2个物理块,则Offset是0,读到的数据就是超级块
  // 如果读取1个物理块,则这个物理块的offset为1024字节的偏移位置就是超级块的开始
  //
  SbOffset = (SBOFF < BlockSize) ? SBOFF : 0;
  Ext2Fs = (EXT2FS *)(&Buffer[SbOffset]);

获取到超级块数据之后就需要判断它是不是有效的超级块,判断代码:

  //
  // 超级块首先需要判断魔术字是否正确
  //
  if (E2FS_MAGIC != Ext2Fs->Ext2FsMagic) {
    Status = EFI_NOT_FOUND;
    goto DONE;
  }
  //
  // 其次需要判断版本信息以及相关的配套数据是否正确
  // 1. 如果EXT2的版本是1,则第一个可用Inode是从Inode 11开始的(注意Inode的Index从1开始计数,而不是0)
  // 2. 文件系统中的Inode大小必须是固定的128个字节或者256个字节
  // 3. 兼容性判断
  //
  if ((Ext2Fs->Ext2FsRev > E2FS_REV1) ||
      ((E2FS_REV1 == Ext2Fs->Ext2FsRev)  &&
       ((EXT2_FIRSTINO != Ext2Fs->Ext2FsFirstInode) ||
        ((128 != Ext2Fs->Ext2FsInodeSize) && (256 != Ext2Fs->Ext2FsInodeSize)) ||
        (Ext2Fs->Ext2FsFeaturesIncompat & ~EXT2F_INCOMPAT_SUPP)))) {
    Status = EFI_NOT_FOUND;
    goto DONE;
  }

组描述符

组描述符紧接在超级块之后,它的结构体说明如下:

起始结束大小描述
034Low 32bits of block address of block usage bitmap.
474Low 32bits of block address of inode usage bitmap.
8114Low 32bits of starting block address of inode table.
12132Low 16bits of number of unallocated blocks in group.
14152Low 16bits of number of unallocated inodes in group.
16172Low 16bits of number of directories in group.
18192Block group features present.
20234Low 32-bits of block address of snapshot exclude bitmap.
24252Low 16-bits of Checksum of the block usage bitmap.
26272Low 16-bits of Checksum of the inode usage bitmap.
28292Low 16-bits of amount of free inodes. This allows us to optimize inode searching.
30312Checksum of the block group, CRC16(UUID+group+desc).

如果没有开启64位支持,则不用管上述写的Low 32bits,因为也没有更高位的了;如果开启了64位支持,则还有额外的内容:

起始结束大小描述
32354High 32-bits of block address of block usage bitmap.
36394High 32-bits of block address of inode usage bitmap.
40434High 32-bits of starting block address of inode table.
44452High 16-bits of number of unallocated blocks in group.
46472High 16-bits of number of unallocated inodes in group.
48492High 16-bits of number of directories in group.
50512High 16-bits of amount of free inodes.
52554High 32-bits of block address of snapshot exclude bitmap.
56572High 16-bits of checksum of the block usage bitmap.
58592High 16-bits of checksum of the inode usage bitmap.
60634Reserved as of Linux 5.9rc3.

不关注64位支持的话对应的代码如下:

//
// EXT2文件系统块描述符
//
typedef struct {
  UINT32   Ext2BGDBlockBitmap;      // 块位图块所在位置
  UINT32   Ext2BGDInodeBitmap;      // Inode位图块所在位置
  UINT32   Ext2BGDInodeTables;      // Inode表块所在位置
  UINT16   Ext2BGDFreeBlocks;       // 空闲块数据
  UINT16   Ext2BGDFreeInodes;       // 空闲Inode数据
  UINT16   Ext2BGDNumDir;           // 目录数据
  UINT16   Rsvd;                    // 保留
  UINT32   Rsvd2[5];                // 保留
  UINT32   Ext2BGDInodeTablesHi;    // 如果支持64位模式,则表示Inode表块所在位置的高32位
  UINT32   Rsvd3[5];                // 保留
} EXT2GD;

需要注意块组描述符并不是只有一个,而是一个集合,所以上述的结构体有多个。事实上每个块组中的包含了文件系统中所有块组的组描述符。因此从每个块组都可以确定系统中所有其它块组的下列信息:

  • 块和Inode位图的位置
  • Inode表的位置
  • 空闲块和Inode的数据

组描述符的个数通过如下的函数计算(磁盘大小是200M):

#define HOWMANY(x, y)               (((x)+((y)-1))/(y))

FileSystem->Ext2FsNumCylinder =
    HOWMANY ((FileSystem->Ext2Fs.Ext2FsBlockCount - FileSystem->Ext2Fs.Ext2FsFirstDataBlock),
             FileSystem->Ext2Fs.Ext2FsBlocksPerGroup);

Ext2FsBlockCount表示的是块个数,因为一个块是1024字节(1KB),所以200M的磁盘对应的就是204800Ext2FsFirstDataBlock表示的是EXT2文件系统的数据,所以最开始就是超级块,它在第一个块,所以这里的值是1Ext2FsBlocksPerGroup表示的是一个块组的块个数,这个是在创建EXT2文件系统的时候就固定下来的,默认是8192。最终计算的结果是(((x)+((y)-1))/(y)) = (((204800 - 1)+((8192)-1))/(8192)) = 25。从计算方式来看,组描述符的个数主要跟磁盘大小有关。最后还需要注意的是组描述符个数的名称是Ext2FsNumCylinder,这里涉及到了Cylinder这个普通硬盘才会出现的概念,大概是对于那种通过磁头来寻址的硬盘来说,组描述符的设计有利于更快速的寻址,上述的计算方式也是为了服务于此。

计算一个Inode所在的块组和在该块组中的位置可以用如下的宏:

//
// 计算Inode位置所在块组
// 计算Inode在块组中的Index
//
#define INOTOCG(fs, x)              (((x) - 1) / (fs)->Ext2Fs.Ext2FsInodesPerGroup)
#define INODETOFSBO(fs, x)          ((((x) - 1) % (fs)->Ext2Fs.Ext2FsInodesPerGroup) % (fs)->Ext2FsInodesPerBlock)

Inode

Inode表的位置在组描述符中指定,从这里可以找到所有的Inode。其结构体描述如下:

起始结束大小描述
012Type and Permissions (see below)
232User ID
474Lower 32 bits of size in bytes
8114Last Access Time (in POSIX time)
12154Creation Time (in POSIX time)
16194Last Modification time (in POSIX time)
20234Deletion time (in POSIX time)
24252Group ID
26272Count of hard links (directory entries) to this inode. When this reaches 0, the data blocks are marked as unallocated.
28314Count of disk sectors (not Ext2 blocks) in use by this inode, not counting the actual inode structure nor directory entries linking to the inode.
32354Flags (see below)
36394Operating System Specific value #1
40434Direct Block Pointer 0
44474Direct Block Pointer 1
48514Direct Block Pointer 2
52554Direct Block Pointer 3
56594Direct Block Pointer 4
60634Direct Block Pointer 5
64674Direct Block Pointer 6
68714Direct Block Pointer 7
72754Direct Block Pointer 8
76794Direct Block Pointer 9
80834Direct Block Pointer 10
84874Direct Block Pointer 11
88914Singly Indirect Block Pointer (Points to a block that is a list of block pointers to data)
92954Doubly Indirect Block Pointer (Points to a block that is a list of block pointers to Singly Indirect Blocks)
96994Triply Indirect Block Pointer (Points to a block that is a list of block pointers to Doubly Indirect Blocks)
1001034Generation number (Primarily used for NFS)
1041074In Ext2 version 0, this field is reserved. In version >= 1, Extended attribute block (File ACL).
1081114In Ext2 version 0, this field is reserved. In version >= 1, Upper 32 bits of file size (if feature bit set) if it’s a file, Directory ACL if it’s a directory
1121154Block address of fragment
11612712Operating System Specific Value #2

可以看到有一部分成员是根据不同的操作系统存在差异的,对应的代码:

typedef struct {
  UINT16    Ext2DInodeMode;                     //   0: 文件模式,保存了访问权限和文件类型(目录,设备文件等)
  UINT16    Ext2DInodeUid;                      //   2: 所有者UID的低16位
  UINT32    Ext2DInodeSize;                     //   4: 文件长度,以字节为单位
  UINT32    Ext2DInodeAcessTime;                //   8: 上一次访问文件的时间戳
  UINT32    Ext2DInodeCreatTime;                //  12: 上一次修改Inode的时间戳
  UINT32    Ext2DInodeModificationTime;         //  16: 上一次修改文件的时间戳
  UINT32    Ext2DInodeDeletionTime;             //  20: 文件删除的时间戳
  UINT16    Ext2DInodeGid;                      //  24: 组ID的低16位
  UINT16    Ext2DInodeLinkCount;                //  26: 指向Inode的硬链接的数目
  UINT32    Ext2DInodeBlockCount;               //  28: 文件长度,以块为单位
  UINT32    Ext2DInodeStatusFlags;              //  32: 文件标志
  UINT32    Ext2DInodeLinuxRsvd1;               //  36: 保留
  UINT32    Ext2DInodeBlocks[NDADDR + NIADDR];  //  40: 块指针(块号),指向文件数据块的指针保存在这里,
                                                //      默认情况下数组元素个数是12+3,前12个寻址直接块,
                                                //      后3个用于实现简单、二次和三次间接
  UINT32    Ext2DInodeGen;                      // 100: 文件版本,用户NFS
  UINT32    Ext2DInodeFileAcl;                  // 104: 文件ACL(访问控制表)
  UINT32    Ext2DInodeDirAcl;                   // 108: 目录ACL(访问控制表)
  UINT32    Ext2DInodeFragmentAddr;             // 112: 碎片地址(未使用)
  UINT8     Ext2DInodeFragmentNum;              // 116: 碎片编号(未使用)
  UINT8     Ext2DInodeFragmentSize;             // 117: 锁片长度(未使用)
  UINT16    Ext2DInodeLinuxRsvd2;               // 118: 保留
  UINT16    Ext2DInodeUidHigh;                  // 120: 所有者UID的高16位
  UINT16    Ext2DInodeGidHigh;                  // 122: 组ID的高16位
  UINT32    Ext2DInodeLinuxRsvd3;               // 124: 保留
} EXTFS_DINODE;

文件类型的可能值(BIT12 ~ BIT15):

Type value in hexType Description
0x1000FIFO
0x2000Character device
0x4000Directory
0x6000Block device
0x8000Regular file
0xA000Symbolic link
0xC000Unix socket

访问权限的可能值(BIT0 ~ BIT11):

Permissionvalue in hexPermissionvalue in octalPermission Description
0x00100001Other—execute permission
0x00200002Other—write permission
0x00400004Other—read permission
0x00800010Group—execute permission
0x01000020Group—write permission
0x02000040Group—read permission
0x04000100User—execute permission
0x08000200User—write permission
0x10000400User—read permission
0x20001000Sticky Bit
0x40002000Set group ID
0x80004000Set user ID

Inode的大小根据EXT2的版本不同,值也不同,具体的计算方法如下:

//
// 计算INODE结构体的大小,跟EXT2版本有关:
//   EXT2版本0中Inode的大小是128个字节;
//   EXT2版本1开始,Inode的大小通过超级块中的成员指定;
//
#define EXT2_REV0_DINODE_SIZE       sizeof(EXTFS_DINODE)
#define EXT2_DINODE_SIZE(fs)        ((fs)->Ext2Fs.Ext2FsRev > E2FS_REV0 ?    \
                                     (fs)->Ext2Fs.Ext2FsInodeSize :    \
                                     EXT2_REV0_DINODE_SIZE)

读取Inode的流程大致如下:

开始
读取超级块信息,包括块大小、块组中块的个数和Inode的个数、第一个组开始的块等信息
确定Inode属于哪个块组
读取该块组的描述符
根据块组描述符确定块组Inode表所在位置
确定Inode在该Inode表中的索引
读取Inode数据
结束

存储在Inode结构中的文件元数据能够关联到位于磁盘数据块部分的文件内容,二者之间的关联是通过将数据块的地址存储在Inode中建立的。需要注意,数据块并不一定是连续的

数据块的地址存放在Ext2DInodeBlocks,不过Ext2DInodeBlocks只有总计15个32位的数据,而一个数据块的大小可能只有1024个字节,所以如果直接去指定的话,会发现数据显然是不够的。这里使用了直接和间接(间接还有好几级)来寻址数据块的方式,大致如下图所示:

在这里插入图片描述

直接寻址只用于小文件;如果文件较大,则会使用间接,此时文件系统在磁盘上分配一个数据块,不存储文件,专门用于存储块号,这样的话,假设一个块大小是1024字节,而32比特表示一个数据块地址,则可以存放256个指针,对应256个数据库块,能够存储的数据大小是256x1024=256K,如果是双重间接则能够存储的数据大小是256x256x1024=65536K,如果是三重间接则能够存储的数据大小是256x256x256x1024=16777216KB=16G,即单个文件最大支持的就是16GB。

直接寻址没有什么介绍的,间接寻址有两种方式,一种是EXT2本身的,一种是EXT4扩展的,这里主要介绍EXT4扩展的。

EXT4扩展版本使用了新的结构体来表示,称为EXT4扩展索引树,此时Inode结构体中的Ext2DInodeBlocks(60个字节)就不再是一个数组,而表示的是索引树结构体(大小也是60个字节):

#define EXT4_MAX_HEADER_EXTENT_ENTRIES   4
#define EXT4_EXTENT_HEADER_MAGIC         0xF30A
//
// 一个间接块可以表示的数据块,因为数据块寻址地址的大小是UINT32,就是块大小除以UINT32的大小
//
#define NINDIR(fs)                  ((fs)->Ext2FsBlockSize / sizeof(UINT32))
//
// EXT4扩展版的间接寻址
//
typedef struct {
  UINT16    EhMagic;      // 魔数0xF30A
  UINT16    EhEntries;    // 当前节点中有效entry的数目
  UINT16    EhMax;        // 当前节点中entry的最大数目
  UINT16    EhDepth;      // 当前节点在树中的深度
  UINT32    EhGen;        // 索引树版本
} EXT4_EXTENT_HEADER;

typedef struct {
  UINT32    EiBlk;        // 当前节点块的索引
  UINT32    EiLeafLo;     // 物理块指针低位
  UINT16    EiLeafHi;     // 物理块指针高位
  UINT16    EiUnused;
} EXT4_EXTENT_INDEX;

typedef struct {
  UINT32    Eblk;         // 当前节点的第一个块位置
  UINT16    Elen;         // 块数目
  UINT16    EstartHi;     // 物理块指针高位
  UINT32    EstartLo;     // 物理块指针低位
} EXT4_EXTENT;
//
// 跟Inode中的Ext2DInodeBlocks大小一样,都是64个字节
//
typedef struct {
  EXT4_EXTENT_HEADER Eheader;
  union {
    EXT4_EXTENT_INDEX  Eindex[EXT4_MAX_HEADER_EXTENT_ENTRIES];
    EXT4_EXTENT        Extent[EXT4_MAX_HEADER_EXTENT_ENTRIES];
  } Enodes;
} EXT4_EXTENT_TABLE;

整个索引的过程如下图所示(图片来自《Addition of Ext4 Extent and Ext3 HTree DIR Read-Only Support in NetBSD》):

在这里插入图片描述

索引树的循环代码大致如下:

    while (Etable->Eheader.EhDepth > 0) {
      ExtIndex = NULL;
      for (Index=1; Index < Etable->Eheader.EhEntries; Index++) {
        ExtIndex = &(Etable->Enodes.Eindex[Index]);
        if (((UINT32) FileBlock) < ExtIndex->EiBlk) {
          ExtIndex = &(Etable->Enodes.Eindex[Index-1]);
          break;
        }
        ExtIndex = NULL;
      }

      if (NULL != ExtIndex) {
        //
        // 目前还不支持48位
        //
        ASSERT (ExtIndex->EiLeafHi == 0);
        NextLevelNode = ExtIndex->EiLeafLo;

        //
        // 继续深入读取下一个间接块
        //
        Status = MediaReadBlocks (
                  File->BlockIo,
                  FSBTODB (Fp->FsPtr, (DADDRESS) NextLevelNode),
                  FileSystem->Ext2FsBlockSize,
                  Buf
                  );
        if (EFI_ERROR (Status)) {
          DEBUG ((EFI_D_ERROR, "%a MediaReadBlocks failed. - %r\n", __FUNCTION__, Status));
          return Status;
        }

        Etable = (EXT4_EXTENT_TABLE*) Buf;
        if (Etable->Eheader.EhMagic != EXT4_EXTENT_HEADER_MAGIC) {
            DEBUG ((DEBUG_ERROR, "EXT4 extent header magic mismatch 0x%X!\n", Etable->Eheader.EhMagic));
            return EFI_DEVICE_ERROR;
        }
      } else {
        DEBUG ((DEBUG_ERROR, "Could not find FileBlock #%d in the index extent data!\n", FileBlock));
        return EFI_NO_MAPPING;
      }
    }

文件读取

一个文件对应一个Inode,Inode会对应到数据,但是Inode跟数据的对应根据前面的介绍可以看出有直接和间接两种方式,所以为了读取整个文件,需要先将文件切成块的大小,然后每个块对应一个文件索引,通过这个文件块索引结合Inode里面的寻址数据,最终找到对应的数据块,然后读取整个数据块。如此循环,直到将整个文件都读取出来。

未读完
读完
开始
读取超级块信息
读取组描述符
读取根目录
遍历整个路径,找到最终的文件Inode
循环读取块
将文件地址转换成块
根据块和Inode计算出数据块位置
读取块位置数据
结束

代码示例

对应的代码实现已经上传https://gitee.com/jiangwei0512/edk2-beni,命令执行结果:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值