ファむルシステムを読むこずを孊ぶ

この蚘事では䜕が起こりたすか



画像



Academic Universityの OSコヌスの資料に基づいお、Linuxカヌネルでファむルシステムを䜜成するこずに関する䞀連の蚘事を続けたす。



前回は、コアに粟通するために必芁な環境を蚭定したした。 次に、ロヌド可胜なカヌネルモゞュヌルを芋お、簡単な「Hello、World」を䜜成したした。 そしお最埌に、シンプルで圹に立たないファむルシステムを䜜成したした。 続行する時間です。



この蚘事の䞻な目的は、ファむルシステムにディスクからの読み取りを教えるこずです。 これたでのずころ、圌女はサヌビス情報スヌパヌブロックおよびむンデックスノヌドのみを読み取るため、それを䜿甚するこずは䟝然ずしお非垞に困難です。



なぜそんなに少ないのですか 実際、この投皿では、ファむルシステムの構造、぀たりディスクに保存する方法を決定する必芁がありたす。 さらに、SLABやRCUなどの興味深い点がいく぀かありたす。 これにはすべお説明が必芁になりたす-たくさんの単語ず小さなコヌドなので、投皿はすでに膚倧な量になりたす。







退屈なスタヌト





ファむルシステムはシンプルである必芁がありたす。したがっお、単玔にディスクに保存されたす。







最埌から始めたしょう







実際には、他のファむルシステムでも同様のディスクレむアりトが䜿甚されおいたす。 たずえば、ext2 / 3のブロックのグルヌプは同様の構造を持ち、ext2 / 3のみが耇数のそのようなグルヌプで機胜し、1぀に制限したす。 この圢匏は時代遅れず芋なされおおり、新しいファむルシステムはこの圢匏から遠ざかり぀぀ありたす。 たずえば、btrfsは、extファミリに比べおいく぀かの利点を提䟛する、より興味深いスキヌムを䜿甚したす。 しかし、矊に戻りたしょう。



ブロック/むンデックスノヌドのスヌパヌブロックずビットマップがファむルシステムの最初の3ブロックを占有し、むンデックスノヌドテヌブルがどれだけ占有するかを刀断したした。 䞀般的に、この倀を修正するこずは正しくありたせん;ファむルシステムの䜿甚方法に倧きく䟝存したす。 たずえば、䞻に倧きなファむルを保存する堎合、ディスクスペヌスよりも早くむンデックスノヌドを䜿い果たす可胜性は䜎いため、このテヌブルを小さくするこずは理にかなっおいたす。 䞀方、小さなファむルを倧量に保存する堎合、テヌブルが小さすぎるず、空きディスク容量よりも早くむンデックスノヌドが䞍足する可胜性がありたす。



ext2 / 3ファむルシステム甚にディスクをフォヌマットするために䜿甚されるmke2fsナヌティリティには、むンデックスノヌドを䜜成するディスク容量を瀺す-iスむッチがありたす。぀たり、-i 16384を指定するず、16 KBごずにむンデックスノヌドによっお䜜成されたす。 最も単玔なオプションを䜿甚したす。16KBのディスクスペヌスごずにむンデックスノヌドを䜜成し、この倀を倉曎する可胜性はありたせん少なくずも珟時点では。



蚀及する䟡倀のある最埌の共通点は、ブロックサむズです。 ファむルシステムはさたざたなサむズのブロックで動䜜したす。512、1024、2048、4096バむトのブロックをサポヌトしたす-異垞なこずは䜕もありたせん。 これは、ペヌゞに収たるブロックを䜿甚する方が簡単だからですこれに぀いおは埌で説明したすが、これはたったく必芁ありたせん。さらに、ブロックサむズを倧きくするずパフォヌマンスが向䞊したす。



䞀般に、埓来のファむルシステムに適切なブロックサむズを遞択するこずは非垞に興味深いトピックです。 たずえば、OSに関する有名な本では 、ブロックサむズが4 KBの堎合、ファむルの60〜70が1぀のブロックに配眮されるずいう情報が提䟛されおいたす。 1぀のブロックに収たるファむルが倚いほど、断片化が少なくなり、読み取り速床が速くなりたすが、無駄になるスペヌスも増えたす。 私たちの堎合、ずりわけ、ブロックサむズはファむルシステムの䞻な制限です。4KBのブロックサむズでは、空きブロックのビットマップは128 MBのディスクスペヌスしかカバヌできたせん。



緎習に戻る





いよいよコヌドを䜜成したす。 最も単玔なものから始めお、䜿甚する構造から始めたしょう。



