Ext4文件系统解析
--以cache.img为例,基于32位的文件系统
目录
Cache文件结构:
可见ext4文件系统cache中包含. .. hello lost+found hello_world.ko , 其中有四个Directory Entries
和一个Regular file
Cache文件系统结构
总体结构
Group 0 Padding |
ext4 Super Block |
Group Descriptors |
Reserved GDT Blocks |
Data Block Bitmap |
inode Bitmap |
inode Table |
Data Blocks |
1024 bytes |
1 block |
many blocks |
many blocks |
1 block |
1 block |
many blocks |
many more blocks |
超级块:
Offset |
Size |
Name |
Description |
||||||||||
0x0 |
__le32 |
s_inodes_count |
Total inode count. 0x2000=8192 |
||||||||||
0x4 |
__le32 |
s_blocks_count_lo |
Total block count. 0x8000=32768 |
||||||||||
0x8 |
__le32 |
s_r_blocks_count_lo |
This number of blocks can only be allocated by the super-user. |
||||||||||
0xC |
__le32 |
s_free_blocks_count_lo |
Free block count. 0x79f0=31216 |
||||||||||
0x10 |
__le32 |
s_free_inodes_count |
Free inode count. 0x1ff2=8178 |
||||||||||
0x14 |
__le32 |
s_first_data_block |
First data block. This must be at least 1 for 1k-block filesystems and is typically 0 for all other block sizes. |
||||||||||
0x18 |
__le32 |
s_log_block_size |
Block size is 2 ^ (10 + s_log_block_size).0x2=2^(10+2)=4096 |
||||||||||
0x1C |
__le32 |
s_log_cluster_size |
Cluster size is (2 ^ s_log_cluster_size) blocks if bigalloc is enabled, zero otherwise. |
||||||||||
0x20 |
__le32 |
s_blocks_per_group |
Blocks per group. 0x8000=32768 |
||||||||||
0x24 |
__le32 |
s_clusters_per_group |
Clusters per group, if bigalloc is enabled. |
||||||||||
0x28 |
__le32 |
s_inodes_per_group |
Inodes per group. 0x2000=8192 |
||||||||||
0x2C |
__le32 |
s_mtime |
Mount time, in seconds since the epoch. 0x573bc363 |
||||||||||
0x30 |
__le32 |
s_wtime |
Write time, in seconds since the epoch. 0x573bc363 |
||||||||||
0x34 |
__le16 |
s_mnt_count |
Number of mounts since the last fsck. 0x06 |
||||||||||
0x36 |
__le16 |
s_max_mnt_count |
Number of mounts beyond which a fsck is needed. 0xffff |
||||||||||
0x38 |
__le16 |
s_magic |
Magic signature, 0xEF53 0xEF53 |
||||||||||
0x3A |
__le16 |
s_state |
File system state. Valid values are:
|
||||||||||
0x3C |
__le16 |
s_errors |
Behaviour when detecting errors. One of:
|
||||||||||
0x3E |
__le16 |
s_minor_rev_level |
Minor revision level. 0x0 |
||||||||||
0x40 |
__le32 |
s_lastcheck |
Time of last check, in seconds since the epoch. 0x0 |
||||||||||
0x44 |
__le32 |
s_checkinterval |
Maximum time between checks, in seconds. 0x0 |
||||||||||
0x48 |
__le32 |
s_creator_os |
OS. One of:
|
||||||||||
0x4C |
__le32 |
s_rev_level |
Revision level. One of:
|
||||||||||
0x50 |
__le16 |
s_def_resuid |
Default uid for reserved blocks. |
||||||||||
0x52 |
__le16 |
s_def_resgid |
Default gid for reserved blocks. |
||||||||||
These fields are for EXT4_DYNAMIC_REV superblocks only. Note: the difference between the compatible feature set and the incompatible feature set is that if there is a bit set in the incompatible feature set that the kernel doesn't know about, it should refuse to mount the filesystem. e2fsck's requirements are more strict; if it doesn't know about a feature in either the compatible or incompatible feature set, it must abort and not try to meddle with things it doesn't understand... |
|||||||||||||
0x54 |
__le32 |
s_first_ino |
First non-reserved inode. 0x0B |
||||||||||
0x58 |
__le16 |
s_inode_size |
Size of inode structure, in bytes. 0x100 |
||||||||||
0x5A |
__le16 |
s_block_group_nr |
Block group # of this superblock. 0x0 |
块组描述符表:
Offset |
Size |
Name |
Description |
||||||
0x0 |
__le32 |
bg_block_bitmap_lo |
Lower 32-bits of location of block bitmap. 0x09 |
||||||
0x4 |
__le32 |
bg_inode_bitmap_lo |
Lower 32-bits of location of inode bitmap. 0x0a |
||||||
0x8 |
__le32 |
bg_inode_table_lo |
Lower 32-bits of location of inode table. 0x0b |
||||||
0xC |
__le16 |
bg_free_blocks_count_lo |
Lower 16-bits of free block count. 0x79f0 |
||||||
0xE |
__le16 |
bg_free_inodes_count_lo |
Lower 16-bits of free inode count. 0x1ff2 |
||||||
0x10 |
__le16 |
bg_used_dirs_count_lo |
Lower 16-bits of directory count. 0x03 |
||||||
0x12 |
__le16 |
bg_flags |
Block group flags. Any of:
|
||||||
0x14 |
__le32 |
bg_exclude_bitmap_lo |
Lower 32-bits of location of snapshot exclusion bitmap. |
||||||
0x18 |
__le16 |
bg_block_bitmap_csum_lo |
Lower 16-bits of the block bitmap checksum. |
||||||
0x1A |
__le16 |
bg_inode_bitmap_csum_lo |
Lower 16-bits of the inode bitmap checksum. |
||||||
0x1C |
__le16 |
bg_itable_unused_lo |
Lower 16-bits of unused inode count. If set, we needn't scan past the(sb.s_inodes_per_group - gdt.bg_itable_unused)th entry in the inode table for this group. |
||||||
0x1E |
__le16 |
bg_checksum |
Group descriptor checksum; crc16(sb_uuid+group+desc) if the RO_COMPAT_GDT_CSUM feature is set, or crc32c(sb_uuid+group_desc) & 0xFFFF if the RO_COMPAT_METADATA_CSUM feature is set. |
||||||
These fields only exist if the 64bit feature is enabled and s_desc_size > 32. |
|||||||||
0x20 |
__le32 |
bg_block_bitmap_hi |
Upper 32-bits of location of block bitmap. |
||||||
0x24 |
__le32 |
bg_inode_bitmap_hi |
Upper 32-bits of location of inodes bitmap. |
||||||
0x28 |
__le32 |
bg_inode_table_hi |
Upper 32-bits of location of inodes table. |
||||||
0x2C |
__le16 |
bg_free_blocks_count_hi |
Upper 16-bits of free block count. |
||||||
0x2E |
__le16 |
bg_free_inodes_count_hi |
Upper 16-bits of free inode count. |
||||||
0x30 |
__le16 |
bg_used_dirs_count_hi |
Upper 16-bits of directory count. |
||||||
0x32 |
__le16 |
bg_itable_unused_hi |
Upper 16-bits of unused inode count. |
||||||
0x34 |
__le32 |
bg_exclude_bitmap_hi |
Upper 32-bits of location of snapshot exclusion bitmap. |
||||||
0x38 |
__le16 |
bg_block_bitmap_csum_hi |
Upper 16-bits of the block bitmap checksum. |
||||||
0x3A |
__le16 |
bg_inode_bitmap_csum_hi |
Upper 16-bits of the inode bitmap checksum. |
||||||
0x3C |
__u32 |
bg_reserved |
Padding to 64 bytes. |
Inode talble:
根据块组描述符中的bg_inode_table_lo关键字可以获取到inodetable的位置
首先查看 .目录的table表:bg_inode_table_lo*block_size+(2-1)*inode_size.(.目录的inode_number=2)
位置 |
值 |
名称 |
描述 |
0x0 |
__le16:0x41ed |
i_mode |
文件模式 |
0x2 |
__le16:0x0000 |
i_uid |
所有者UID. |
0x4 |
__le32:0x00001000 |
i_size_lo |
文件大小. |
0x8 |
__le32:0x00000000 |
i_atime |
读取时间. |
0xC |
__le32:0x57395adc |
i_ctime |
Inode修改时间 |
0x10 |
__le32:0x57395adc |
i_mtime |
文件修改时间. |
0x14 |
__le32:0x00000000 |
i_dtime |
删除时间 |
0x18 |
__le16:0x0000 |
i_gid |
GID. |
0x1A |
__le16:0x0004 |
i_links_count |
硬链接计数. |
0x1C |
__le32:0x00000008 |
i_blocks_lo |
块计数(512字节) |
0x20 |
__le32:0x00080080 |
i_flags |
文件标识(ext4使用extent需要标记0x80000) |
... |
|||
0x28 |
__le32 |
i_block[EXT4_N_BLOCKS=15] |
块映射(ext2/3)或区段树(ext4) |
... |
Extern树
由于extern树使能,因此需要查看extern树结构体:
从40-99这60个字节过去是用作块映射的,如今用作存储extent信息。extent结构体有12字节的大小,反应快的同学马上会说,那么一个inode可以存放最多5个extent。然而这是不对的,因为前12个字节(40-51)被段头(extentheader)所占据,所以,一个inode中的区段数最多只能是4。
首先看段头:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le16:0xf30a |
eh_magic |
幻数magic number, 0xF30A. |
0x2 |
__le16:0x01 |
eh_entries |
区段数. |
0x4 |
__le16:0x03 |
eh_max |
最大的区段数. |
0x6 |
__le16:0x0000 |
eh_depth |
段节点在段树中的深度。0则表示为叶子节点,指向数据块;否则指向其它段节点。 |
0x8 |
__le32 |
eh_generation |
暂不讨论 |
内部节点:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le32 |
ei_block |
逻辑块号. |
0x4 |
__le32 |
ei_leaf_lo |
区段树中下一层的区段节点块地址(低32位),可以指向叶子节点或者内部节点。 |
0x8 |
__le16 |
ei_leaf_hi |
上一栏的高16位地址 |
0xA |
__u16 |
ei_unused |
未使用 |
叶子节点:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le32:0x00000000 |
ee_block |
此区段的第一个块号,起始块号 |
0x4 |
__le16:0x0001 |
ee_len |
区段内包含的块数. |
0x6 |
__le16:0x0000 |
ee_start_hi |
此区段所指向的块号(高16位) |
0x8 |
__le32:0x0000060c |
ee_start_lo |
此区段所指向的块号(低32位 |
Directory Entries
根据extern tree叶节点所指向的块号:0x60c*0x1000=0x60c000
Directory Entries结构体
.:
Offset |
Size |
Name |
Description |
||||||||||||||||
0x0 |
__le32:0x00000002 |
inode |
Number of the inode |
||||||||||||||||
0x4 |
__le16:0x000C |
rec_len |
Length of this directory entry. |
||||||||||||||||
0x6 |
__u8:0x01 |
name_len |
Length of the file name. |
||||||||||||||||
0x7 |
__u8:0x02 |
file_type |
File type code, one of:
|
||||||||||||||||
0x8 |
Char:0x2E=. |
name[EXT4_NAME_LEN] |
File name. |
lost+found:
Offset |
Size |
Name |
Description |
||||||||||||||||
0x0 |
__le32:0x0000000B |
inode |
Number of the inode |
||||||||||||||||
0x4 |
__le16:0x0014 |
rec_len |
Length of this directory entry. |
||||||||||||||||
0x6 |
__u8:0x0a |
name_len |
Length of the file name. |
||||||||||||||||
0x7 |
__u8:0x02 |
file_type |
File type code, one of:
|
||||||||||||||||
0x8 |
Char:lost+found |
name[EXT4_NAME_LEN] |
File name. |
hello_world.ko
Offset |
Size |
Name |
Description |
||||||||||||||||
0x0 |
__le32:0x0000000C |
inode |
Number of the inode |
||||||||||||||||
0x4 |
__le16:0x0018 |
rec_len |
Length of this directory entry. |
||||||||||||||||
0x6 |
__u8:0x0e |
name_len |
Length of the file name. |
||||||||||||||||
0x7 |
__u8:0x01 |
file_type |
File type code, one of:
|
||||||||||||||||
0x8 |
Char:hello_world.ko |
name[EXT4_NAME_LEN] |
File name. |
反查inode table寻找文件具体位置
根据hello_world.ko的inode的节点号,再去inode table中反查,0xB000+(0xC-1)*0x100
inode table:
位置 |
值 |
名称 |
描述 |
0x0 |
__le16:0x81ff |
i_mode |
文件模式 |
0x2 |
__le16:0x0000 |
i_uid |
所有者UID. |
0x4 |
__le32:0x00000a80= 2688 |
i_size_lo |
文件大小.
|
0x8 |
__le32:0x572c42ae |
i_atime |
读取时间. |
0xC |
__le32:0x572c42c2 |
i_ctime |
Inode修改时间 |
0x10 |
__le32:0x572c42ae |
i_mtime |
文件修改时间. |
0x14 |
__le32:0x00000000 |
i_dtime |
删除时间 |
0x18 |
__le16:0x0000 |
i_gid |
GID. |
0x1A |
__le16:0x0001 |
i_links_count |
硬链接计数. |
0x1C |
__le32:0x00000008 |
i_blocks_lo |
块计数(512字节) |
0x20 |
__le32:0x00080080 |
i_flags |
文件标识(ext4使用extent需要标记0x80000) |
... |
|||
0x28 |
__le32 |
i_block[EXT4_N_BLOCKS=15] |
块映射(ext2/3)或区段树(ext4) |
... |
同样extern树使能了,所以需要查看extern树
首先看段头:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le16:0xf30a |
eh_magic |
幻数magic number, 0xF30A. |
0x2 |
__le16:0x01 |
eh_entries |
区段数. |
0x4 |
__le16:0x04 |
eh_max |
最大的区段数. |
0x6 |
__le16:0x0000 |
eh_depth |
段节点在段树中的深度。0则表示为叶子节点,指向数据块;否则指向其它段节点。 |
0x8 |
__le32 |
eh_generation |
暂不讨论 |
内部节点:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le32 |
ei_block |
逻辑块号. |
0x4 |
__le32 |
ei_leaf_lo |
区段树中下一层的区段节点块地址(低32位),可以指向叶子节点或者内部节点。 |
0x8 |
__le16 |
ei_leaf_hi |
上一栏的高16位地址 |
0xA |
__u16 |
ei_unused |
未使用 |
叶子节点:
偏移 |
大小 |
名称 |
描述 |
0x0 |
__le32:0x00000000 |
ee_block |
此区段的第一个块号,起始块号 |
0x4 |
__le16:0x0001 |
ee_len |
区段内包含的块数. |
0x6 |
__le16:0x0000 |
ee_start_hi |
此区段所指向的块号(高16位) |
0x8 |
__le32:0x0000060E |
ee_start_lo |
此区段所指向的块号(低32位 |
根据extern tree叶节点所指向的块号:0x60E*0x1000=0x60E000开始的block,数量为1个block
至此一个文件就找到了,就可以根据分区信息计算出位于emmc的block的偏移和数量,然后将文件读出即可
综上,其实我们再执行某个分区挂载的时候mount,其实就是将系统的super_block,block_group_des和当前目录的inode表读取出来去初始化分配好的结构体。当你读或者写一个文件的时候再去更新这些结构体,同时根据结构体的信息去找到你要读或者写的文件位于存储外设的硬件的block的位置,并更新文件内容
几个重要的结构体关系图: