レガシーガジェット研究所

気になったことのあれこれ。

Linux Kernel ~ ext2ファイルシステム ~

概要

「詳解Linux Kernel」を参考にVersion 2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelx86とする。

今回はrxt2のファイルシステムについて見ていく。

ext2

ext2は1994年に導入されたファイルシステムLinuxにとってネイティブのファイルシステムである。ext2の特徴としては以下のようなものが挙げられる。

  • ファイルシステム作成時に扱うファイルの平均サイズの予測からブロックサイズを1~4KBの間で選択可能。
  • ファイルシステム作成時に扱うファイル数の予測から一定サイズあたりのiノード数を選択可能。
  • ファイルの断片化を可能な限り防ぐためファイルの拡張のための隣接する領域を予め確保する。
  • シンボリックリンクの文字列が60文字以下の場合にデータブロックではなくiノードにパス名を保存する。

データ構造

ext2パーティションは先頭のブートブロックとその後に末尾まで続く複数のブロックグループから構成される。

詳解Linuxカーネル

全てのブロックグループは同じ大きさなのでインデックスからその位置を算出することが可能。この仕組みによって同じファイルのデータブロックは同じブロックグループ内に格納しようと試みるので断片化が起こりづらくなる。

ブロックグループ内には以下の情報が含まれている。

  • スーパーブロック
  • ブロックグループディスクリプタ
  • データブロックのビットマップ
  • iノードのビットマップ
  • iノードテーブル
  • データブロック

スーパーブロック及びプロックグループディスクリプタは先頭からブロックグループ0, 1、あとは3, 5, 7の累乗番目に存在する。カーネルは使用するのは0番目のもののみだが他のブロックグループにバックアップとして福祉しておくことでプライマリ(0番目)が壊れた際にも復旧(fsck)が可能となる。

ブロックグループの個数はパーティションサイズとブロックサイズに依存する。主な制約としては上記のビットマップが単一のブロックに収まる必要があるということ。

スーパーブロック

スーパーブロックはext2_super_block構造体で定義されている。スーパーブロックはパーティション全体の情報を保持している。

// include/linux/ext2_fs.h
struct ext2_super_block {
    __le32  s_inodes_count;     /* iノード数 */
    __le32  s_blocks_count;     /* ブロック数 */
    __le32  s_r_blocks_count;   /* 予約ブロック数 */
    __le32  s_free_blocks_count;    /* 空きブロック数 */
    __le32  s_free_inodes_count;    /* 空きiノード数 */
    __le32  s_first_data_block; /* 先頭データブロック番号 */
    __le32  s_log_block_size;   /* ブロック長 */
    __le32  s_log_frag_size;    /* フラグメント長 */
    __le32  s_blocks_per_group; /* 各グループのブロック数 */
    __le32  s_frags_per_group;  /* # 各ブループのフラグメント数 */
    __le32  s_inodes_per_group; /* # 各グループのiノード数 */
    __le32  s_mtime;        /* 最終マウント時間 */
    __le32  s_wtime;        /* 参集書き込み時間 */
    __le16  s_mnt_count;        /* マウント操作カウンタ */
    __le16  s_max_mnt_count;    /* fsckまでのマウント回数 */
    __le16  s_magic;        /* マジック番号 */
    __le16  s_state;        /* 状態フラグ */
    __le16  s_errors;       /* エラー検出時の動作 */
    __le16  s_minor_rev_level;  /* マイナー改版レベル */
    __le32  s_lastcheck;        /* 最終fsck時間 */
    __le32  s_checkinterval;    /* fsckを行う時間感覚 */
    __le32  s_creator_os;       /* ファイルシステムを作成したOS */
    __le32  s_rev_level;        /* 改版レベル */
    __le16  s_def_resuid;       /* 予約ブロック用のデフォルトUID */
    __le16  s_def_resgid;       /* 予約ブロック用のデフォルトGID */