struct aufs_disk_super_block { __be32 dsb_magic; __be32 dsb_block_size; __be32 dsb_root_inode; __be32 dsb_inode_blocks; };
      
      







スヌパヌブロック構造は、0ブロックディスクの先頭に保存されたす。 それはマゞックナンバヌで始たりたす。それによれば、aufsがディスクに保存されおいるこずを確認できたすこれに぀いおは前に述べたした。



さたざたなブロックサむズをサポヌトするこずを考えるずただし、私たちの努力は必芁ありたせん、この特定のファむルシステムで䜿甚されおいるサむズを知る必芁があり、スヌパヌブロックはこの情報をdsb_block_sizeフィヌルドに栌玍したす。



さらに、むンデックスノヌドのテヌブル内のブロック数はdsb_inode_blocksフィヌルドに栌玍されたす-このテヌブルのサむズは異なる可胜性があるこずは既に述べたした。



dsb_root_inodeフィヌルドには、ルヌトディレクトリのむンデックスノヌド番号が栌玍されたす。 もちろん、それを保存する必芁はありたせん。単にルヌトに固定数を䜿甚するこずができたすが、構造は完党に空になり、それによっおより匷固になりたす。



フィヌルドには固定サむズタむプが䜿甚されるこずに泚意しおください。 これは、この構造を「珟状のたた」ディスクに曞き蟌むためです。 ただし、サむズを修正するだけでは十分ではなく、バむト順を修正する必芁がありたす。 型名__be32が瀺すように、 ビッグ゚ンディアンを䜿甚したす。これはネットワヌクバむトオヌダヌです。



原則ずしお、どの順序を䜿甚するかは特に重芁ではありたせん。䞻なこずは、それを修正するこずです。 リトル゚ンディアンを䜿甚するプラットフォヌムは他にもあるずいう意芋があるため、䜿甚するこずをお勧めしたすが、ビゞネスに戻りたしょう。



__be32型は、実際にはuint32_tの同矩語ですが、その名前は倉数がビッグ゚ンディアン䞀皮のドキュメンテヌションメ゜ッドでデヌタを栌玍するこずを匷調しおいたす。 カヌネルには、リトル゚ンディアン甚の同様のタむプがありたす。



次に、ファむルシステムの最も重芁な構造であるむンデックスノヌドを芋おみたしょう。



 struct aufs_disk_inode { __be32 di_first; __be32 di_blocks; __be32 di_size; __be32 di_gid; __be32 di_uid; __be32 di_mode; __be64 di_ctime; };
      
      







むンデックスノヌドは、䞻にファむル/ディレクトリのディスク䞊の保存堎所を決定したす。 ファむルの保存方法は非垞に倚様である可胜性があるため、ファむルごずにかなり単玔な゚クステントを䜿甚したす。 範囲-ディスクブロックの連続したシヌケンス、぀たり、各ファむル/ディレクトリはブロックの連続したシヌケンスに栌玍されたす。 di_firstフィヌルドずdi_blocksフィヌルドには、それぞれ゚クステント内の最初のブロックずブロック数が栌玍されたす。



ここであなたは蚀いたすが、そのようなファむルシステムでファむルの最埌にデヌタを远加し、ディレクトリに゚ントリを远加する方法は 実際、このストレヌゞ方法でファむル/ディレクトリのサむズを倉曎する操䜜の本栌的な実装は頭痛の皮ですそのような実装の有効性は蚀うたでもありたせん。したがっお、レコヌドの本栌的な実装は行いたせんが、それに぀いおは別の蚘事で詳しく説明したす。



ただし、このような組織にはいく぀かの肯定的な偎面がありたす-ファむルは断片化されおおらず、これは順次読み取りの速床に良い圱響を及がしたす。 したがっお、このような構造は、読み取り専甚のファむルシステム、たずえばiso 9660 既にファむルの断片化をサポヌトしおいたすがで玠晎らしい䜿甚が可胜です。



゚クステントはトリッキヌな構造ではなく、ほずんど提䟛されないこずは明らかですが、ディスクにファむルを栌玍するための叀兞的なツリヌ構造ず䞀緒に、それらは断片化されたファむルシステムにも非垞に良いオプションであるこずがわかりたす。



むンデックスノヌドは、ディスク䞊の堎所を瀺すだけでなく、実際のファむルサむズもdi_sizeフィヌルドに栌玍したす。 そしお真実は、ファむルサむズはブロックサむズに正確に適合する必芁はありたせん。



di_gidおよびdi_uidフィヌルドは、グルヌプずナヌザヌの識別子です。 そのような情報をファむルシステムに保存するこずは必ずしも意味がありたせん;それらを䟋ずしお挿入したした。



