Linux Kernel: Ext2
Table of Contents
1. Linux Kernel: Ext2
1.1. Ext2 的磁盘布局
Ext2 将磁盘分割为几个连续的, 大小相同的 block group, 每个 group 的结构为:
super block
占一个 block (如果没有特别说明, 这里及以后所指的的 block 都是指 mkfs 时指定的 block size, 与物理设备的 block (sector)无关)
group descriptors
占用多个 block, 表示所有的 group 的相关信息 (而并非仅保存当前 group 的信息)
block bitmap
占一个 block
inode bitmap
一个 block
inode table
占用多个 block
data blocks
占用多个 block 直到 group 末尾
之所以分成许多 group, 是为了减少外部碎片: 对于同一个文件, 尽是分配在同一个 group.
1.1.1. super block
super block 和 group descriptors 在每个 group 都有一个, 但平时只有 group 0 的 super block 会被使用, 其它 group 的都是备份, 和 fsck 有关
super block 的主要成员:
s_inodes_count
文件系统能容纳的最大的 inode 个数
s_block_count
文件系统的大小
s_r_blocks_count
reserved 的 block 数目
s_groups_count
group 数目
s_free_inodes_count
可用的 inode 数, s_free_inodes_count/s_groups_count 可计算出每个 group 平均的 inode 数, 后续创建 inode 时会根据这个值选择相应的 group
s_free_blocks_count
可用的 block 数
s_log_block_size
block 大小
s_blocks_per_group
每个 group 的 block 数
s_inodes_per_group
每个 group 的 inode 数
s_mtime/s_wtime
最近的 mount 时间和修改时间
s_mnt_count
mount 次数
s_def_resuid/s_def_resgid
可用使用 reserved block 的 uid/gid
s_last_mounted
最近一次被挂载到哪
super block 的这些成员大多是运行时的统计信息, 但有一些是需要用户通过 mkfs 或 tune2fs 指定的:
s_r_blocks_count
默认的 reserved block 数目是 s_block_count 的 5%
s_log_block_size
block 大小默认为 1024B
s_blocks_per_group
大小和 block 大小有关: 因为一个 group 只用一个 block 来保存 block bitmap, 所以对于 1024B 的 block 来说, block bitmap 最多只能表示 1024*8*1024=8MB 的大小, 即 1024*8 = 8192 个 block
s_inodes_per_group
和磁盘大小有关, 默认情况下, 每个 inode 需要至少 8KB, 所以假设 block size 为 1K, 若磁盘大小为 256K, 则 inode 总数是 256/8 = 32, group 为 1, s_inodes_per_group 为 32.
- s_def_resuid/s_def_resgid
1.1.2. group descriptors
group descriptors 是一系列的 group descriptor 的集合, 占用多个 block, 每个 group descriptor 的成员包括:
bg_block_bitmap
block bitmap 对应的 block number
bg_inode_bitmap
inode bitamp 对应的 block number
bg_inode_table
inode table 起始 block 对应的 block number, 因为有 s_inodes_per_group, 所以这里不需要记录 inode table 的大小
- …
1.1.3. block bitmap
block bitmap 占用一个 block, 记录的是 group 所有 data block 是否已经分配, 这个 block 的大小决定了每个 group 能容纳多少 data block.
1.1.4. inode bitmap
占用一个 block, 记录 group 的某个 inode 是否已经被使用.
1.1.5. inode table
占用多个 block, 这个 table 中每一项都是一个 128B 的 inode 结构, 所以 inode table 占用的 block 数 = (128 / s_log_block_size ) * s_inodes_per_group
由于每个 group 的 inode table 大小都是相同的, 所以 Ext2 的 inode number 是这样实现的: 对 group x 的 inode table 中的第 y 个 inode, 其 inode number 为: x * s_inodes_per_group + y, 反之, 根据 inode number 可以很快找到对应的 group 和 inode table 中对应的 inode
1.2. 管理 Ext2 磁盘空间
1.2.1. 创建 inode
创建 inode 的过程主要是需要分配一个 inode number, 或者说找到一个合适的 group, 并且在这个 group 的 inode table 中找到某一个 entry.
大约的函数调用为:
ext2_new_inode: inode = new_inode(sb); // 找一个合适的 group if (S_ISDIR(mode)): group = find_group_orlov(sb, dir); else: group = find_group_other(sb, dir); bitmap_bh = read_inode_bitmap(sb, group); // 通过 inode bitmap 找一个可用的 inode table entry ino = ext2_find_next_zero_bit((unsigned long *)bitmap_bh->b_data, EXT2_INODES_PER_GROUP(sb), ino); ext2_set_bit_atomic(sb_bgl_lock(sbi, group),ino, bitmap_bh->b_data) mark_buffer_dirty(bitmap_bh); // 计算 inode number ino += group * EXT2_INODES_PER_GROUP(sb) + 1; inode->i_ino = ino; inode->i_uid = current->fsuid; inode->i_mode = mode; inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC; // ... return inode
创建 inode 最主要的过程是查找一个`合适`的 group (find_group_xxx), 该过程使用如下的 heuristic:
- 若新建的 inode 对应一个目录:
- 若 parent 是 root inode, 则 inode 会尽量平均分布在各个 group: 选择 free inode 和 free block 大于平均值的 group
- 对于 parent 不是 root 的 inode, 会尽量选择 parent inode 所在的 group, 除非这个 group 有太多的目录或者 free inode 已经很少
- 做为 1 和 2 的 fallback, 如果 1 和 2 没有找到合适的 group, 则从 parent inode 所在的 group 开始, 找一个 free inode 大于平均值的 group
若新建 inode 是一个文件:
从 parent inode 所在的 group 开始 (假设为 i), 跳跃式的查找一个可用的 inode, 大约是这样跳的: i, i+1, i+1+2, i+1+2+4 …, 代码如下:
for (i = 1; i < ngroups; i <<= 1) { group += i; if (group >= ngroups) group -= ngroups; desc = ext2_get_group_desc (sb, group, &bh); if (desc && le16_to_cpu(desc->bg_free_inodes_count) && le16_to_cpu(desc->bg_free_blocks_count)) goto found; }
若上面的查找失败, 进行线性查找:
group = parent_group; for (i = 0; i < ngroups; i++) { if (++group >= ngroups) group = 0; desc = ext2_get_group_desc (sb, group, &bh); // 这里并不需要 group 有 free block ..., 因为极有可能找不到这样的 // group if (desc && le16_to_cpu(desc->bg_free_inodes_count)) goto found; }
总的来说, find group 主要的逻辑是:
- inode 尽量与 parent inode 位于同一个 group
- inode 尽量的分散到不同的 group
1.2.2. 删除 inode
ext2_free_inode 负责删除 inode 本身 (修改 inode bitmap), 至于 unlink 和 rmdir 相关的其它逻辑, 比如通过 link count 决定是否需要真正删除 inode, 修改 parent directory 的 dentry, 删除 inode 的 data block, 以及判断目录是否为空等由其它函数实现, ext2_free_inode 被调用时可以认为之前的动作已经完成.
ext2_free_inode: block_group = (ino - 1) / EXT2_INODES_PER_GROUP(sb); bit = (ino - 1) % EXT2_INODES_PER_GROUP(sb); bitmap_bh = read_inode_bitmap(sb, block_group); ext2_clear_bit_atomic(sb_bgl_lock(EXT2_SB(sb), block_group),bit, (void *) bitmap_bh->b_data)) ext2_release_inode(sb, block_group, is_directory); desc = ext2_get_group_desc(sb, group, &bh); desc->bg_free_inodes_count = cpu_to_le16(le16_to_cpu(desc->bg_free_inodes_count) + 1); if (dir): desc->bg_used_dirs_count = cpu_to_le16(le16_to_cpu(desc->bg_used_dirs_count) - 1); mark_buffer_dirty(bh); mark_buffer_dirty(bitmap_bh);
unlink 调用 ext2_free_inode 的逻辑为:
sys_unlink: path_lookup(name, LOOKUP_PARENT, &nd); vfs_unlink(nd.dentry->d_inode, dentry); ext2_unlink () ext2_delete_entry (de, page); ext2_dec_count(inode); iput(inode); /* truncate the inode here */ iput_final(inode) generic_drop_inode(inode) if (!inode->i_nlink): generic_delete_inode(inode); ext2_delete_inode(inode) // ext2_truncate 释放 inode 的 data blocks (定位 inode 所 // 有的 block 并修改 block bitmap) if (inode->i_blocks): ext2_truncate (inode); ext2_free_inode (inode);
1.2.3. data block
1.2.3.1. Locate data block
一个 inode 对应的 data block 通过 ext2_inode->i_block[EXT2_N_BLOCKS] 来定位, EXT2_N_BLOCKS 为 15, 其中:
- 0~11 是直接索引
- 12 是一级间接索引
- 13 是二级间接索引
- 14 是三级间接索引
#define EXT2_NDIR_BLOCKS 12 #define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
假设 block size 为 1K, 则能定位的最大单个文件大小为:
12 * 1K + 1 * 256 * 1K + 1 * 256^2 * 1K + 1 * 256^3 * 1K = 16G
其中 256 指一个 block (1K) 能保存 256 个 block 指针 (block 指针类型为__le32)
若 block size 为 4K, 则最大文件大小为 4T
实际上 ext2 支持的最大单个文件大小是 2T 而不是 4T, 2T 的限制来源于 ext2_inode->i_blocks, 这个值表示的是 sector 的大小, 所以 4B 的 i_blocks 最多只能表示 2T.
另外, 由于 block number 是 4B 大小, 所以 ext2 支持的最大文件系统大小为 4G * 8K = 32 T (ext2 最大可以使用 8K 做为 block size)
ext2_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create): int offsets[4]; Indirect chain[4]; Indirect *partial; // ext2_block_to_path 作用是将 iblock 转换为一个 offsets 数组, 这个数组中的第 n 项表示 // 第 n 级 indirect block 在 n-1 级 indirect block 中的 offset, 例如: // // 若 iblock 为 11, 而 offset[0] = 11, 返回 depth = 1 // 若 iblock 为 12, 则 offset[0] = 12 (EXT2_IND_BLOCK), offset[1] = 0, depth = 2 // 若 iblock 为 13, 则 offset[0] = 12, offset[1] = 1, depth = 2 // ... // 这个过程不需要做 IO int depth = ext2_block_to_path(inode, iblock, offsets, &boundary); if (i_block < 0): ext2_warning (inode->i_sb, "ext2_block_to_path", "block < 0"); else if (i_block < direct_blocks): offsets[n++] = i_block; final = direct_blocks; else if ( (i_block -= direct_blocks) < indirect_blocks): offsets[n++] = EXT2_IND_BLOCK; offsets[n++] = i_block; final = ptrs; else if ((i_block -= indirect_blocks) < double_blocks): offsets[n++] = EXT2_DIND_BLOCK; offsets[n++] = i_block >> ptrs_bits; offsets[n++] = i_block & (ptrs - 1); final = ptrs; else if (((i_block -= double_blocks) >> (ptrs_bits * 2)) < ptrs): offsets[n++] = EXT2_TIND_BLOCK; offsets[n++] = i_block >> (ptrs_bits * 2); offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1); offsets[n++] = i_block & (ptrs - 1); final = ptrs; return n if (depth == 0): goto out; // 把整个 iblock[] 及其 indirect blocks 看做一棵树的话, 上一步计算的 // offsets 数组相当于一个遍历这个树的索引, 下一步的 ext2_get_branch // 需要根据这个索引从磁盘读取相应的 block, 将结果保存在 chain[] 中, // chain 与 offsets 是对应的: chain[depth-1] 就是最终 get_block 需要 // 的 block partial = ext2_get_branch(inode, depth, offsets, chain, &err); /* Simplest case - block found, no allocation needed */ if (!partial): // map_bh 会将 bh_result 设置为 BH_mapped map_bh(bh_result, inode->i_sb, le32_to_cpu(chain[depth-1].key));
1.2.3.2. Allocate data block
分配 data block 与分配 inode 类似, 也是使用了某种 heuristic. 与分配 inode 不同的是, 分配 data block 主要考虑的是 locality, 简单的说:
- 能连续分配的尽量连续分配
- 同一个 inode 的 block 应该尽量分配在一起
ext2_get_block: // 假设前面的代码没有找到对应的 block, 说明需要分配一个新的 block // ext2_find_goal 用来找一个 block 作为 goal, 它并不关心这个 goal // 是否已经被占用, 只是为了找一个最理想的目标, goal 真正是否能分配以及 // 被占用了怎么处理由 ext2_alloc_branch 处理 ext2_find_goal(inode, iblock, chain, partial, &goal) if ((block == ei->i_next_alloc_block + 1) && ei->i_next_alloc_goal): ei->i_next_alloc_block++; ei->i_next_alloc_goal++; // 若当前是某种连续分配的情形, 则 goal 为上一次分配的 block+1 if (block == ei->i_next_alloc_block): *goal = ei->i_next_alloc_goal; if (!*goal): *goal = ext2_find_near(inode, partial); // ext2_find_near 找一个和 partial 最接近的 block, 分三种情形 /* Try to find previous block */ // 1. 找一个之前 indirect block 中已经分配过的 block for (p = ind->p - 1; p >= start; p--): if (*p) return le32_to_cpu(*p); // 2. 没有分配过, 直接使用 indirect block 自身 if (ind->bh): return ind->bh->b_blocknr; // 3. 没有使用 indirect block, 说明 inode 目前只使用了 direct block, 则 // 使用 inode (inode table) 附近的 block // 下面的 heuristic 是指: 在 inode 所在的 group 找一个 block, 并且 // 通过 pid 避免多个进程使用同一块 block 区域... bg_start = (ei->i_block_group * EXT2_BLOCKS_PER_GROUP(inode->i_sb)) + le32_to_cpu(EXT2_SB(inode->i_sb)->s_es->s_first_data_block); colour = (current->pid % 16) * (EXT2_BLOCKS_PER_GROUP(inode->i_sb) / 16); return bg_start + colour; // find goal 以后, ext2_alloc_branch 负责真正去分配 block ext2_alloc_branch(inode, left, goal, offsets+(partial-chain), partial); ext2_new_block(goal) // 若 goal 空闲, 返回 goal, 否则, 向后一定范围内查找一个空闲的 ret_block = grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data, group_size, ret_block); // 若上一步没有找到, 则从当前 group 开始从所有 group 查找 // ...
1.2.3.3. Release data block
前面提到的 unlink 已经提到释放 data block 的过程: unlink 时通过 ext2_truncate 调用 ext2_free_blocks 释放 data block, 主要就是修改 block bitmap
1.3. Ext2 file holes
1.3.1. File holes on disk
#> dd if=/dev/zero of=hole bs=4096 count=1 seek=5 ~@dell-work> sudo debugfs /dev/sda2 debugfs 1.42.13 (17-May-2015) debugfs: cd sunway debugfs: bmap hole 0 0 debugfs: bmap hole 1 0 debugfs: bmap hole 2 0 debugfs: bmap hole 3 0 debugfs: bmap hole 4 0 debugfs: bmap hole 5 86016417
1.3.2. Read from file holes
block_read_full_page: get_block(inode, iblock, bh, 0) // 对于空洞部分的 block, get_block 返回 BH_mapped 为 false if (!buffer_mapped(bh)): void *kaddr = kmap_atomic(page, KM_USER0); memset(kaddr + i * blocksize, 0, blocksize); set_buffer_uptodate(bh); // continued, submit_bh(bh) is skipped continue;
1.4. 使用 debugfs 观察磁盘布局
~@dell-work> sudo debugfs debugfs 1.42.13 (17-May-2015) debugfs: open -w /dev/loop0 debugfs: stats ... Inode count: 32 Block size: 1024 Blocks per group: 8192 Inode size: 128 Group 0: block bitmap at 3, inode bitmap at 4, inode table at 5 233 free blocks, 21 free inodes, 2 used directories ... // 展示 inode bitmap 的使用 debugfs: bd 4 0000 ff07 0000 ffff ffff ffff ffff ffff ffff ................ 0020 ffff ffff ffff ffff ffff ffff ffff ffff ................ * debugfs: ls 2 (12) . 2 (12) .. 11 (1000) lost+found debugfs: write hello hello Allocated inode: 12 debugfs: ls 2 (12) . 2 (12) .. 11 (20) lost+found 12 (980) hello debugfs: quit ~@dell-work> sudo debugfs debugfs 1.42.13 (17-May-2015) debugfs: open -w /dev/loop0 debugfs: bd 4 0000 ff0f 0000 ffff ffff ffff ffff ffff ffff ................ 0020 ffff ffff ffff ffff ffff ffff ffff ffff ................ * // NOTE: 增加一个 inode 12 后, inode bitmap 由 ff07 -> ff0f // 若再增加 13, 14, 15, 16, 17 inode, 则 bitmap 会依次变为 ff1f -> ff3f -> ff7f -> ffff -> ffff01 // 最后一个是 ffff01 而不是 ffff10, 因为 kernel 是使用 bts 指令来置位的 // 后面的部分展示的是所谓的 "恢复已删除文件" 的原理 debugfs: rm hello debugfs: lsdel Inode Owner Mode Size Blocks Time deleted 12 0 100644 6 1/ 1 Sun Jun 12 11:49:44 2016 1 deleted inodes found. debugfs: id <12> 0000 a481 0000 0600 0000 badb 5c57 badb 5c57 ..........\W..\W 0020 badb 5c57 d8db 5c57 0000 0000 0200 0000 ..\W..\W........ 0040 0000 0000 0000 0000 1700 0000 0000 0000 ................ 0060 0000 0000 0000 0000 0000 0000 0000 0000 ................ * # imap 显示 inode 12 在 inode table 的位置为 block 6+0x180 # 手动的计算过程为: inode table 起始于 block 5, 这个 block 保存着 # inode 1 ~ inode 8 共 8 个 inode (1024/128=8), block 6 中依次为 9, 10, 11, 12... # 所以 inode 12 的 offset 为 3*128=0x180 debugfs: imap <12> Inode 12 is part of block group 0 located at block 6, offset 0x0180 debugfs: bd 6 0000 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 0400 c041 0000 0030 0000 f0d9 5c57 f0d9 5c57 .A...0....\W..\W 0420 f0d9 5c57 0000 0000 0000 0200 1800 0000 ..\W............ 0440 0000 0000 0000 0000 0a00 0000 0b00 0000 ................ 0460 0c00 0000 0d00 0000 0e00 0000 0f00 0000 ................ 0500 1000 0000 1100 0000 1200 0000 1300 0000 ................ 0520 1400 0000 1500 0000 0000 0000 0000 0000 ................ 0540 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 0600 a481 0000 0600 0000 badb 5c57 badb 5c57 ..........\W..\W 0620 badb 5c57 d8db 5c57 0000 0000 0200 0000 ..\W..\W........ 0640 0000 0000 0000 0000 1700 0000 0000 0000 ................ 0660 0000 0000 0000 0000 0000 0000 0000 0000 ................ * debugfs: quit ~@dell-work> sudo dd if=/dev/loop0 of=dump bs=1024 count=1 skip=6 ~@dell-work> od -A x -t x1 dump 000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 000100 c0 41 00 00 00 30 00 00 f0 d9 5c 57 f0 d9 5c 57 000110 f0 d9 5c 57 00 00 00 00 00 00 02 00 18 00 00 00 000120 00 00 00 00 00 00 00 00 0a 00 00 00 0b 00 00 00 000130 0c 00 00 00 0d 00 00 00 0e 00 00 00 0f 00 00 00 000140 10 00 00 00 11 00 00 00 12 00 00 00 13 00 00 00 000150 14 00 00 00 15 00 00 00 00 00 00 00 00 00 00 00 000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 000180 a4 81 00 00 06 00 00 00 ba db 5c 57 ba db 5c 57 000190 ba db 5c 57 d8 db 5c 57 00 00 00 00 02 00 00 00 0001a0 00 00 00 00 00 00 00 00 17 00 00 00 00 00 00 00 0001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 000400 ~@dell-work> sudo debugfs debugfs 1.42.13 (17-May-2015) debugfs: open -w /dev/loop0 debugfs: lsdel Inode Owner Mode Size Blocks Time deleted 12 0 100644 6 1/ 1 Sun Jun 12 11:49:44 2016 1 deleted inodes found. debugfs: dump <12> aaa ~@dell-work> cat aaa hello debugfs: blocks <12> 23 debugfs: bd 23 0000 6865 6c6c 6f0a 0000 0000 0000 0000 0000 hello........... 0020 0000 0000 0000 0000 0000 0000 0000 0000 ................ * debugfs: zap_block -o 0x180 -l 128 6 debugfs: quit ~@dell-work> sudo debugfs debugfs 1.42.13 (17-May-2015) debugfs: open -w /dev/loop0 debugfs: id <12> 0000 0000 0000 0000 0000 0000 0000 0000 0000 ................ * debugfs: lsdel Inode Owner Mode Size Blocks Time deleted 0 deleted inodes found.
需要注意的是 debugfs dump 或 od 在 dump 时, 第一列的 file offset 是 octal 而不是 hex
1.5. Journaling
1.6. Appendix
1.6.1. immutable files
通过 chattr/lsattr 可以修改/查看文件的 attribute, 这里的 attribute 包括:
- S_IMMUTABLE, 文件不可更改, 删除, 改名等
- S_APPEND, 文件只能追加
- …
attribute 与 ext2 的 extended attribute 并没有关系, 它是 VFS 定义的 (inode->i_flags 保存了文件的 attribute), ext2 通过如下代码支持 attribute:
1.6.1.1. VFS 中的 IS_IMMUTABLE 宏
VFS 在许多地方都会调用 IS_IMMUTABLE 一类的宏来判断 inode->i_flags, ext2 在 read inode 时会负责初始化 inode->i_flags
ext2_read_inode: ext2_set_inode_flags(inode) unsigned int flags = EXT2_I(inode)->i_flags; if (flags & EXT2_IMMUTABLE_FL): inode->i_flags |= S_IMMUTABLE; // if xxx
1.6.1.2. 支持 chattr/lsattr
ext2_ioctl: switch (cmd) { case EXT2_IOC_GETFLAGS: flags = ei->i_flags & EXT2_FL_USER_VISIBLE; return put_user(flags, (int __user *) arg); case EXT2_IOC_SETFLAGS: ei->i_flags = flags; ext2_set_inode_flags(inode);
1.6.2. reserved block
ext2_new_block: reserve_blocks() free_blocks = percpu_counter_read_positive(&sbi->s_freeblocks_counter); root_blocks = le32_to_cpu(es->s_r_blocks_count); if (free_blocks < root_blocks + count && !capable(CAP_SYS_RESOURCE) && sbi->s_resuid != current->fsuid && (sbi->s_resgid == 0 || !in_group_p (sbi->s_resgid))): /* * We are too close to reserve and we are not privileged. * Can we allocate anything at all? */ if (free_blocks > root_blocks) count = free_blocks - root_blocks; else return 0; return count; // reserve_blocks ends
1.6.3. Ext2 directory layout
ext2 的目录的 data block 保存着一系列的 ext2_dir_entry_2:
struct ext2_dir_entry_2 { __le32 inode; /* Inode number */ __le16 rec_len; /* Directory entry length */ __u8 name_len; /* Name length */ __u8 file_type; char name[EXT2_NAME_LEN]; /* File name */ };
1.6.4. extended attribute
Ext2 支持 inode 扩展属性, 扩展属性并不像普通 inode 属性那样保存在 128B 的 inode table entry 中, 而是通过一个单独的 block 保存, ext2_inode_info->i_file_acl 指向这个 block, 从 i_file_acl 名字也能猜到 extended attribute 主要用来实现 ACL (例如 user1, user2 可以访问这个文件), 另外, selinux 也使用 extended attr 来保存 file context
1.6.5. bitmap 如何被 sync 到磁盘
从 fsync 代码可以看到 inode 对应的 data 部分 (address_space) 和 inode 部分 (实际对应于 inode table) 最终会通过 writepage 写到磁盘 (), 但 superblock, inode bitmap 以及 block bitmap 如何被写到磁盘?
从代码上猜测是通过 block device file:
sys_sync: sync_inodes(); /* All mappings, inodes and their blockdevs */ while ((sb = get_super_to_sync()) != NULL): // sb 记录的所有 dirty inode 及其 mappings 被写回 sync_inodes_sb(sb, 0); sync_blockdev(sb->s_bdev); // 由于文件系统内部对设备的读写都是通过 bd_inode 的 pagecache // (_bread), 所以 sync bd_inode 的 address_space 会导致文件系统 // 内部的这样修改(包括对 bitmap 的修改)被写回 filemap_fdatawrite(bdev->bd_inode->i_mapping); sync_supers(); /* Write the superblocks */ // sync_supers 只是修改 superblock 并置相应 block 为 dirty, // 后续的 sync_inodes 会保证它被写回 sync_inodes(); /* sync inodes again? */
1.6.6. Ext2 会占用多少磁盘空间
新建的 ext2 文件系统占用的磁盘空间, 主要依赖于 groups 的个数和 inode 的个数.
以一个 128GB 的磁盘为例, 假设 ext2 block size 为 1KB
- 一个 group 能索引的空间大小为 1K*8*1K = 8M, 则一共需要 128G/8M = 16384 个 group, 这些 group 的 block bitmap 和 inode bitmap 一共需要占用 16384 * 2K = 32MB
- 默认配置下, 每 8 KB 分配一个 inode, 则一共 128G/8K = 16777216 个 inode, 每个 inode 占 128B, 一共需要 2G
可见, 占用空间的主要是 inode table, 不考虑 inode table 与 block size 的对齐的话, 比例大约是 128/8K = 1.6%, 这个比例和 ext2 的 block size 基本没关系, 和 mkfs.ext2 是指定的 bytes_per_inode 有关.
1.6.7. Utility
1.6.7.1. tune2fs
1.6.7.2. dumpe2fs
1.6.7.3. blktrace
// dev/sda2 是 /home/sunway 所在的设备 # 1> su $ 1> cd /tmp; blktrace -d /dev/sda2 -o trace # 2> cd /home/sunway # 2> dd if=/dev/zero of=test bs=1024 count=100000 $ 1> blkparse -i trace|grep " A " 8,0 2 4012 6.118875228 6444 A W 517705728 + 2048 <- (8,2) 517703680 8,0 2 4015 6.118984246 6444 A W 517707776 + 2048 <- (8,2) 517705728 8,0 2 4018 6.119091708 6444 A W 517709824 + 2048 <- (8,2) 517707776 8,0 2 4021 6.119195285 6444 A W 517711872 + 2048 <- (8,2) 517709824 8,0 2 4024 6.119299673 6444 A W 517713920 + 2048 <- (8,2) 517711872 .... // blkparse 打印的 517705728 是 IO 操作的起始 sector, 由于 /dev/sda2 的 block size 是 4K, // 所以除 8 得到 IO 操作的 block $ 1> echo 517705728/8|bc 64713216 # 3> sudo debugfs debugfs: open /dev/sda2 debugfs: icheck 64713216 Block Inode number 64713216 21496249 debugfs: ncheck 21496249 Inode Pathname 21496249 /sunway/test ^C
1.6.7.4. debugfs
1.6.7.4.1. stats
1.6.7.4.2. stat
1.6.7.4.3. id
dump inode
1.6.7.4.4. bd
dump block
1.6.7.4.5. dump
dump file
1.6.7.4.6. blocks
show data blocks of a file
1.6.7.4.7. icheck
block -> inode
1.6.7.4.8. ncheck
inode -> file name
1.6.7.4.9. imap
inode -> inode table entry
1.6.7.4.10. bmap
logical block -> physical block
1.6.7.4.11. mi
modify inode by inode structure
1.6.7.4.12. lsdel
1.6.7.4.13. undel
1.6.7.4.14. write
copy file from host