0から作るソフトウェア開発

日々勉強中。。。

Tips
リンク

ファイルシステム >

0から作るLinuxプログラム Ext2ファイルシステム ステップ3 スーパーブロックの管理情報

Ext2ファイルシステム

Linuxのファイルシステムのうち基本的なファイルシステムであるExt2について
みていきます。

それでは、順番に見ていきます。

目次 Ext2ファイルシステム

  1. ファイルシステムの概要

  2. ファイルシステムへのアクセス

  3. カーネルサイドから見たシステムコール

  4. ファイルシステム関連のシステムコール

  5. 仮想ファイルシステム(VFS)

  6. ステップ0 Ext2ファイルシステムと簡単なモジュールの実装

  7. ステップ1 Ext2ファイルシステムタイプとマウントメソッド

  8. ステップ2 Ext2スーパーブロックの読み込み

  9. (現在のページ) ステップ3 スーパーブロック管理情報

  10. ステップ4 マウントとスーパーブロックオブジェクト

  11. ステップ5 スーパーブロックオブジェクトと管理情報のセットアップ

  12. ステップ6 ブロックグループディスクリプターの読み込み

  13. ステップ7 ルートディレクトリのinode読み込み

  14. ステップ8 簡単なディレクトリの読み込み

  15. ステップ9 ディレクトリ読み込みとページキャッシュとaddress_space

  16. ステップ10 inodeオブジェクトのlookupメソッド

  17. ステップ11 inodeオブジェクトのmkdirメソッドとper-cpuカウンター

  18. ステップ12 簡単なブロックの新規割り当て処理

  19. ステップ13 スーパーブロックオブジェクトのwrite_inodeメソッド

  20. ステップ14 Orlov方式による新規ディレクトリのinodeを割り当てるブロックグループ選択

  21. ステップ15 inodeオブジェクトのrmdirメソッドとunlinkメソッド

  22. ステップ16 inodeオブジェクトのrenameメソッド

  23. ステップ17 inodeオブジェクトのcreateメソッドとファイルオブジェクトの汎用メソッド

  24. ステップ18 inodeオブジェクトのlinkメソッド

  25. ステップ19 シンボリックとinodeオブジェクトのsymlinkメソッド

  26. ステップ20 inodeオブジェクトのmknodメソッドとtmpfileメソッド

  27. ステップ21 スーパーブロックオブジェクトのevict_inodeメソッド

  28. ステップ22 スーパーブロックオブジェクトのsync_fsメソッド

  29. ステップ23 スーパーブロックオブジェクトのstatfsメソッドとメモリーバリアー/フェンス

  30. ステップ24 スーパーブロックオブジェクトのremount_fsメソッドとマウントオプション解析

  31. ステップ25 スーパーブロックオブジェクトのshow_optionsメソッドとprocfs

  32. ステップ26 カーネルオブジェクトとsysfs

  33. ステップ27 スーパーブロックオブジェクトのfreeze_fs/unfreeze_fsメソッド

  34. ステップ28 ファイルオブジェクトのioctlメソッド

  35. ステップ29 ファイルオブジェクトのsetattrメソッド

  36. ステップ30 address_spaceのdirect_IOメソッド

  37. ステップ31 リザベーションウィンドウ

  38. ステップ32 拡張アトリビュートその1 ハンドラーの呼び出し

  39. ステップ33 拡張アトリビュートその2 コア処理

  40. ステップ34 POSIX ACLその1 ハンドラーの呼び出し

  41. ステップ35 POSIX ACLその2 コア処理

  42. ステップ36 ディスククォータ

スーパーブロック管理情報

ここまでで、スーパーブロックの読み込みができるようになりました。今回は読み込んだ

スーパーブロックの管理情報の準備をしていきたいと思います。なぜ、管理情報が必要

となってくるのでしょうか。ちょっとだけ見ていきたいと思います。

まず、仮想ファイルシステムから呼び出されるファイルシステムのメソッドには、仮想ファイル