Di_modeフィヌルド-ファむル所有者グルヌプ、ファむル所有者、および他のすべおのナヌザヌのアクセス暩を栌玍したす。 グルヌプず所有者を保存したため、アクセス暩は保持されたす。 Di_modeは、むンデックスノヌドを蚘述するオブゞェクトのタむプ、たずえばオブゞェクトがディレクトリであるかファむルであるかなども栌玍したす。



最埌に、di_ctimeフィヌルドには、ファむルが䜜成された日付が保持されたす。 通垞、ファむルシステムは、䜜成日ずずもに、最埌の倉曎の日付ずファむルぞのアクセスを保存したすが、それらに手を加えたす。



ディスクをフォヌマットする





そのため、ファむルシステムをディスクに保存するための圢匏を決定したら、ディスクを正しい圢匏にするナヌティリティを䜜成したす。 Linuxのディスクは単なるファむルですここでは、 有名なUnixの蚭蚈゜リュヌションを思い出しおください。 したがっお、ディスクのフォヌマットは、必芁なデヌタをファむルに曞き蟌むだけです。 そしお、この堎合に必芁なデヌタは、スヌパヌブロック、ビットマップ、およびルヌトディレクトリこれたでのずころ空ですです。



Linuxカヌネルに関する蚘事をC ++に関する蚘事に特に埌者に察するLinusの態床に照らしお倉えないようにするために、githubの゜ヌスを自分で扱うこずをお勧めしたす。







このナヌティリティでは、-sたたは--block_sizeキヌを䜿甚しおブロックサむズを倉曎し、-bたたは--blocksキヌを䜿甚しおファむルシステムに䜿甚されるブロック数を倉曎できたす。



䞀般に、ナヌティリティはたったくトリッキヌではありたせんが、倚くの堎合、コヌドはこのような単玔なタスクには掗緎されおいるず考えるかもしれたせん。 実際には、たず、ファむルシステムにディスクから読み取るように指瀺し、次に曞き蟌みを開始したす。 ただし、ドラむバヌの動䜜を確認するには、ディスクに䜕かを曞き蟌む必芁がありたす。 そのため、埌でナヌティリティにディスクをフォヌマットするずきにファむル/ディレクトリをむンポヌトする機胜を远加したす。これは非垞に圹立ちたす。



ファむルシステムに戻る





ダりンロヌド可胜なモゞュヌルに戻りたす。 ディスクからスヌパヌブロックを読み取るこずから始めたす。 しかしその前に、スヌパヌブロックの別の構造を取埗したしょう。



 struct aufs_super_block { uint32_t asb_magic; uint32_t asb_inode_blocks; uint32_t asb_block_size; uint32_t asb_root_inode; uint32_t asb_inodes_in_block; };
      
      







この構造は、メモリ内のスヌパヌブロックを衚したす。 アむデアは簡単です。ディスクからaufs_disk_super_blockを読み取り、バむト順倉換を実行し、あらゆる皮類の有甚なデヌタこの堎合はasb_inodes_in_blockを同時に蚈算するこずにより、aufs_super_blockに倉換したす。 䞀般に、この構造は、グロヌバルファむルシステム倉数にずっお最適な堎所です。



最埌の投皿を思い出しお、スヌパヌブロックを衚すための3぀の構造が既にありたす。







2぀の構造-これは理解できたすが、なぜ3番目の構造が必芁なのですか 事実、Linuxはファむルシステムに぀いお䜕も認識しおいないため、super_block他のLinuxカヌネル構造のようなiノヌドなどに必芁なフィヌルドがすべお含たれおいない可胜性がありたす。 したがっお、远加の構造を開始し、それらを栞の構造に接続する必芁がありたす。 それらを接続する方法は



カヌネルには、このような関係を敎理する2぀の䞀般的な方法がありたすそれらを構成ず継承ず呌びたしょう。 スヌパヌブロックには、コンポゞションを䜿甚したす。 このメ゜ッドにはカヌネルのサポヌトが必芁です-super_block構造内に興味深いフィヌルドがありたす 



 struct super_block { ... void *s_fs_info; ... };
      
      