    __le32  s_first_ino;        /* 最初の未予約iノード番号 */
    __le16   s_inode_size;      /* iノード構造体のサイズ */
    __le16  s_block_group_nr;   /* スーパーブロックのブロックグループ番号 */
    __le32  s_feature_compat;   /* 互換機能の有効無効を示すビットマップ */
    __le32  s_feature_incompat;     /* 互換のない機能の有効無効を示すビットマップ */
    __le32  s_feature_ro_compat;    /* 読み取り専用時の互換機能の有効無効を示すビットマップ */
    __u8    s_uuid[16];        /* 128bitファイルシステム識別子 */
    char   s_volume_name[16];     /* ボリューム名 */
    char   s_last_mounted[64];    /* 前回のマウントポイント */
    __le32  s_algorithm_usage_bitmap; /* 圧縮処理で使用 */

    __u8    s_prealloc_blocks;  /* 先行割り当てを行うブロック数 */
    __u8    s_prealloc_dir_blocks;  /* ディレクトリ用に先行割り当てを行うブロック数 */
    __u16   s_padding1;
    
    :
    // ジャーナリングのためのフィールド
}

グループディスクリプタ

グループディスクリプタext2_group_desc構造体で定義されおり、グループ毎の情報が格納されている。グループディスクリプタテーブルの連続する複数のブロックからなる領域にグループの数だけ配置される。

// include/linux/ext2_fs.h
struct ext2_group_desc
{
    __le32  bg_block_bitmap;        /* ブロックビットマップのブロック番号 */
    __le32  bg_inode_bitmap;        /* iノードビットマップのブロック番号 */
    __le32  bg_inode_table;     /* iノードテーブルのブロック番号 */
    __le16  bg_free_blocks_count;   /* グループ内の空きブロック数 */
    __le16  bg_free_inodes_count;   /* グループ内の空きiノード数 */
    __le16  bg_used_dirs_count; /* グループ内のディレクトリ数 */
    __le16  bg_pad; /* ワード境界のアラインメント用 */
    __le32  bg_reserved[3]; /* パディング */
};

データブロックビットマップ及びiノードビットマップ

各ビットマップはグループディスクリプタが保持しているグループ内の位置にある単一のブロックで表現される。

iノードテーブル

iノードテーブルは連続した複数のブロックからなり、複数のiノードが各ブロックに格納される。iノードのサイズは128バイトで、ext2_inode構造体として定義されている。

// include/linux/ext2_fs.h
struct ext2_inode {
    __le16  i_mode;     /* ファイルの種類及びアクセス権 */
    __le16  i_uid;      /* UIDの下位16bit */
    __le32  i_size;     /* ファイルサイズ(単位:バイト) */
    __le32  i_atime;    /* 最終アクセス時間 */
    __le32  i_ctime;    /* 最終iノード変更時間 */
    __le32  i_mtime;    /* 最終ファイル内容変更時間 */
    __le32  i_dtime;    /* ファイル削除時刻 */
    __le16  i_gid;      /* GIDの下位16bit */
    __le16  i_links_count;  /* ハードリンクカウンタ */
    __le32  i_blocks;   /* データブロック数 */
    __le32  i_flags;    /* ファイルフラグ */
    union {
        struct {
            __le32  l_i_reserved1;
        } linux1;
        struct {
            __le32  h_i_translator;
        } hurd1;
        struct {
            __le32  m_i_reserved1;
        } masix1;
    } osd1;             /* OS固有情報 */
    __le32  i_block[EXT2_N_BLOCKS];/* データブロックの番号 */
    __le32  i_generation;   /* ファイルバージョン(NFS用) */
    __le32  i_file_acl; /* ファイルのACL(Access Control List) */
    __le32  i_dir_acl;  /* ディレクトリのACL */
    __le32  i_faddr;    /* フラグメントのアドレス */
    union {
        struct {
            __u8    l_i_frag;   /* Fragment number */
            __u8    l_i_fsize;  /* Fragment size */
            __u16   i_pad1;
            __le16  l_i_uid_high;   /* these 2 fields    */
            __le16  l_i_gid_high;   /* were reserved2[0] */
            __u32   l_i_reserved2;
        } linux2;
        struct {
            __u8    h_i_frag;   /* Fragment number */
            __u8    h_i_fsize;  /* Fragment size */
            __le16  h_i_mode_high;
            __le16  h_i_uid_high;
            __le16  h_i_gid_high;
            __le32  h_i_author;
        } hurd2;
        struct {
            __u8    m_i_frag;   /* Fragment number */
            __u8    m_i_fsize;  /* Fragment size */
            __u16   m_pad1;
            __u32   m_i_reserved2[2];
        } masix2;
    } osd2;             /* OS固有情報 */
};