システムのオブジェクトが引数で渡されます。ファイルシステムでは仮想ファイルシステムの

オブジェクトからどのディスクのスーパーブロックなのかを判断する必要があります。例えば、

/dev/sda1と/dev/sda2と/dev/sda3の3つのパーティションに区切っていて、それ

ぞれExt2ファイルシステムとしてマウントしている場合、3つのスーパーブロックオブジェクトが

できます。このため、それぞれのどのスーパーブロックかを判断する場面があります。例えば、

スーパーブロックを更新するとき、対応するバッファーキャッシュのデータを更新しなければ

いけません。


この判断にはいろいろあるかと思いますが、ここまで見てきましたようにファイルシステムの

メソッドはシステムコールをトリガーに頻繁に呼び出されることになりますので、なるべ

く簡単な処理でどのディスクのスーパーブロックか判断したいです。仮想ファイルシステムの

スーパーブロックオブジェクトにはファイルシステムのスーパーブロック情報と関連付けるための

変数が用意されています。


まず、仮想ファイルシステムのスーパーブロックオブジェクトについて簡単にその構造を見て

いきましょう。

仮想ファイルシステムのスーパーブロックオブジェクトとの関連付け

仮想ファイルシステムのスーパーブロックオブジェクトはsuper_block構造体となります。

mountメソッドの引数でも出てきましたが、いままでポインターでしか出てきていませんでた。

ここでは、super_block構造体についてもう少しだけ詳しく見ていきます。


super_block構造体からステップ3.で使用するメンバーを少し抜粋しました。次の

メンバーを使用します。(super_block構造体はステップ4.で詳しく見ていきたいと

思います)。

				
struct super_block
{
    ...
    unsigned long           s_magic;
    void                    *s_fs_info;     /* Filesystem private info */
    ...
};



ステップ3.で使用するsuper_block構造体のメンバー
名前 説明
unsigned longs_magic ファイルシステムのマジックナンバーを格納します。
void *s_fs_info ファイルシステムの独自管理情報のアドレスを格納します。


super_block構造体のs_fs_infoはファイルシステムで自由に使って

よいメンバーとなります。通常s_fs_infoに仮想ファイルシステムのスーパー

ブロックオブジェクトと関連付けたファイルシステム独自のスーパーブロック管理

情報のアドレスを記録しておきます。このようにしておけば、仮想ファイルシステム

からスーパーブロックオブジェクトを渡されたときに、s_fs_infoのアドレスを

参照することで対応するディスクのスーパーブロックをすぐに参照することが

できます。


そして、s_fs_infoに記録しておいた、ファイルシステム独自のスーパー

ブロック管理情報は、ディスクから読み込んだデータが格納されているバッファー

キャッシュのアドレスを保持しておきます。


これにより、ディスクのスーパーブロックを更新する必要があるときなどに即座にその

バッファーキャッシュにアクセスすることができます。

スーパーブロック管理情報

それでは、実装していきます。

スーパーブロック管理情報の実装

スーパーブロック管理情報の定義を行っていきます。バッファーキャッシュを格納

できるようにします。ここではme2fs_sb_info構造体をme2fs.hファイルに

定義していきます。

[me2fs.h]

/*
---------------------------------------------------------------------------------
    Me2fs(Ext2) Super Block
---------------------------------------------------------------------------------
*/
struct me2fs_sb_info
{
    struct ext2_super_block     *s_esb;
    struct buffer_head          *s_sbh;
    
};



ここで、ext2_super_block型のポインターs_esbを定義しています。

これはバッファーキャッシュのデータそのもののアドレスを格納する用となります。

汎用オブジェクトの動的メモリー割り当て関数

定義はできたのですが、変数を確保して実際にメモリー上に配置しないと

使用することができません。スーパーブロックの管理情報は新しくマウントされる

たびに作成されることになります。このため、静的にメモリーを確保していては、

マウントに制限が出てきます。そこで、動的にメモリーを確保するようにします。

カーネルでは動的にメモリーを確保するときの処理が実装されています。この