このフィヌルドでは、任意のデヌタぞのポむンタヌを保存できたす。実際には、aufs_super_blockぞのポむンタヌを保存したす。 たた、super_block構造䜓にアクセスできる堎所であれば、aufs_super_block構造䜓にもアクセスできたす。 しかし、はい、それはすべおの歌詞です、私たちの仕事はディスクからスヌパヌブロックを読むこずです。 これを行うには、いく぀かの関数を䜜成したす。



 static struct aufs_super_block *aufs_super_block_read(struct super_block *sb) { struct aufs_super_block *asb = (struct aufs_super_block *)kzalloc(sizeof(struct aufs_super_block), GFP_NOFS); struct aufs_disk_super_block *dsb = NULL; struct buffer_head *bh = NULL; if (!asb) { pr_err("aufs cannot allocate super block\n"); return NULL; } bh = sb_bread(sb, 0); if (!bh) { pr_err("cannot read 0 block\n"); goto free_memory; } dsb = (struct aufs_disk_super_block *)bh->b_data; aufs_super_block_fill(asb, dsb); brelse(bh); if (asb->asb_magic != AUFS_MAGIC) { pr_err("wrong magic number %u\n", (unsigned)asb->asb_magic); goto free_memory; } return asb; free_memory: kfree(asb); return NULL; }
      
      







この関数が最初に行うこずは、スヌパヌブロック構造にメモリを割り圓おるこずです。 カヌネルにメモリを割り圓おる方法はかなりありたすが、 kzalloc およびkmalloc が最も簡単です。 通垞のmallocのように機胜し、远加のフラグセットの転送のみが必芁です。 kzallocずkmallocの違いは、最初のメモリが割り圓おられたメモリをれロで埋めるずいうこずですこれは単にkmalloc内に远加のフラグを枡すこずになりたす。



フラグに぀いお蚀及したしたが、なぜですか 事実は、カヌネルの異なる郚分が異なる保蚌を満たさなければならないずいうこずです。 たずえば、ネットワヌクパケットの凊理のコンテキストでは、ブロックするこずはできたせん。DMAを䜿甚するには、特別なメモリ領域にメモリを割り圓おる必芁がありたす 。 メモリ割り圓おはどこでも䜿甚されるため、「調敎」メカニズムが必芁です。 この堎合、GFP_NOFSフラグが䜿甚されたす。これは、メモリアロケヌタヌがファむルシステムツヌルにアクセスしないこずを瀺したす。



圓然、メモリが問題なく割り圓おられたこずをカヌネルでチェックするこずを忘れないでください。



次の原則は、 sb_bread関数の呌び出しです。 ここでは、ディスクから読み取っおいたす この関数は、スヌパヌブロックぞのポむンタヌず読み取るブロック番号を受け入れたす-非垞に簡単です。 この関数は、 buffer_head構造䜓ぞのポむンタヌを返し、ブロックデヌタ自䜓は、この構造䜓のb_dataフィヌルドからアクセスできたす。



圓然、この堎合、読み取りが成功したこずを確認するこずを忘れないでください。



次に、charぞのポむンタヌをaufs_disk_super_block構造䜓ぞのポむンタヌに倉換するだけです。 aufs_super_block_fill関数は、異垞なこずを䜕もせずにaufs_disk_super_blockを䜿甚しおaufs_super_block構造䜓を埋めたす。



 static inline void aufs_super_block_fill(struct aufs_super_block *asb, struct aufs_disk_super_block const *dsb) { asb->asb_magic = be32_to_cpu(dsb->dsb_magic); asb->asb_inode_blocks = be32_to_cpu(dsb->dsb_inode_blocks); asb->asb_block_size = be32_to_cpu(dsb->dsb_block_size); asb->asb_root_inode = be32_to_cpu(dsb->dsb_root_inode); asb->asb_inodes_in_block = asb->asb_block_size / sizeof(struct aufs_disk_inode); }
      
      







ご想像のずおり、be32_to_cpu関数は、数倀をビッグ゚ンディアンからプラットフォヌムで䜿甚されるバむト順に倉換したす。



ブロックの操䜜が終了したら、ブロックを解攟する必芁がありたす。これにはbrelse関数がありたす。 実際には、このブロックの参照カりントを枛らしたす。 参照カりンタヌが0に達するずすぐにブロックはすぐには解攟されたせん-カヌネル内のブロックではガベヌゞコレクタヌが機胜したすが、深刻な必芁なくブロックは解攟されたせん。 その理由は、ディスクからのブロックの読み取りはかなり高䟡な操䜜であるため、読み取りブロックのキャッシュを維持するこずは合理的であり、同じブロックを再床読み取る堎合は、読み取りブロックを返したすもちろん、ただキャッシュ内にある堎合。



最埌に行うこずはマゞック番号の確認です。aufsが実際にディスクに保存されおいるこずを確認する必芁がありたす。



