panda's tech note

Advent Calendar 2019: advfs

Day 15: 重複排除機能(4)

今日も引き続き重複排除の実装です。これまで inode やブロックなどについてメモリを直接読み書きしていましたが,これを行ってしまうと複数の inode から参照されたブロックを書き換えてしまい,両方のブロックを同時に更新してしまうことになります。メモリプロテクトをかけて Copy-on-Write で対応するという手もありますが,今回はデバイスがブロックデバイスになった場合も想定して,諸々の関数をブロック単位での読み書きに変更していきます。

ブロック番号の解決

ある inode から参照されるブロックの読み書きを行うには,inode ごとに管理される論理ブロック番号からブロックデバイス上でのブロック番号(物理ブロック番号)を解決する必要があります。そこでそのブロック番号を解決するための関数を以下のように定義します。第3引数の pos は論理ブロック番号を表し,返り値は物理ブロック番号へのポインタを表します。このポインタは更新を高速に行うため直接 ramfs のメモリを指し示していおり,ブロック毎の操作ではない,この関数は外部からは呼び出さないように注意します。

static uint64_t *
_resolve_block(advfs_t *advfs, advfs_inode_t *inode, uint64_t pos)
{
    uint64_t *b;
    uint64_t *block;

    if ( pos < ADVFS_INODE_BLOCKPTR - 1 ) {
        /* The block number is included in the inode structure */
        b = &inode->blocks[pos];
    } else {
        /* Resolve from the chain */
        b = &inode->blocks[ADVFS_INODE_BLOCKPTR - 1];
        block = _get_block(advfs, *b);
        pos -= ADVFS_INODE_BLOCKPTR - 1;
        while ( pos >= (ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1) ) {
            /* Get the next chain */
            b = &block[ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1];
            block = _get_block(advfs, *b);
            pos -= ADVFS_BLOCK_SIZE / sizeof(uint64_t) - 1;
        }
        b = &block[pos];
    }

    return b;
}

ブロックの読み込み

ブロックの読み込みは上記で定義した _resolve_block() 関数で物理ブロックの場所を特定し,そのブロックを読み込むだけです。ただし,ブロックが空であることを示す 0 の場合は,ブロックの内容を 0 で埋めて返します。

int
advfs_read_block(advfs_t *advfs, advfs_inode_t *inode, void *buf, uint64_t pos)
{
    uint64_t *b;
    uint64_t *block;

    b = _resolve_block(advfs, inode, pos);
    if ( 0 == *b ) {
        memset(buf, 0, ADVFS_BLOCK_SIZE);
    } else {
        block = _get_block(advfs, *b);
        memcpy(buf, block, ADVFS_BLOCK_SIZE);
    }

    return 0;
}

ブロックの書き込み

ブロックの書き込みも上記で定義した _resolve_block() 関数で物理ブロックの場所を特定し書き込みます。ただし,書き込みは重複排除機能を実装するため,書き込むブロックのハッシュ値を取り,重複するブロックの存在を確認します。もし重複するブロックが見つかった場合は,物理ブロック番号を論理ブロックから参照し,参照カウンタを増やします。また,書き換え対象のブロックが空でなかった場合は,こちらの参照カウンタを減らします。なお,参照が 0 になったブロックは解放すべきですが,今回は簡単のため省略しています。

重複ブロックが見つからなかった場合は,論理ブロックから参照されるブロックが空であれば新しくブロックを作成して,この論理ブロックから参照します。空でない場合は,元のブロックの参照カウンタが 1,つまり自分のブロックからしか参照されていない場合はこのブロックを書き換えます。参照カウンタが 1 よりも大きい場合は,Copy-on-Write 機構のようにこのブロックの参照を減らし,新しいブロックを作成します。

int
advfs_write_block(advfs_t *advfs, advfs_inode_t *inode, void *buf,
                  uint64_t pos)
{
    uint64_t b;
    uint64_t *cur;
    uint64_t *block;
    unsigned char hash[SHA384_DIGEST_LENGTH];
    advfs_block_mgt_t *mgt;

    /* Find the corresponding block */
    cur = _resolve_block(advfs, inode, pos);

    /* Calculate the hash value */
    SHA384(buf, ADVFS_BLOCK_SIZE, hash);

    /* Check the duplication */
    b = _block_search(advfs, hash);
    if ( 0 != b ) {
        /* Found */
        if ( *cur != b ) {
            /* Contents changed */
            if ( 0 != *cur ) {
                /* Release the old block */
                mgt = _get_block_mgt(advfs, *cur);
                mgt->ref--;
            }
            /* Update the new reference */
            mgt = _get_block_mgt(advfs, b);
            mgt->ref++;
        }
        *cur = b;

        return 0;
    } else {
        /* Not found */
        if ( 0 == *cur ) {
            /* Allocate a new block */
            b = advfs_alloc_block(advfs);
            if ( 0 == b ){
                return -1;
            }
            *cur = b;
            mgt = _get_block_mgt(advfs, *cur);
            mgt->ref = 1;
        } else {
            mgt = _get_block_mgt(advfs, *cur);
            assert(mgt->ref >= 1);
            if ( mgt->ref > 1 ) {
                /* Decrement the reference */
                mgt->ref--;

                /* Copy */
                b = advfs_alloc_block(advfs);
                if ( 0 == b ){
                    return -1;
                }
                *cur = b;
                mgt = _get_block_mgt(advfs, *cur);
                mgt->ref = 1;
            } else {
                /* Update the block of *cur */
            }
        }

        block = _get_block(advfs, *cur);
        memcpy(block, buf, ADVFS_BLOCK_SIZE);

        /* Add the block */
        _block_add(advfs, *cur);

        return 0;
    }
}

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

重複排除を行う上での基本的なブロックの読み書きは実装しました。ただし,この状態ではバグがあるようなので,明日以降ちゃんと動くようにしていきます。