処理ではアプリケーション空間で使用するmalloc関数と同じように、汎用

オブジェクトの割り当て、つまりいろいろなサイズのオブジェクトの動的割り当てが

可能となっています。汎用オブジェクトの動的メモリー割り当てには次の関数が

使用できます。


void *kmalloc( size_t size, gfp_t flags );
void *kzalloc( size_t size, gfp_t flags );

void *kmalloc_array( size_t n, size_t size, gfp_t flags );
void *kcalloc( size_t n, size_t size, gfp_t flags );

void *kmalloc_node( size_t size, gfp_t flags, int node );
void *kzalloc_node( size_t size, gfp_t flags, int node );

void kfree( const void *block );
void kzfree( const void *p );



汎用オブジェクトの動的メモリー割り当て関数
関数 説明
kmalloc カーネル空間のメモリーを動的に割り当てます。アプリケーション空間のmalloc関数と同じように使用できます。(フラグを指定する必要はあります)。
kzalloc kmallocと同じですが、割り当てるメモリーを0に初期化します。(kmallocのflagsに__GFP_ZEROを指定したときと同じ動作となります)。
kmalloc_array カーネル空間のメモリーを配列として動的に割り当てます。(確保したい配列のサイズをkmallocするのと同じです)。
kcalloc kmalloc_arrayと同じですが、割り当てるメモリーを0に初期化します。
kmalloc_node kmallocと同じですが、指定したメモリーノードから割り当てを行います。
kzalloc_node kmalloc_nodeと同じですが、割り当てられるメモリーを0に初期化します。
kfree kmallocおよび上記関数で確保したメモリーを解放します。
kzfree kfreeと同じですが、kfreeする前にメモリーを0で初期化します。


kmallocとkzallocの引数と戻り値

kmallocとkzallocの引数は次のようになります。

kmalloc、kzallocの引数
名前 説明
size_tsize 確保したいメモリーのサイズを指定します。
gfp_tflags メモリーを割り当てるときの動作を指定します。次のようなフラグがあります。kmallocなどの汎用オブジェクトの動的メモリー割り当て関数は、スラブアロケーター(後で説明を記載します)を利用しています。スラブアロケーターでは、空いているスラブが無い場合には、新規にスラブを生成するために新たにページ割り当てを行う必要があり、フラグには原則的にページ割り当て時に使用するgfp_maskフラグを指定する必要があります。
flags引数
メモリーゾーンの動作を変更するフラグ
フラグ 説明
__GFP_DMA DMAゾーンからページ割り当てを行います。
__GFP_HIGHMEM HIGHMEMゾーンからページ割り当てを行います。
__GFP_DMA32 DMA32ゾーンからページ割り当てを行います。
__GFP_MOVABLE 断片化を解消するためにページの移動やページの再利用が可能なページです。
ページ割り当て動作を変更するフラグ
フラグ 説明
__GFP_WAIT ページ割り当て時にカレントプロセスを待たせて(再スケジュールを行って)もよいことを示すフラグです。
__GFP_NOWAIT ページ割り当て時にカレントプロセスを待たせてないようにするフラグです。
__GFP_HIGH ページが不足しているときに緊急用のページを割り当ててもよいことを示すフラグです。
__GFP_IO ページ割り当て時に不要なページを解放するために、I/O(スワップ処理)を行ってもよいことを示すフラグです。
__GFP_FS ページ割り当て時にファイルシステム関連の処理を行ってよいことを示すフラグです。
__GFP_COLD 要求したページがキャッシュコールドページであるとこを示すフラグです。
__GFP_NOWARN ページ割り当てが失敗したときに警告メッセージを抑制します。
__GFP_REPEAT ページが割り当てられるまでリトライを行います。(できない場合、最終的にページ割り当ては失敗することがあります。)
__GFP_NOFAIL ページが割り当てられるまでリトライし続けます。(リトライは無限に行います。)
__GFP_NORETRY ページ割り当てに失敗してもリトライを行いません。
__GFP_MEMALLOC 緊急用に予約されているメモリーにアクセスすることを許可します。
__GFP_COMP 大きなサイズのページ領域にあるページを取得します。
__GFP_ZERO ページ割り当て成功時にページを0で初期化します。
__GFP_NOMEMALLOC 緊急用に予約されているメモリーの割り当ては行いません。
__GFP_HARDWALL ハードウォールCPUセットのメモリー割り当て動作を行います。(複数のユーザーで共有使用するページ割り当てを行いません)。
__GFP_THISNODE コンテキスト中のメモリーノードからページ割り当てを行います。代替として他のノードからページ割り当てを行わないようにします。
__GFP_RECLAIMABLE 再利用可能なページを要求します。再利用可能なページは、他のページ割り当て要求が発生したときに、そのページはスワップアウトされて再利用されます。
__GFP_NOTRACK kmemcheckによる追跡を行いません。
__GFP_NO_KSWAPD ページ割り当て時にkswapdを起動させません。
__GFP_OTHER_NODE ページ割り当て時に他のノードからページ割り当てを行います。
__GFP_WRITE ダーティページの割り当てを行ってもよいことを示すフラグです。