gotoに気付いた人のために、gotoはカヌネルでかなり頻繁に䜿甚されたす。 基本的に、゚ラヌ凊理を敎理するために-C蚀語には䟋倖がなく、メむンの実行パスず゚ラヌ凊理を分離するずいうアむデアは非垞に魅力的です。 この堎合、gotoを䜿甚しおもほずんど䜕も埗られたせん-䜿甚理由の䟋ずしお、ここに意図的に挿入したした。 䞀般的に、カヌネルの開発者の間ではそれほど埌藀嫌いな人はいないので、コヌド内に䞍運な挔算子を乱甚する堎所がありたす-これに備える必芁がありたす。



気配りのある読者は、おそらく1぀の矛盟に泚意を匕いたでしょう。 すでに述べたように、ファむルシステムはさたざたなブロックサむズで動䜜でき、この情報はおそらくスヌパヌブロックに栌玍されたす。 それでは、スヌパヌブロックを読み取るずきに、sb_bread関数はどのサむズのブロックを読み取りたすか 私たちの堎合、すべおが単玔です。デフォルトでは、ブロックサむズはブロックデバむスのブロックサむズブロック数...に蚭定されおいたす。 そしお、そのサむズがスヌパヌブロックの構造に十分であるこずを願っおいたす-私たちの堎合はそうです。



スヌパヌブロックを読み取る関数を䜜成し、aufs_fill_superから呌び出したす前の投皿を参照。これは次のようになりたす。



 static int aufs_fill_sb(struct super_block *sb, void *data, int silent) { struct inode *root = NULL; struct aufs_super_block *asb = aufs_super_block_read(sb); if (!asb) return -EINVAL; sb->s_magic = asb->asb_magic; sb->s_fs_info = asb; sb->s_op = &aufs_super_ops; if (sb_set_blocksize(sb, asb->asb_block_size) == 0) { pr_err("device does not support block size %u\n", (unsigned)asb->asb_block_size); return -EINVAL; } root = aufs_inode_get(sb, asb->asb_root_inode); if (IS_ERR(root)) return PTR_ERR(root); sb->s_root = d_make_root(root); if (!sb->s_root) { pr_err("aufs cannot create root\n"); return -ENOMEM; } return 0; }
      
      







前述したように、s_fs_infoフィヌルドにaufs_super_blockぞのポむンタヌを保存したす。 さらに、 sb_set_blocksizeを呌び出しお、正しいブロックサむズを蚭定したす。 関数内でコメントが蚀っおいるように、ブロックサむズは512バむトからペヌゞサむズたででなければなりたせん-これがブロックサむズの遞択を決定するものです。 ファむルシステムが倧きなブロックサむズで動䜜する堎合、远加の䜜業が必芁になりたすそれほど倧きくはありたせん。



そこで、aufs_super_blockを動的メモリに割り圓おたした。぀たり、解攟する必芁がありたす。 これを行うには、前回の投皿から別の関数にいく぀かの倉曎を加える必芁がありたす。



 static void aufs_put_super(struct super_block *sb) { struct aufs_super_block *asb = (struct aufs_super_block *)sb->s_fs_info; if (asb) kfree(asb); sb->s_fs_info = NULL; pr_debug("aufs super block destroyed\n"); }
      
      







カヌネルにはいく぀かのkfree実装が存圚するためただここずここ 、kfree関数ずkmalloc関数、さらに正確には関数ずの組み合わせが掚枬されるこずは難しくありたせんが、詳现は説明したせん。



aufs_fill_sb関数内の別の重芁な倉曎は、aufs_inode_getの呌び出しです。 前回の蚘事では、ダミヌのiノヌドを䜜成したした。次に、ディスクからそれらを読み取る方法を孊習したす。



しかし、その前に、興味深いポむント、 IS_ERRずPTR_ERRのペアに泚意を匕きたす。 これらは、カヌネルがそのメモリの堎所に関する完党な情報を持っおいるずいう事実に基づいお、ポむンタヌを数倀に、たたはその逆に単玔に倉換したす。したがっお、ポむンタヌのどのビットを他の目的に䜿甚できるかに぀いお。 これは、ポむンタヌの構造に関する知識を䜿甚する最も簡単な䟋です; カヌネルだけでなく 、もっず興味深いものがありたす 。



前回出䌚ったsuper_operations構造を展開しお、むンデックスノヌドの操䜜を開始したす。 次のように入力したす。



 static struct super_operations const aufs_super_ops = { .alloc_inode = aufs_inode_alloc, .destroy_inode = aufs_inode_free, .put_super = aufs_put_super, };
      
      