#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)

ext2_inodeは主にファイルの情報を保持している。

iノード拡張属性

拡張属性とはiノードとは別のブロックに配置されるデータ構造で以下のようにブロック上に展開される。

拡張属性が展開されるブロックはiノードのi_file_aclメンバで参照される。

// fs/ext2/xattr.h
struct ext2_xattr_entry {
    __u8    e_name_len; /* 名前の長さ */
    __u8    e_name_index;   /* 拡張属性名のインデックス */
    __le16  e_value_offs;   /* ディスクブロック内にある値の位置を示すオフセット */
    __le32  e_value_block;  /* ディスクブロック属性が保存されているブロック */
    __le32  e_value_size;   /* 値のデータサイズ */
    __le32  e_hash;     /* 要素名と値のハッシュ */
    char   e_name[0]; /* 可変長の要素名 */
};

拡張属性の操作のためのシステムコールには以下のようにものがある。

システムコール 用途
setxattr, lsetxattr, fsetxattr 拡張属性の設定
getxattr, lgetxattr, fgetxattr 拡張属性値の取得
listxattr, llistxattr, flistxattr 全ての拡張属性値の取得
removexattr, lremovexattr, fremovexattr

アクセス制御リスト

アクセス制御リスト(Access Control List)はUnixファイルシステムの保護機能として存在し、所有者、グループ、その他の3つの指定方法とは別にそれぞれのファイルにACLを設定し、ユーザまたはグループを指定することでアクセス制御を行う。ACLはiノードの拡張属性(前述)を用いて実現されている。

ファイル種別

ext2が認識する異なる種別のファイルは異なる方法でデータブロックを使用する。

通常ファイル

通常のファイルは作成時に空でありデータブロックを必要としない。

ディレクト

ext2ではディレクトリもファイルとして実装しており、ディレクトリのデータブロックにはファイル名と対応するiノード番号を一組にして保持している。ディレクトリのデータブロックに保持されているのは可変長のデータ構造であるext2_dir_entry_2構造体で以下のように定義されている。

// include/linux/ext2_fs.h
struct ext2_dir_entry_2 {
    __le32  inode;          /* iノード番号 */
    __le16  rec_len;        /* エントリの長さ */
    __u8    name_len;       /* 名前の長さ */
    __u8    file_type;  /* ファイル種別 */
    char   name[EXT2_NAME_LEN];    /* ファイル名(可変長: 4の倍数サイズ) */
};

nameは4バイトでアラインメントされており\0で埋められる。

ファイル種別は以下のように定義されている。

// include/linux/ext2_fs.h
enum {
    EXT2_FT_UNKNOWN,    /* 未定義 */
    EXT2_FT_REG_FILE,   /* 通常ファイル */
    EXT2_FT_DIR,        /* ディレクトリ */
    EXT2_FT_CHRDEV, /* キャラクタデバイス */
    EXT2_FT_BLKDEV, /* ブロックデバイス */
    EXT2_FT_FIFO,       /* パイプ */
    EXT2_FT_SOCK,       /* ソケット */
    EXT2_FT_SYMLINK,    /* シンボリックリンク */
    EXT2_FT_MAX
};

シンボリックリンク

シンボリックリンクが参照するファイルパスが60バイト未満の場合にはiノードのi_blockメンバ内に保存する。60バイト以上の場合にはパス名の文字列ためのデータブロックが必要となる。

バイスファイル、パイプ、ソケット

この種類のファイルはデータブロックを必要としない。

ext2が使用するメモリ上のデータ構造

効率的に処理を行うためext2パーティションではファイルシステムのマウント時にほとんどのデータ構造をRAMに読み込む。RAMに読み込むことで頻繁に変更されるデータを効率よく処理することが可能となる。

  • 新たなファイルの作成時にext2スーパーブロックのs_free_inodes_countメンバ及び対応するグループディスクリプタbg_free_inodes_countメンバの値をデクリメントする
  • ファイルの拡張時にext2スーパーブロックのs_free_blocks_countメンバと対応するブロックグループディスクリプタbg_free_blocks_countメンバの値を変更する
  • 既存のファイルを更新した際にext2スーパーブロックのs_writeメンバを更新する

