panda's tech note

Advent Calendar 2019: advfs

Day 17: 重複排除機能(6)

ブロックデバイスを模したRAMを直接触る破壊的な部分とコピーして触る部分が混ざっていることで,inode のメタ情報(ファイルエントリ数など)は更新されるのに中身が更新されていないということが発生していそうなので,大がかりな手術をします。

設計

ちゃんと設計しましょう。ブロックサイズ = 4096バイトとします。

  • スーパーブロック:4096バイト
    • inode 領域へのポインタ(物理ブロック番号)
    • ブロック管理領域へのポインタ(物理ブロック番号)
    • ブロック領域へのポインタ(物理ブロック番号)
    • 総 inode 数
    • 利用済み inode 数
    • ブロック管理領域のルート(重複排除の木構造で使用)
    • 総ブロック数(スーパーブロック,inode 領域,ブロック管理領域を除く)
    • 利用済みブロック数
    • 空きブロック管理用リストの先頭ブロック番号
  • inode 領域 (512バイト * inode 数)
  • ブロック管理領域 (128バイト * ブロック数)
  • ブロック領域

このうち,スーパーブロック,inode 領域,ブロック管理領域は生のブロックで扱います。すなわち重複排除をしません(重複排除の管理構造体が含まれるのでできません)。ブロック領域のみを重複排除の対象とします。

これを踏まえて,まずスーパーブロック,inode 領域,ブロック管理領域をブロック単位で扱う関数を定義します。

スーパーブロック

スーパーブロックの読み書きは昨日定義したとおりです。

inode 領域

これまでは inode を直接扱ってきましたが,ここでバグを生んでいると思っているので,inode 領域を破壊的に触らないように,inode の読み書き関数 advfs_read_inode() および advfs_write_inode() を定義します。

第1引数にこのファイルシステムを管理する advfs_t 構造体,第2引数に読み書き対象の advfs_inode_t 構造体のバッファ(読み込みであればここに書き込まれ,書き込みであればここのデータを書き込みます),第3引数に inode 番号を指定します。

これらの関数の中身は単純で,inode 番号に対応する advfs_inode_t 構造体の保存されているブロックとそのブロックのオフセットを計算して,その領域に対して読み書きします。実装は以下の通りです。

int
advfs_read_inode(advfs_t *advfs, advfs_inode_t *inode, uint64_t nr)
{
    void *ptr;
    uint64_t b;
    uint64_t off;
    advfs_superblock_t sb;
    uint8_t buf[ADVFS_BLOCK_SIZE];

    /* Read the superblock */
    advfs_read_superblock(advfs, &sb);

    /* Resolve the position */
    b = sb.ptr_inode + (sizeof(advfs_inode_t) * nr) / ADVFS_BLOCK_SIZE;
    off = (sizeof(advfs_inode_t) * nr) % ADVFS_BLOCK_SIZE;

    /* Assert the size to prevent buffer overflow */
    assert( off + sizeof(advfs_inode_t) <= ADVFS_BLOCK_SIZE );

    /* Read the block */
    advfs_read_raw_block(advfs, buf, b);
    ptr = buf + off;
    memcpy(inode, ptr, sizeof(advfs_inode_t));

    return 0;
}

書き込みについては,以下のように読み込んだブロックの該当部分を書き換え,最後に書き戻しを行っています。

int
advfs_write_inode(advfs_t *advfs, const advfs_inode_t *inode, uint64_t nr)
{
    void *ptr;
    uint64_t b;
    uint64_t off;
    advfs_superblock_t sb;
    uint8_t buf[ADVFS_BLOCK_SIZE];

    /* Read the superblock */
    advfs_read_superblock(advfs, &sb);

    /* Resolve the position */
    b = sb.ptr_inode + (sizeof(advfs_inode_t) * nr) / ADVFS_BLOCK_SIZE;
    off = (sizeof(advfs_inode_t) * nr) % ADVFS_BLOCK_SIZE;

    /* Assert the size to prevent buffer overflow */
    assert( off + sizeof(advfs_inode_t) <= ADVFS_BLOCK_SIZE );

    /* Read the block */
    advfs_read_raw_block(advfs, buf, b);
    ptr = buf + off;
    memcpy(ptr, inode, sizeof(advfs_inode_t));

    /* Write back */
    advfs_write_raw_block(advfs, buf, b);

    return 0;
}

ブロック管理領域

ブロック管理領域は頻繁にアクセスがあるので,性能を求めるなら直接メモリを叩いた方が良いのですが,今回は inode 同様にブロック単位でアクセスします。性能を求めるのであれば,キャッシュを持つ実装になると思いますが今回はそこまでやりません。

実装はほぼ inode の読み書きと同じです。コードを読んだ方がわかりやすいと思うので,以下に掲載します。

int
advfs_read_block_mgt(advfs_t *advfs, advfs_block_mgt_t *mgt, uint64_t nr)
{
    void *ptr;
    uint64_t b;
    uint64_t off;
    advfs_superblock_t sb;
    uint8_t buf[ADVFS_BLOCK_SIZE];

    /* Read the superblock */
    advfs_read_superblock(advfs, &sb);

    /* Resolve the position */
    b = sb.ptr_block_mgt + (sizeof(advfs_block_mgt_t) * nr) / ADVFS_BLOCK_SIZE;
    off = (sizeof(advfs_block_mgt_t) * nr) % ADVFS_BLOCK_SIZE;

    /* Assert the size to prevent buffer overflow */
    assert( off + sizeof(advfs_block_mgt_t) <= ADVFS_BLOCK_SIZE );

    /* Read the block */
    advfs_read_raw_block(advfs, buf, b);
    ptr = buf + off;
    memcpy(mgt, ptr, sizeof(advfs_block_mgt_t));

    return 0;
}
int
advfs_write_block_mgt(advfs_t *advfs, const advfs_block_mgt_t *mgt, uint64_t nr)
{
    void *ptr;
    uint64_t b;
    uint64_t off;
    advfs_superblock_t sb;
    uint8_t buf[ADVFS_BLOCK_SIZE];

    /* Read the superblock */
    advfs_read_superblock(advfs, &sb);

    /* Resolve the position */
    b = sb.ptr_block_mgt + (sizeof(advfs_block_mgt_t) * nr) / ADVFS_BLOCK_SIZE;
    off = (sizeof(advfs_block_mgt_t) * nr) % ADVFS_BLOCK_SIZE;

    /* Assert the size to prevent buffer overflow */
    assert( off + sizeof(advfs_block_mgt_t) <= ADVFS_BLOCK_SIZE );

    /* Read the block */
    advfs_read_raw_block(advfs, buf, b);
    ptr = buf + off;
    memcpy(ptr, mgt, sizeof(advfs_block_mgt_t));

    /* Write back */
    advfs_write_raw_block(advfs, buf, b);

    return 0;
}

今日のまとめと明日の予定

これでブロックの読み書き以外をブロックとして扱えるようになりました。15日目で実装したブロックの読み書きは _resolve_block() 関数を見ても分かるとおり,inode 構造体を直接変更するような実装でした。明日はこの重複排除機能付きのブロックの読み書きを inode 構造体を直接変更しない形で実装し直します。