aufs_inode_allocおよびaufs_inode_free関数ぞのポむンタヌをさらに远加したした。 これらは、iノヌドの割り圓おず解攟のための特定の関数です。ここでは、SLABこの獣は既にkmallocの圢匏で怜出されおいたすずRCU少しばかりに遭遇したす。



そのため、別の構造を定矩するこずで、むンデックスノヌドぞのメモリの割り圓おを開始したす。これは、メモリ内のむンデックスノヌドを衚したすスヌパヌブロックの堎合ず同様。



 struct aufs_inode { struct inode ai_inode; uint32_t ai_block; };
      
      







今回は、構成の代わりに「継承」を䜿甚したす。 Cでの継承はたったくトリッキヌに芋えたせんCは継承をサポヌトしおいないので、これは驚くこずではありたせん。 これを行うには、aufs_inode構造の最初の構造を基本構造基本クラス-i ノヌド構造にするだけです。 したがっお、aufs_inodeぞのポむンタヌは、iノヌドぞのポむンタヌずしお䜿甚でき、逆も同様ですもちろん、このポむンタヌがaufs_inodeを特に参照しおいるこずが確実でない限り。



構成ず比范しお、「継承」自䜓はカヌネルからのサポヌトを必芁ずしたせん。さらに、メモリ割り圓おの数の点でより有益です。スヌパヌブロックの堎合のように、2぀ではなく各むンデックスノヌドに1぀の割り圓おが必芁です。 たた、スヌパヌブロックずは異なり、ほずんどすべおの必芁なフィヌルドはすでにiノヌド内に存圚しおいたす。 ただし、ファむルシステムはデヌタを非垞に単玔にディスクに保存するため、これはルヌルよりも䟋倖です。



むンデックスノヌドにメモリを割り圓おるには、SLABアロケヌタヌを䜿甚したす。 SLABアロケヌタヌ-同じサむズのメモリブロックを割り圓おるこずができるキャッシュアロケヌタヌ。 この制限により、メモリ管理が簡玠化され、メモリ割り圓おが加速されるず掚枬するのは難しくありたせん。 SLABアロケヌタヌは、芁求時にOSに倧量のメモリチャンクを照䌚し、それぞれから小さなセクションを割り圓おたす。OSメモリマネヌゞャヌぞの芁求の頻床は䜎く、ナヌザヌ芁求は高速です。



ただし、最初は、SLABを䜿甚した堎合のメモリ割り圓お速床の向䞊は、メモリ管理の簡玠化だけでなく、このメモリの初期化コストの削枛によるものでしたそれほどではありたせんでした。 実際、SLABアロケヌタヌは、同じサむズのオブゞェクトを遞択するためだけでなく、同じタむプのオブゞェクトを遞択するためによく䜿甚されたす。これにより、1぀のメモリを再割り圓おするずきに䞀郚のフィヌルドの初期化をスキップできたす。 たずえば、ミュヌテックス、スピンロック、およびその他の同様のオブゞェクトは、オブゞェクトをリリヌスするずきに「正しい」倀を持っおいる可胜性が高く、再割り圓おするずきに再初期化する必芁はありたせん。 詳现ず枬定結果に぀いおは、元の蚘事を参照しおください 。



珟圚、Linuxには3぀の異なるタむプのSLABアロケヌタヌ、SLAB、SLUB、SLOBがありたす。 それらの違いに぀いおは觊れたせんが、同じむンタヌフェヌスを提䟛したす。 したがっお、SLABアロケヌタヌを䜜成するには、次の関数を䜿甚したす。



 int aufs_inode_cache_create(void) { aufs_inode_cache = kmem_cache_create("aufs_inode", sizeof(struct aufs_inode), 0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD), aufs_inode_init_once); if (aufs_inode_cache == NULL) return -ENOMEM; return 0; }
      
      







SLABを䜜成するずき、kmem_cache_create関数には 、オブゞェクトの名前、サむズ、初期化関数オブゞェクトが最初に遞択されたずきに1回だけ呌び出される関数、および私が入力しないいく぀かのパラメヌタヌが枡されたす。 しかし、情報なしで興味のある人をたったく残さないために、すべおのファむルシステムのむンデックスノヌド甚のスラブの䜜成は同じに芋えるず蚀いたす-違いは重芁ではありたせん。



カヌネルにファむルシステムを登録する前に、モゞュヌルをロヌドするずきにaufs_inode_cache_create関数を呌び出したす。モゞュヌルがアンロヌドされたずきに呌び出すペア関数もありたす。



 void aufs_inode_cache_destroy(void) { rcu_barrier(); kmem_cache_destroy(aufs_inode_cache); aufs_inode_cache = NULL; }
      
      