上記の操作で処理するデータは全てディスク上のext2パーテションのブロックが保持しているため、当該データにはページキャッシュを使用する。

ext2のデータ構造とVFSのデータは以下のように対応する。

種類 ディスク上 メモリ上 キャッシュ
スーパーブロック ext2_super_block ext2_sb_info
ブロックグループディスクリプタ ext2_group_desc ext2_group_desc
ブロックビットマップ ブロック内のビット配列 バッファ内のビット配列 動的
iノードビットマップ ブロック内のビット配列 バッファ内のビット配列 動的
iノード ext2_inode ext2_inode_info 動的
データブロック バイト列 VFSバッファ 動的
空きiノード ext2_inode
空きブロック バイト列

"常"となっているデータは常にメモリ上に存在する。"動的"となっていデータは参照されている間はキャッシュに置かれる、そのためファイルのクローズや削除、ページフレームの回収などがキャッシュからパージされる可能性がある。

ext2スーパーブロックオブジェクト

VFSスーパーブロックのs_fs_infoメンバはファイルシステム固有のデータを保持しており、ext2の場合にはext2_sb_info構造体となる。

// include/linux/ext2_fs_sb.h
struct ext2_sb_info {
    unsigned long s_frag_size;    /* フラグメントのサイズ(単位: バイト) */
    unsigned long s_frags_per_block;/* ブロック内にあるフラグメント数 */
    unsigned long s_inodes_per_block;/* ブロック内のiノード数 */
    unsigned long s_frags_per_group;/* グループ内のフラグメント数 */
    unsigned long s_blocks_per_group;/* グループ内のブロック数 */
    unsigned long s_inodes_per_group;/* グループ内のiノード数 */
    unsigned long s_itb_per_group;    /* グループ内のiノードテーブルに使用されるブロック数 */
    unsigned long s_gdb_count;    /* グループディスクリプタ用のブロック数 */
    unsigned long s_desc_per_block;   /* ブロック内のグループディスクリプタ数 */
    unsigned long s_groups_count; /* ブロックグループ数 */
    struct buffer_head * s_sbh;    /* バッファが保持しているスーパーブロック */
    struct ext2_super_block * s_es;    /* バッファ内のスーパーブロックを参照するポインタ */
    struct buffer_head ** s_group_desc; /* グループディスクリプタ */
    unsigned long  s_mount_opt; /* マウントオプション */
    uid_t s_resuid; /* UID */
    gid_t s_resgid; /* GID */
    unsigned short s_mount_state; /* マウントの状態 */
    unsigned short s_pad; /* パディング */
    int s_addr_per_block_bits; /* ブロックアドレスのアラインメント */
    int s_desc_per_block_bits; /* ブロック内のディスクリプタ数 */
    int s_inode_size; /* iノードサイズ */
    int s_first_ino; /* 予約されていない最初のiノード番号 */
    spinlock_t s_next_gen_lock; /* ロック用メンバ */
    u32 s_next_generation; /* 次のiノードバージョン番号 */
    unsigned long s_dir_count; /* ディレクトリ数 */
    u8 *s_debts; 
    struct percpu_counter s_freeblocks_counter; /* 空きブロック数 */
    struct percpu_counter s_freeinodes_counter; /* 空きiノード数 */
    struct percpu_counter s_dirs_counter; /* ディレクトリ数 */
    struct blockgroup_lock s_blockgroup_lock; /* ブロックグループロック */
};

詳解Linuxカーネル

ext2のマウント時にはext2_fill_super関数を呼び出してext2_sb_infoを初期化する。

