ファイルシステム(その5)
前回は、ファイルのデータを読み込む場合を例に、低レイヤファイルシステム(ここでは、ext2)を介して、データがどのように存在し、どのように処理され、どうやって保存されているかの実装を見てきた。
今回は、通常ファイルを例に、ファイルがどのように作成されるのか。ファイルの起源の実装を調べてみる。
ところで、プロセスがファイルを参照する場合、VFS レイヤにおいて、主に 2 つのデータ構造を参照する。一つが file_struct 構造体であり、もう一つが file 構造体。
両者の関係を図示したものが、こちら
http://opentechpress.jp/kernel/internal24/node99.shtml#SECTION03600000000000000000
(お馴染みの高橋浩和さんのサイトである。尚、二番目の図は正確ではない(はず)。カーネル 2.6 において、file_struct 構造体の fd メンバが参照するポインタ配列の各要素が file 構造体へのアドレスを持っている。そして、配列のオフセットがユーザープロセスが参照するファイルディスクリプタとなる)。
file 構造体(以下、ファイルオブジェクト) の f_mapping メンバは、ファイルのアドレス空間オブジェクトを参照している。つまり、ファイルオブジェクトが生成された時点で、inode が取得されている事を意味する。
これからの話の見通しを良くする為、open() システムコールのサービスルーチン(sys_open() 関数) 以下、VFSにおける処理ルーチンを簡単に書くと以下のようになる。尚、'>' の次に書かれた関数名は、'>'の個数が一個少ない行に対応する直上に書かれた関数から呼ばれる事を意味する(説明するまでもないか)。
sys_open()
> do_sys_open()
>> do_filp_open()
>>> open_namei()
>>>> path_lookup_create()
>>>>> __path_lookup_intent_open()
>>>> lookup_hash()
>>>> open_namei_create()
>>>>> vfs_create()
後に話をする、inode 操作関数の create メソッドに対応する低レイヤファイルシステムのルーチン以下の処理ルーチンを、以下同様に簡単に書く。
ext2_create
> ext2_new_inode
>> new_inode
>>> alloc_inode
>>>> ext2_alloc_inode
>> find_group_other
>>> ext2_group_desc
>> read_inode_bitmap
>> ext2_find_next_zero
又、以下の話の中では、opne() システムコールは、O_CREAT フラグを付けて実行されたものとする。ファイルオープン時に、O_CREAT フラグを立てて呼び出すと、ファイルが存在しなかった場合に、ファイルが作成される。
O_CREAT フラグを立てて、sys_open() 関数を呼び出した場合、open_namei() 関数において、path_lookup_create() 関数が実行される。path_lookup_create() は、LOOKUP_PARENT | LOOKUP_CREATE フラグを立てて、__path_lookup_intent_open() 関数を呼び出し、パス名検索を行う。LOOKUP_PARENT フラグは、検索対象ファイルが置かれているディレクトリ(ファイル)を検索対象とする時に使われる。LOOKUP_CREATE フラグは、何の為に存在するのかよくわからない。バイブルには、"ファイルを生成する目的で検索する(ファイルが存在しない場合)"とあるが、ソースではこのフラグ判定を用いておらず、パス名検索中に、ファイル作成を行っていない。
__path_lookup_intent_open() 関数ではまず、ファイルオブジェクトを取得する為に、get_empty_filp() 関数を呼び出す。ここでは、未使用のファイルオブジェクトの取得、初期化を行い、そのアドレスを返す。ファイルオブジェクトの数には制限があり、割当て可能なファイルオブジェクトの数(システム内で同時にアクセス可能な最大のファイル数)は、files_stat_struct 型のグローバル変数 files_stat オブジェクトの max_files メンバで設定されている値になる(/pro/sys/fs/file-max の値を操作する事で、この値を変更できるらしい。デフォルト値は、主記憶のおよそ 1/10)。現在の値は、fs/file_tables.c で宣言されている、percpu_counter型 のグローバル変数 nr_files の count メンバに保存されている。
これに成功すると、filp_cachep キャッシュからファイルオブジェクトのメモリ領域を取得し、これを 0 で初期化した後、ファイルオブジェクトの uid と gid を設定する(かつて、ファイルオープン時における、この処理を意識しなかった事で、恥をかいた)。その後、生成したファイルオブジェクトのアドレスを返している。
次に、do_path_lookup() 関数によるパス名検索処理についてだが、これまでにも何度か登場している dentry オブジェクトについて、ここでも説明(の更新)を行う。
カーネルは、プロセスが参照するパス名の要素に対して、d エントリオブジェクトを作成する。つまり、ルートファイルシステムから参照できるファイルに対してそれぞれ、d エントリオブジェクトを作成している。これは、ファイルの実態である inode と、inode が持つアドレス空間オブジェクトの存在を抽象化したデータ構造である(と解釈している)。
__path_lookup_intent_open() 関数では、linux のパス名検索 (do_path_lookup() 関数) によって、目的としているファイルの dentry を取得し、ファイルオープン時における一時データ構造の nameidata オブジェクトの dentry メンバにアドレスが格納される。
そして、do_path_lookup() 関数では、検索開始ディレクトリを指定し、link_path_walk() 関数を実行する。
検索開始ディレクトリは、指定されたパス名の先頭が'/'であるかどうかで判断する。'/' である場合には、ルートディレクトリから、そうでない場合には、カレントディレクトリの dentry のアドレスを nemeidata 型のオブジェクト nr->dentry に格納し、nr を引数に、link_path_walk() 関数を呼び出す。
ここまでで、対象ファイルが存在する dentry オブジェクトを取得できた。次に、lookup_hash() 関数を呼び出し、対象ファイルの dentry オブジェクトを取得する為、cached_lookup() を呼び出す。ここではまず、d エントリキャッシュを探すため、 __d_lookup() 関数を呼び出し、失敗した場合、d_lookup() 関数を呼び出し、ディレクトリの d エントリオブジェクトから階層構造をたどって、対象ファイルの d エントリを取得する。
これに失敗した場合(ファイルが存在しない場合)、d_alloc() 関数を呼び出し、対象ファイル用の d エントリオブジェクトを dentry_cache キャッシュから生成し、ここで初期化を行い、作成した d エントリオブジェクトを返す。尚、初期化処理において、d_inode メンバは NULL に設定される。
open_namei_create() 関数に戻り、lookup_hash() 関数によって得た d エントリオブジェクトが参照する inode が存在しない場合(対象ファイルが無い場合)、open_namei_create() 関数を呼び出す。ここでは、vfs_create() 関数を呼び出し、ディレクトリの inode オブジェクトの inode_operations型 inode 操作メソッドの create メソッドを呼び出し、ファイル用の inode を作成している。ファイルシステムが ext2 の場合、ext2_dir_inode_operations で定義されたext2_create() 関数が実行される。
ext2_new_inode() 関数ではまず、new_inode() 関数を呼び出し、inode の作成を行っている。ここでは、alloc_inode() を呼び出し、inode を作成し、inode_in_use リストに作成した inode を追加する。又、alloc_inod() では、super_block 型オブジェクトのメソッドalloc_inode() を呼び出し、低レイヤファイルシステムにおける inode を作成し、VFS inode を取得する。
ext2 の場合では、スーパーブロックオブジェクトのメソッドは、fs/ext2/super.c の ext2_sops で宣言されている。alloc_inode() で実行された alloc_inode メソッドは、ext2_alloc_inode() 関数を呼び出す。
ext2_alloc_inode() 関数は、ext2_inode_cachep キャッシュからメモリ領域を取得し、ext2_inode_info 型オブジェクトを作成し、vfs_inode メンバのアドレスを返す。ここでは、ext2_inode_info 型オブジェクト の生成だけ行い、初期化処理は、ext2_new_inode() 関数内で行う。又、VFS inode の初期化処理、及びアドレス空間オブジェクトの初期化処理は、alloc_inode() 内で行われ、ext2_new_inode() に処理が帰る。
ここまでで、inode は作成されたが、これがディスク上のどこに置かれるかは、まだ決定されていない。ここではあくまで、ディスクに置く inode をメモリ上で作成しただけである。
次に、IS_DIR() マクロで sys_open() によって渡された引数、mode をチェックし、作成されるファイルがディレクトリか、通常ファイルかをチェックする。通常ファイルの場合、find_group_other() 関数を呼び出し、ファイル用 inode を置くブロックグループ番号を選択する。原則、ディレクトリが属するブロックグループに inode を置く(ヘッドの移動時間短縮の為)。同一ブロックグループに空き inode スペースが無い場合、適当な計算((ディレクトリのブロックグループ + ディレクトリの inode 番号) % ファイルシステムに存在するブロックグループの数)によってブロックグループを検索。これによっても、inode の空きスペースを見付けられない時、線形的にブロックグループを探索する。
取得したブロックグループ番号を引数に、read_inode_bitmap() 関数を呼び出し、引数で渡したブロックグループ番号の inode ビットマップのブロックバッファヘッドを取得し、find_next_zero_bit() 関数を呼び出し、取得したブロックバッファのデータにおいて、最初に 0 が出現する位置を返す。つまり、ブロックグループ内における、最初の空き inode オブジェクトの inode 番号。ここで、取得した数値が、スーパーブロックの s_inode_per_group (ブロックグループに存在する inode 数)を越えていた場合(恐らく、SMPシステムにおける配慮)、隣のブロックグループにおいてブロックグループの数だけ同一の処理を行う。
これにより、O_CREAT フラグ付でオープンされたファイルが、作成される処理が理解できた。
- Category(s)
- 学習経過
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/ohyama/30d530a130a430eb30b730b930c630e0-305d306e5/tbping