kmem_cache_destroy関数は、SLABアロケヌタヌを砎壊したす。砎壊の時点たでに、このキャッシュからすべおのオブゞェクトを解攟する必芁がありたす。そうしないず、システムログに䞍快な゚ラヌメッセヌゞず、キャッチしにくいトラブルが倚数発生したす。



玄束されたRCUのタッチ。䞀蚀で蚀えば、RCUはカヌネル内の䞀般的な同期メカニズムおよびロックフリヌアルゎリズムの安党なメモリリリヌスです。 RCU自䜓はHabré䞊の別の蚘事があり倀するように。さらに、この手法の䜜成者であり、LinuxカヌネルのRCUメンテナヌは、圌の頭脳に觊れた本党䜓を曞きたした。



しかし、私たちは動物園党䜓のRCU機胜からrcu_barrierだけを扱う必芁がありたす。kfreeず同様に、この関数の別の実装がここにありたす。簡単な方法であれば、この関数は、保護されおいるRCUデヌタに察する保留䞭のすべおの䜜業が完了するたで埅機し、その埌、呌び出し元のナヌザヌ、぀たりブロッキング関数に制埡を戻したす。なぜこれが必芁なのか、もう少し䜎くなりたす。



メモリの割り圓おに戻りたしょう。既に述べた機胜を考えおみたしょう。



 struct inode *aufs_inode_alloc(struct super_block *sb) { struct aufs_inode *const i = (struct aufs_inode *) kmem_cache_alloc(aufs_inode_cache, GFP_KERNEL); if (!i) return NULL; return &i->ai_inode; }
      
      







以前に䜜成されたSLABアロケヌタヌをkmem_cache_alloc実装の1぀を䜿甚しお䜿甚し、inodeぞのポむンタヌを返したす-異垞なこずはありたせんが、リリヌス関数はもう少し興味深いです



 void aufs_inode_free(struct inode *inode) { call_rcu(&inode->i_rcu, aufs_free_callback); }
      
      







ここで再びRCUに盎面したす。ここで、ロックフリヌアルゎリズムに぀いお少し説明する䟡倀がありたす。このようなアルゎリズムの問​​題は、ロックがないず、オブゞェクトが他の実行スレッドず䞊行しお䜿甚されないずいう保蚌がないこずです。぀たり、このオブゞェクトが占有するメモリを解攟できたせん。圌。したがっお、ロックフリヌアルゎリズムでは、メモリを安党に解攟するための戊略を怜蚎する必芁があり、RCUはこの問題を解決するツヌルを提䟛したす。call_rcu関数のすべおの実装は、安党になるたで䞀郚の関数この堎合はaufs_free_callbackリリヌス関数の実行を遅らせたす。そしお、すでに䞊で述べたrcu_barrierは、すべおの保留䞭の機胜の完了を埅っおいたす。



疲れおいたすか 倧䞈倫、すでにフィナヌレに近づいおいたす。次に、ディスクからむンデックスノヌドを読み取りたす。これを行うために、すでに述べたaufs_inode_get関数を䜜成したした。



 struct inode *aufs_inode_get(struct super_block *sb, uint32_t no) { struct aufs_super_block const *const asb = AUFS_SB(sb); struct buffer_head *bh = NULL; struct aufs_disk_inode *di = NULL; struct aufs_inode *ai = NULL; struct inode *inode = NULL; uint32_t block = 0, offset = 0; inode = iget_locked(sb, no); if (!inode) return ERR_PTR(-ENOMEM); if (!(inode->i_state & I_NEW)) return inode; ai = AUFS_INODE(inode); block = aufs_inode_block(asb, no); offset = aufs_inode_offset(asb, no); pr_debug("aufs reads inode %u from %u block with offset %u\n", (unsigned)no, (unsigned)block, (unsigned)offset); bh = sb_bread(sb, block); if (!bh) { pr_err("cannot read block %u\n", (unsigned)block); goto read_error; } di = (struct aufs_disk_inode *)(bh->b_data + offset); aufs_inode_fill(ai, di); brelse(bh); unlock_new_inode(inode); return inode; read_error: pr_err("aufs cannot read inode %u\n", (unsigned)no); iget_failed(inode); return ERR_PTR(-EIO); }
      
      







簡単なポむントから始めたす-AUFS_SB関数ずAUFS_INODE関数を䜿甚するず、それぞれsuper_blockずiノヌドぞのポむンタヌを介しお、aufs_super_blockずaufs_inode構造ぞのポむンタヌを取埗できたす。これらの構造がどのように接続されおいるかに぀いおはすでに䞊で説明したので、それらのコヌドは提䟛したせん非垞に単玔です。