// fs/ext2/super.c
static int ext2_fill_super(struct super_block *sb, void *data, int silent)
{
    struct buffer_head * bh;
    struct ext2_sb_info * sbi;
    struct ext2_super_block * es;
    struct inode *root;
    unsigned long block;
    unsigned long sb_block = get_sb_block(&data);
    unsigned long logic_sb_block;
    unsigned long offset = 0;
    unsigned long def_mount_opts;
    int blocksize = BLOCK_SIZE;
    int db_count;
    int i, j;
    __le32 features;

    // ext2_sb_info
    sbi = kmalloc(sizeof(*sbi), GFP_KERNEL);
    if (!sbi)
        return -ENOMEM;
    sb->s_fs_info = sbi;
    memset(sbi, 0, sizeof(*sbi)); // ゼロクリア

    blocksize = sb_min_blocksize(sb, BLOCK_SIZE); // ブロックサイズの設定
    if (!blocksize) {
        printk ("EXT2-fs: unable to set blocksize\n");
        goto failed_sbi;
    }

    /* スーパーブロックの開始位置を算出 */
    if (blocksize != BLOCK_SIZE) {
        logic_sb_block = (sb_block*BLOCK_SIZE) / blocksize;
        offset = (sb_block*BLOCK_SIZE) % blocksize;
    } else {
        logic_sb_block = sb_block;
    }

    /* スーパーブロックの読み出し */
    if (!(bh = sb_bread(sb, logic_sb_block))) {
        printk ("EXT2-fs: unable to read superblock\n");
        goto failed_sbi;
    }
    // スーパーブロックへのポインタを取得
    es = (struct ext2_super_block *) (((char *)bh->b_data) + offset);
    sbi->s_es = es;
    sb->s_magic = le16_to_cpu(es->s_magic);

    if (sb->s_magic != EXT2_SUPER_MAGIC)
        goto cantfind_ext2;

    /* マウントオプションのパース */
    def_mount_opts = le32_to_cpu(es->s_default_mount_opts);
    if (def_mount_opts & EXT2_DEFM_DEBUG)
        set_opt(sbi->s_mount_opt, DEBUG);
    if (def_mount_opts & EXT2_DEFM_BSDGROUPS)
        set_opt(sbi->s_mount_opt, GRPID);
    if (def_mount_opts & EXT2_DEFM_UID16)
        set_opt(sbi->s_mount_opt, NO_UID32);
    if (def_mount_opts & EXT2_DEFM_XATTR_USER)
        set_opt(sbi->s_mount_opt, XATTR_USER);
    if (def_mount_opts & EXT2_DEFM_ACL)
        set_opt(sbi->s_mount_opt, POSIX_ACL);
    
    if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_PANIC)
        set_opt(sbi->s_mount_opt, ERRORS_PANIC);
    else if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_RO)
        set_opt(sbi->s_mount_opt, ERRORS_RO);

    // 予約ブロックのデフォルトUID及びGID
    sbi->s_resuid = le16_to_cpu(es->s_def_resuid);
    sbi->s_resgid = le16_to_cpu(es->s_def_resgid);
    
    if (!parse_options ((char *) data, sbi))
        goto failed_mount;

    sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
        ((EXT2_SB(sb)->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ?
         MS_POSIXACL : 0);

    features = EXT2_HAS_INCOMPAT_FEATURE(sb, ~EXT2_FEATURE_INCOMPAT_SUPP);

    /* ブロックサイズを算出 */
    blocksize = BLOCK_SIZE << le32_to_cpu(sbi->s_es->s_log_block_size);

    // ファイル帳の最大値
    sb->s_maxbytes = ext2_max_size(sb->s_blocksize_bits);

    sbi->s_frag_size = EXT2_MIN_FRAG_SIZE <<
                   le32_to_cpu(es->s_log_frag_size);
    if (sbi->s_frag_size == 0)
        goto cantfind_ext2;
    sbi->s_frags_per_block = sb->s_blocksize / sbi->s_frag_size;

    /* グループ内のブロック数、フラグメント数、iノード数を取得 */
    sbi->s_blocks_per_group = le32_to_cpu(es->s_blocks_per_group);
    sbi->s_frags_per_group = le32_to_cpu(es->s_frags_per_group);
    sbi->s_inodes_per_group = le32_to_cpu(es->s_inodes_per_group);

    if (EXT2_INODE_SIZE(sb) == 0)
        goto cantfind_ext2;
    /* ブロック毎のiノード数 */
    sbi->s_inodes_per_block = sb->s_blocksize / EXT2_INODE_SIZE(sb);
    if (sbi->s_inodes_per_block == 0)
        goto cantfind_ext2;
    /* iノードテーブルに使用されるブロック数 */
    sbi->s_itb_per_group = sbi->s_inodes_per_group /
                    sbi->s_inodes_per_block;
    /* ブロック内のグループディスクリプタ数 */
    sbi->s_desc_per_block = sb->s_blocksize /
                    sizeof (struct ext2_group_desc);
    sbi->s_sbh = bh; // スーパーブロック
    sbi->s_mount_state = le16_to_cpu(es->s_state); // マウント状態
    sbi->s_addr_per_block_bits = // アドレスのアライメント
        log2 (EXT2_ADDR_PER_BLOCK(sb));
    sbi->s_desc_per_block_bits = // ブロック内のディスクリプタ数
        log2 (EXT2_DESC_PER_BLOCK(sb));

    // マジックナンバーのチェック
    if (sb->s_magic != EXT2_SUPER_MAGIC)
        goto cantfind_ext2;

    if (EXT2_BLOCKS_PER_GROUP(sb) == 0)
        goto cantfind_ext2;
    /* ブロックグループ数 */
    sbi->s_groups_count = (le32_to_cpu(es->s_blocks_count) -
                        le32_to_cpu(es->s_first_data_block) +
                       EXT2_BLOCKS_PER_GROUP(sb) - 1) /
                       EXT2_BLOCKS_PER_GROUP(sb);
    db_count = (sbi->s_groups_count + EXT2_DESC_PER_BLOCK(sb) - 1) /
           EXT2_DESC_PER_BLOCK(sb);
    /* ブロックグループディスクリプタ */
    sbi->s_group_desc = kmalloc (db_count * sizeof (struct buffer_head *), GFP_KERNEL);
    if (sbi->s_group_desc == NULL) {
        printk ("EXT2-fs: not enough memory\n");
        goto failed_mount;
    }

    /* 空きブロック数、空きiノード数、ディレクトリ数を初期化 */
    percpu_counter_init(&sbi->s_freeblocks_counter);
    percpu_counter_init(&sbi->s_freeinodes_counter);
    percpu_counter_init(&sbi->s_dirs_counter);
    bgl_lock_init(&sbi->s_blockgroup_lock);

    /*  */
    sbi->s_debts = kmalloc(sbi->s_groups_count * sizeof(*sbi->s_debts),
                   GFP_KERNEL);
    if (!sbi->s_debts) {
        printk ("EXT2-fs: not enough memory\n");
        goto failed_mount_group_desc;
    }
    memset(sbi->s_debts, 0, sbi->s_groups_count * sizeof(*sbi->s_debts));

    /* グループディスクリプタの読み込み */
    for (i = 0; i < db_count; i++) {
        block = descriptor_loc(sb, logic_sb_block, i);
        sbi->s_group_desc[i] = sb_bread(sb, block);
        if (!sbi->s_group_desc[i]) {
            for (j = 0; j < i; j++)
                brelse (sbi->s_group_desc[j]);
            printk ("EXT2-fs: unable to read group descriptors\n");
            goto failed_mount_group_desc;
        }
    }
    
    sbi->s_gdb_count = db_count; // グループディスクリプタ用のブロック数
    get_random_bytes(&sbi->s_next_generation, sizeof(u32));
    spin_lock_init(&sbi->s_next_gen_lock);
    
    sb->s_op = &ext2_sops; // スーパーブロック用のメソッド
    sb->s_export_op = &ext2_export_ops;
    sb->s_xattr = ext2_xattr_handlers; // 拡張属性のためのハンドラ群
    root = iget(sb, EXT2_ROOT_INO);
    sb->s_root = d_alloc_root(root); // ルートディレクトリ用のdエントリ
    if (!sb->s_root) {
        iput(root);
        printk(KERN_ERR "EXT2-fs: get root inode failed\n");
        goto failed_mount2;
    }
    if (!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size) {
        dput(sb->s_root);
        sb->s_root = NULL;
        printk(KERN_ERR "EXT2-fs: corrupt root inode, run e2fsck\n");
        goto failed_mount2;
    }
    ext2_setup_super (sb, es, sb->s_flags & MS_RDONLY);
    percpu_counter_mod(&sbi->s_freeblocks_counter,
                ext2_count_free_blocks(sb));
    percpu_counter_mod(&sbi->s_freeinodes_counter,
                ext2_count_free_inodes(sb));
    percpu_counter_mod(&sbi->s_dirs_counter,
                ext2_count_dirs(sb));
    return 0;

(*一部エラー処理やバリデーションを省略)

ext2 iノードオブジェクト

VFSがディスク上のiノードにアクセスする時に、ディスクのiノードに対応するext2_inode_info型のiノードディスクリプタを生成する。

// fs/ext2/ext2.h
struct ext2_inode_info {
    __le32  i_data[15]; // データブロック
    __u32   i_flags; // フラグ
    __u32   i_faddr; // フラグメントのアドレス
    __u8    i_frag_no; // フラグメント番号
    __u8    i_frag_size; // フラグメントサイズ
    __u16   i_state; // ステータス
    __u32   i_file_acl; // ファイル用のACL
    __u32   i_dir_acl; // ディレクトリ用のACL
    __u32   i_dtime; // 削除時刻

    __u32   i_block_group; /* ファイルのiノードを保持しているブロックグループ番号 */

    __u32   i_next_alloc_block; /* ファイル内の直近でアロケートされたブロック番号 */

    __u32   i_next_alloc_goal; /* i_next_alloc_blockの対となる物理ブロック番号 */
    __u32   i_prealloc_block; // 予約ブロック
    __u32   i_prealloc_count; // 予約ブロック数
    __u32   i_dir_start_lookup; // 
#ifdef CONFIG_EXT2_FS_XATTR
    struct rw_semaphore xattr_sem; // 拡張属性の取得操作のための読み書きセマフォ
#endif
#ifdef CONFIG_EXT2_FS_POSIX_ACL
    struct posix_acl   *i_acl; // ファイルのACL
    struct posix_acl   *i_default_acl; // ファイルのACL
#endif
    rwlock_t i_meta_lock; // 読み書きロック変数
    struct inode   vfs_inode; // VFSのiノードオブジェクト
};

ext2のiノードオブジェクトを取得するため関数はext2_alloc_inode関数で、以下のようにスラブキャッシュから確保する。

// fs/ext2/super.c
static struct inode *ext2_alloc_inode(struct super_block *sb)
{
    struct ext2_inode_info *ei;
    
    /* スラブキャッシュからiノードオブジェクトを確保 */
    ei = (struct ext2_inode_info *)kmem_cache_alloc(ext2_inode_cachep, SLAB_KERNEL);
    if (!ei)
        return NULL;
#ifdef CONFIG_EXT2_FS_POSIX_ACL
    ei->i_acl = EXT2_ACL_NOT_CACHED;
    ei->i_default_acl = EXT2_ACL_NOT_CACHED;
#endif
    ei->vfs_inode.i_version = 1;
    return &ei->vfs_inode;
}

ext2の関数

VFSのインターフェースとなっている操作の多くに対してext2は固有の実装を行っている。

スーパーブロック操作

スーパーブロックの操作には以下のような関数を用意している。

// fs/ext2/super.c
static struct super_operations ext2_sops = {
    .alloc_inode    = ext2_alloc_inode,
    .destroy_inode  = ext2_destroy_inode,
    .read_inode = ext2_read_inode,
    .write_inode    = ext2_write_inode,
    .delete_inode   = ext2_delete_inode,
    .put_super  = ext2_put_super,
    .write_super    = ext2_write_super,
    .statfs     = ext2_statfs,
    .remount_fs = ext2_remount,
    .clear_inode    = ext2_clear_inode,
#ifdef CONFIG_QUOTA
    .quota_read = ext2_quota_read,
    .quota_write    = ext2_quota_write,
#endif
};

(上記のext2_read_inodeは特に実装が読みやすく、前述のデータ構造からiノードの位置を算出し読み出しているのがよくわかる)

iノード動作

VFSのiノード操作の一部にExt2固有の実装が存在し、通常ファイル操作はext2_file_inode_operations変数、ディレクトリ操作はext2_dir_inode_operations変数に格納されている。

// fs/ext2/namei.c
struct inode_operations ext2_file_inode_operations = {
    .truncate   = ext2_truncate,
#ifdef CONFIG_EXT2_FS_XATTR
    .setxattr   = generic_setxattr,
    .getxattr   = generic_getxattr,
    .listxattr  = ext2_listxattr,
    .removexattr    = generic_removexattr,
#endif
    .setattr    = ext2_setattr,
    .permission = ext2_permission,
};

struct inode_operations ext2_dir_inode_operations = {
    .create     = ext2_create,
    .lookup     = ext2_lookup,
    .link       = ext2_link,
    .unlink     = ext2_unlink,
    .symlink    = ext2_symlink,
    .mkdir      = ext2_mkdir,
    .rmdir      = ext2_rmdir,
    .mknod      = ext2_mknod,
    .rename     = ext2_rename,
#ifdef CONFIG_EXT2_FS_XATTR
    .setxattr   = generic_setxattr,
    .getxattr   = generic_getxattr,
    .listxattr  = ext2_listxattr,
    .removexattr    = generic_removexattr,
#endif
    .setattr    = ext2_setattr,
    .permission = ext2_permission,
};

シンボリックには高速シンボリックリンク(iノード内にパスの文字列を保持している)と通常のシンボリックリンクの2種類があり以下のように定義されている。

// fs/ext2/symlink.c
struct inode_operations ext2_symlink_inode_operations = {
    .readlink   = generic_readlink,
    .follow_link    = page_follow_link_light,
    .put_link   = page_put_link,
#ifdef CONFIG_EXT2_FS_XATTR
    .setxattr   = generic_setxattr,
    .getxattr   = generic_getxattr,
    .listxattr  = ext2_listxattr,
    .removexattr    = generic_removexattr,
#endif
};
 
struct inode_operations ext2_fast_symlink_inode_operations = {
    .readlink   = generic_readlink,
    .follow_link    = ext2_follow_link,
#ifdef CONFIG_EXT2_FS_XATTR
    .setxattr   = generic_setxattr,
    .getxattr   = generic_getxattr,
    .listxattr  = ext2_listxattr,
    .removexattr    = generic_removexattr,
#endif
};

iノードがキャラクタデバイス、ブロックデバイス、パイプなどを参照している場合にはiノード操作はファイルシステムに依存せず上記のような別の関数テーブルを参照する。

ファイル操作

// fs/ext2/file.c
struct file_operations ext2_file_operations = {
    .llseek     = generic_file_llseek,
    .read       = generic_file_read,
    .write      = generic_file_write,
    .aio_read   = generic_file_aio_read,
    .aio_write  = generic_file_aio_write,
    .ioctl      = ext2_ioctl,
    .mmap       = generic_file_mmap,
    .open       = generic_file_open,
    .release    = ext2_release_file,
    .fsync      = ext2_sync_file,
    .readv      = generic_file_readv,
    .writev     = generic_file_writev,
    .sendfile   = generic_file_sendfile,
};

上記のうちgeneric_で始まるのは汎用関数でファイルシステム共通の処理となる。

データブロックのアドレッシング

空でない通常ファイルは一般的に複数のデータブロックから構成されており、以下の手順でファイルのデータが存在するブロックを求める。

  • ファイルのオフセットからファイル内でのブロック番号を求める
  • ファイル内でのブロック番号をパーティション内でのブロック番号に変換する。

データブロック番号はext2ではext2_inode構造体のi_blockメンバに格納されており上記でも見た通り以下のように定義されている。

#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)

__le32  i_block[EXT2_N_BLOCKS];

デフォルトではi_blockは15となっており、以下のようなかたちで使用される。

  • 先頭12個(0~11)はブロック番号が格納されており、ファイルのデータブロックの先頭12個分に対応する。
  • 13個目(インデックス:12)は関節ブロックのブロック番号が格納されており、関節ブロック番号に対応するデータブロックにはファイルデータのブロック番号が4バイト毎に保存されている。よって2段階で参照するかたち(間接ブロック番号→データブロック番号)となるため、12 ~ n(ブロック長) / 4 + 11までのブロック番号(計1024個)を保持する。
  • 14個目(インデックス:13)は3段階の間接ブロックとなっており、間接ブロック番号→間接ブロック番号→データブロック番号となる(計10242個)。
  • 15個目(インデックス:14)は4段階の間接ブロックとなっており、間接ブロック番号→間接ブロック番号→間接ブロック番号→データブロック番号となる(計10243個)。

詳解Linuxカーネル