Linux Kernel: Ext2

Table of Contents

1. Linux Kernel: Ext2

1.1. Ext2 的磁盘布局

Ext2 将磁盘分割为几个连续的, 大小相同的 block group, 每个 group 的结构为:

  1. super block

    占一个 block (如果没有特别说明, 这里及以后所指的的 block 都是指 mkfs 时指定的 block size, 与物理设备的 block (sector)无关)

  2. group descriptors

    占用多个 block, 表示所有的 group 的相关信息 (而并非仅保存当前 group 的信息)

  3. block bitmap

    占一个 block

  4. inode bitmap

    一个 block

  5. inode table

    占用多个 block

  6. data blocks

    占用多个 block 直到 group 末尾

之所以分成许多 group, 是为了减少外部碎片: 对于同一个文件, 尽是分配在同一个 group.

1.1.1. super block

super block 和 group descriptors 在每个 group 都有一个, 但平时只有 group 0 的 super block 会被使用, 其它 group 的都是备份, 和 fsck 有关

super block 的主要成员:

  1. s_inodes_count

    文件系统能容纳的最大的 inode 个数

  2. s_block_count

    文件系统的大小

  3. s_r_blocks_count

    reserved 的 block 数目

  4. s_groups_count

    group 数目

  5. s_free_inodes_count

    可用的 inode 数, s_free_inodes_count/s_groups_count 可计算出每个 group 平均的 inode 数, 后续创建 inode 时会根据这个值选择相应的 group

  6. s_free_blocks_count

    可用的 block 数

  7. s_log_block_size

    block 大小

  8. s_blocks_per_group

    每个 group 的 block 数

  9. s_inodes_per_group

    每个 group 的 inode 数

  10. s_mtime/s_wtime

    最近的 mount 时间和修改时间

  11. s_mnt_count

    mount 次数

  12. s_def_resuid/s_def_resgid

    可用使用 reserved block 的 uid/gid

  13. s_last_mounted

    最近一次被挂载到哪

super block 的这些成员大多是运行时的统计信息, 但有一些是需要用户通过 mkfs 或 tune2fs 指定的:

  1. s_r_blocks_count

    默认的 reserved block 数目是 s_block_count 的 5%

  2. s_log_block_size

    block 大小默认为 1024B

  3. s_blocks_per_group

    大小和 block 大小有关: 因为一个 group 只用一个 block 来保存 block bitmap, 所以对于 1024B 的 block 来说, block bitmap 最多只能表示 1024*8*1024=8MB 的大小, 即 1024*8 = 8192 个 block

  4. s_inodes_per_group

    和磁盘大小有关, 默认情况下, 每个 inode 需要至少 8KB, 所以假设 block size 为 1K, 若磁盘大小为 256K, 则 inode 总数是 256/8 = 32, group 为 1, s_inodes_per_group 为 32.

  5. s_def_resuid/s_def_resgid

1.1.2. group descriptors

group descriptors 是一系列的 group descriptor 的集合, 占用多个 block, 每个 group descriptor 的成员包括:

  1. bg_block_bitmap

    block bitmap 对应的 block number

  2. bg_inode_bitmap

    inode bitamp 对应的 block number

  3. 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:

  1. 若新建的 inode 对应一个目录:
    1. 若 parent 是 root inode, 则 inode 会尽量平均分布在各个 group: 选择 free inode 和 free block 大于平均值的 group
    2. 对于 parent 不是 root 的 inode, 会尽量选择 parent inode 所在的 group, 除非这个 group 有太多的目录或者 free inode 已经很少
    3. 做为 1 和 2 的 fallback, 如果 1 和 2 没有找到合适的 group, 则从 parent inode 所在的 group 开始, 找一个 free inode 大于平均值的 group
  2. 若新建 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 主要的逻辑是:

  1. inode 尽量与 parent inode 位于同一个 group
  2. 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, 其中:

  1. 0~11 是直接索引
  2. 12 是一级间接索引
  3. 13 是二级间接索引
  4. 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, 简单的说:

  1. 能连续分配的尽量连续分配
  2. 同一个 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

  1. 一个 group 能索引的空间大小为 1K*8*1K = 8M, 则一共需要 128G/8M = 16384 个 group, 这些 group 的 block bitmap 和 inode bitmap 一共需要占用 16384 * 2K = 32MB
  2. 默认配置下, 每 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

Author: [email protected]
Date: 2016-05-16 Mon 00:00
Last updated: 2024-08-19 Mon 20:15

知识共享许可协议