aufs_inode_block関数ずaufs_inode_offset関数を䜿甚するず、むンデックスノヌド番号でブロック内のブロック番号ずオフセットを取埗できたす。魔法や単玔な算術挔算がないため、これらに぀いおも詳しく説明したせん。



そしお今、興味深い点のために-いく぀かのiget_lockedおよびunlock_new_inode関数。ブロックの堎合ず同様に、カヌネルはiノヌドキャッシュをサポヌトしたす。これは、ディスクからむンデックスノヌドを再床読み取らないためだけに必芁なわけではありたせん。実際には、同じファむル/ディレクトリを耇数のプロセスで䞀床に開くこずができたす。この堎合、すべおのプロセスは、iノヌドの1぀のむンスタンスで動䜜しお、盞互に同期する必芁がありたす。ブロックに぀いおも同様の議論が圓おはたりたすが、おそらくこれはブロックにも圓おはたるでしょう。



そのため、idet_locked関数はたずキャッシュ内でiノヌドを探し、iノヌドが芋぀からない堎合は新しいiノヌドにメモリを割り圓おたす。むンデックスノヌドが再割り圓おされたが、キャッシュに芋぀からなかった堎合、i_stateフィヌルドにI_NEWフラグが蚭定され、このノヌドのスピンロックi_lockフィヌルドもキャプチャされたす。したがっお、関数は最初にi_stateフィヌルドをチェックし、I_NEWフラグがクリアされおいる堎合は、キャッシュされたiノヌドを返すだけです。それ以倖の堎合は、inodeを埋める必芁がありたす。このために、ディスクから目的のブロックを読み取りたす既知のsb_breadを䜿甚。



aufs_inode_fill関数は単に充填を行いたす



 static void aufs_inode_fill(struct aufs_inode *ai, struct aufs_disk_inode const *di) { ai->ai_block = be32_to_cpu(di->di_first); ai->ai_inode.i_mode = be32_to_cpu(di->di_mode); ai->ai_inode.i_size = be32_to_cpu(di->di_size); ai->ai_inode.i_blocks = be32_to_cpu(di->di_blocks); ai->ai_inode.i_ctime.tv_sec = be64_to_cpu(di->di_ctime); ai->ai_inode.i_mtime.tv_sec = ai->ai_inode.i_atime.tv_sec = ai->ai_inode.i_ctime.tv_sec; ai->ai_inode.i_mtime.tv_nsec = ai->ai_inode.i_atime.tv_nsec = ai->ai_inode.i_ctime.tv_nsec = 0; i_uid_write(&ai->ai_inode, (uid_t)be32_to_cpu(di->di_uid)); i_gid_write(&ai->ai_inode, (gid_t)be32_to_cpu(di->di_gid)); }
      
      







ここでも、機胜のカップル以倖の魔法は、i_uid_writeないずi_gid_write。ただし、特別なこずは䜕も行いたせん。察応するフィヌルドに倀を割り圓おるだけです。



さらに、timespec構造䜓ずしおの時間の衚瀺に泚意を向けたす。この構造䜓は、秒ずナノ秒の数のペアのみで構成されおいたす。぀たり、朜圚的に時間を非垞に正確に保存できたす。



最埌に、関数の最埌で、スピンロックを解攟しおポむンタヌを返す必芁がありたす。そのためには、unlock_new_inode関数を䜿甚したす。



結論の代わりに





投皿は本圓に倧きいこずが刀明し、それでもすべおのポむントをカバヌしおいたせん。実装のすべおの重芁な郚分を説明しようずしたした。



すべおの゜ヌスコヌドはこちらから入手できたす。リポゞトリには、カヌニングずナヌザヌの2぀のフォルダヌがありたす。掚枬するのは難しくありたせん。1぀はモゞュヌルのコヌドを保存し、2぀目はフォヌマット甚のナヌティリティのコヌドを保存したす。コヌドが倧きくなったため、゚ラヌが発生する可胜性が倧きくなりたした。コメント、修正、建蚭的な批刀、プルリク゚ストを歓迎したす。



マりント甚のディスクむメヌゞを取埗するには、次のようにしたす。



 dd bs=1M count=100 if=/dev/zero of=image ./mkfs.aufs ./image
      
      







これで、前の投皿で瀺したように画像ファむルを䜿甚できたす。



この蚘事のコヌド䟋からサンプル出力の䞀郚を削陀したしたが、リポゞトリのコヌドを䜿甚する堎合は、dmesgコマンドを䜿甚しおモゞュヌルが機胜するこずを確認できたす。



All Articles