通常は上記フラグを組み合わせた次のフラグを使用します。特にGFP_KERNELをよく使います。
通常使用するflags引数
フラグ 定義
GFP_ATOMIC スリープしないでメモリーを割り当てます。緊急用にプールしておいたページを使用することがあります。割り込みハンドラーなど緊急を要する処理で使用します。次の定義と同じです。
(__GFP_HIGH)
GFP_NOIO メモリーを割り当てるときにI/Oを行わないようにします。次の定義と同じです。
(__GFP_WAIT)
GFP_NOFS メモリーを割り当てるときにファイルシステム関連の処理呼び出しを行わないようにします。次の定義と同じです。
(__GFP_WAIT | __GFP_IO)
GFP_KERNEL カーネル用のメモリーを割り当てます。次の定義と同じです。プロセスはスリープする可能性があります。
(__GFP_WAIT | __GFP_IO | __GFP_FS)
GFP_TEMPORARY (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE)
GFP_USER ユーザー用のメモリーを割り当てます。次の定義と同じです。プロセスはスリープする可能性があります。次の定義と同じです。
(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
GFP_HIGHUSER ハイメモリーからメモリーを割り当てます。次の定義と同じです。
(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM)
GFP_HIGHUSER_MOVABLE (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE)
GFP_IOFS (__GFP_IO | __GFP_FS)
GFP_TRANSHUGE (GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | __GFP_NO_KSWAPD)



補足
割り込み処理のなかで通常通りのメモリー割り当てを行ってしまうと、スワップ処理が発生

する可能性がありますので、なるべくページ割り当てを行わないようにするか、GFP_ATOMIC

を使用するようにします。



kmallocとkzallocの戻り値は次のようになります。

kmalloc、kzallocの戻り値
説明
void * 確保したオブジェクト(メモリー)のアドレスを返します。メモリーを確保できなかった場合、NULLを返します。


kmalloc_arrayとkcallocの引数と戻り値

kmalloc_arrayとkcallocの引数は次のようになります。

kmalloc_arrayとkcallocの引数
名前 説明
size_tn 確保したい配列の要素数を指定します。
size_tsize 確保したいメモリーのサイズを指定します。確保される配列のサイズはn×sizeとなります。
gfp_tflags kmallocと同じです。


kmalloc_arrayとkcallocの戻り値はkmallocと同じです。

kmalloc_nodeとkzalloc_nodeの引数と戻り値

kmalloc_nodeとkzalloc_nodeの引数は次のようになります。

kmalloc_nodeとkzalloc_nodeの引数
名前 説明
size_tsize 確保したいメモリーのサイズを指定します。
gfp_tflags kmallocと同じです。
intnode メモリーを割り当てるメモリーノードを指定します。


kmalloc_nodeとkzalloc_nodeの戻り値はkmallocと同じです。

kfreeとkzfreeの引数と戻り値

kfreeとkzfreeの引数は次のようになります。

kfreeとkzfreeの引数
名前 説明
const void *blockまたはp kmallocなどの汎用オブジェクトの動的メモリー割り当て関数で割り当てられたオブジェクトのメモリーアドレスを指定します。


kfreeとkzfreeに戻り値はありません。

補足
汎用オブジェクトの割り当てはスラブアロケーターを利用しています。汎用オブジェクトのスラブ

では様々な固定サイズのオブジェクトが用意されています。例えば、8バイト、16バイト、32バイト

、64バイト、128バイトなどがあります。汎用オブジェクトのサイズは固定サイズとなっているため、

例えば、33バイトのオブジェクトのメモリーをスラブアロケーターから割り当てした場合は、64バイト

のスラブからオブジェクトが割り当てられます。このため、たくさんのオブジェクトを生成する場合には

汎用オブジェクトを割り当てるとメモリーの使用効率が極端に悪くなってしまう場合があります。

頻繁に汎用オブジェクトの割り当てを行う場合には、割り当てたいオブジェクト専用の

スラブを生成するようにしてください。今回のスーパーブロック管理情報のメモリーについては

デバイスをマウントした数だけ生成することになりますので、汎用オブジェクトの割り当てはそれほど

頻繁に行われることはないはずです。ですので、スーパーブロック管理情報のメモリー割り当てに

汎用オブジェクトを割り当ててもシステム上特に問題とはならないと考えられますが、

これから見ていきますinode管理情報についてはメモリーの割り当てが頻繁に行われ

ますので、専用のキャッシュを生成していきます。これについては後ほど見ていきましょう。



使用例

mallocと同じような感覚で使用することができます。次の例のように使用します。

ここでは、me2fs_sb_info構造体用のメモリーを確保してから解放する例となります。


    struct me2fs_sb_info    *msi;

    msi = kzalloc( sizeof( struct me2fs_sb_info ), GFP_KERNEL );

    if( !msi )
    {
        ret = -ENOMEM;
        return( ret );
    }

    /* 処理 */
   
   kfree( msi );



fill_superコールバック関数(me2fsFillSuperBlock関数)

それでは、実装していきます。定義しておきましたスーパーブロック管理情報の

me2fs_sb_infoを汎用オブジェクトの動的メモリー割り当て関数でメモリーを

確保しましょう。そして、読みこんだスーパーブロックのバッファーキャッシュのアドレス

をスーパーブロック管理情報me2fs_sb_infoに格納してから、仮想ファイル

システムのスーパーブロックオブジェクトに管理情報を登録します。登録するには

スーパーブロックオブジェクトのs_fs_infoに管理情報のアドレスを格納します。


今回の例ではついでに、読みこんだスーパーブロックのマジックナンバーを確認する

ようにしています。各ファイルシステムのマジックナンバーはカーネルソースの

uapi/linux/magic.hに定義されていますので、これを利用しています。

独自にファイルシステムを実装する場合には、このヘッダーにマジックナンバーを追加

するか、独自ファイルシステムのヘッダーにマジックナンバーを定義しましょう。

Ext2ファイルシステムのマジックナンバーは0xEF53でEXT2_SUPER_MAGIC

で定義されています。今回はme2fs.hにインクルードしてME2FS用に再定義して

います。次のように追加しました。

[me2fs.h]

/*********************************************************************************
    File            : me2fs.h
    Description     : Defines for my ext2 file system

*********************************************************************************/
#ifndef __ME2FS_H__
#define __ME2FS_H__

#include <uapi/linux/magic.h>

/*
==================================================================================

    Prototype Statement

==================================================================================
*/

/*
==================================================================================

    DEFINES

==================================================================================
*/
#define ME2FS_SUPER_MAGIC       EXT2_SUPER_MAGIC        /* 0xEF53               */



そして、このマジックナンバーの定義と読みこんだスーパーブロックのマジック

ナンバーを比較して、一致しているか確認を行います。マジックナンバーが

違っていた場合、マウント処理を失敗させます。

次のように実装します。スーパーブロックに格納されている値はリトルエンディアン

となりますので、le16_to_cpuで値を取得するようにします。


    sb->s_magic = le16_to_cpu( esb->s_magic );

    if( sb->s_magic != ME2FS_SUPER_MAGIC )
    {
        DBGPRINT( "<ME2FS>error : magic of super block is %lu\n", sb->s_magic );
        goto error_read_sb;
    }




それでは、実装例を見ていきましょう。me2fsFillSuperBlock関数を変更

していきます。

[me2fs_super.c]

/*
==================================================================================
    Function    :me2fsFillSuperBlock
    Input       :struct super_block *sb
                 < vfs super block object >
                 void *data
                 < user data >
                 int silent
                 < silent flag >
    Output      :void
    Return      :void

    Description :fill my ext2 super block object
==================================================================================
*/
static int me2fsFillSuperBlock( struct super_block *sb,
                                void *data,
                                int silent )
{
    struct buffer_head      *bh;
    struct ext2_super_block *esb;
    struct me2fs_sb_info    *msi;
    int                     block_size;
    int                     ret = -EINVAL;

    /* ------------------------------------------------------------------------ */
    /* allocate memory to me2fs_sb_info                                         */
    /* ------------------------------------------------------------------------ */
    msi = kzalloc( sizeof( struct me2fs_sb_info ), GFP_KERNEL );

    if( !msi )
    {
        DBGPRINT( "<ME2FS>error: unable to alloc me2fs_sb_info\n" );
        ret = -ENOMEM;
        return( ret );
    }

    /* ------------------------------------------------------------------------ */
    /* set device's block size and size bits to super block                     */
    /* ------------------------------------------------------------------------ */
    block_size = sb_min_blocksize( sb, BLOCK_SIZE );

    DBGPRINT( "<ME2FS>Fill Super! block_size = %d\n", block_size );
    DBGPRINT( "<ME2FS>default block size is : %d\n", BLOCK_SIZE );

    if( !block_size )
    {
        DBGPRINT( "<ME2FS>error: unable to set blocksize\n" );
        goto error_read_sb;
    }

    if( !( bh = sb_bread( sb, 1 ) ) )
    {
        DBGPRINT( "<ME2FS>failed to bread super block\n" );
        goto error_read_sb;
    }

    esb = ( struct ext2_super_block* )( bh->b_data );

    sb->s_magic = le16_to_cpu( esb->s_magic );

    if( sb->s_magic != ME2FS_SUPER_MAGIC )
    {
        DBGPRINT( "<ME2FS>error : magic of super block is %lu\n", sb->s_magic );
        goto error_read_sb;
    }

    msi->s_esb = esb;
    msi->s_sbh = bh;


    //dbgPrintExt2SB( esb );
    /* ------------------------------------------------------------------------ */
    /* set up vfs super block                                                   */
    /* ------------------------------------------------------------------------ */
    sb->s_fs_info   = ( void* )msi;

    return( 0 );

error_read_sb:
    kfree( msi );

    return( ret );
}



コンパイルして実行してみましょう。カーネルの動作がおかしければ再起動してください。

この時点のプログラムでもマウントは失敗します。


それでは、次のステップでは仮想ファイルシステムのスーパーブロックオブジェクトについて

値を埋めてセットアップしていきましょう。次のステップでとりあえずとなりますが、マウントが

成功するまでの処理となります。

PR 説明

ディベロッパー・エクスペリエンス Linux Ext2ファイルシステム
0から作るLinuxプログラム Linux Ext2ファイルシステムの完全説明版(Kindleのみ)となります。実装するステップ数は36ステップあります。少しずつステップを実装して、実際に動かして学習していきます。ファイルシステムの機能を呼び出すシステムコールの実装から、仮想ファイルシステムが呼び出すExt2ファイルシステムの実装を行っていきます。ファイルシステムの開発体験を通すことで、実際にカーネルが行っているファイルシステムの基礎的な動作を理解することができます。ぜひお試しください!

inserted by FC2 system