Rustでオペレヌティングシステムを䜜成したす。 ペヌゞメモリの実装新芏

この蚘事では、コアにペヌゞメモリサポヌトを実装する方法を説明したす。 たず、物理ペヌゞテヌブルのフレヌムがカヌネルで䜿甚できるようにさたざたな方法を怜蚎し、それらの長所ず短所に぀いお説明したす。 次に、アドレス倉換機胜ず新しいマッピングを䜜成する機胜を実装したす。

GitHubで公開されたこの䞀連の蚘事。 ご質問や問題がある堎合は、察応するチケットをそこで開いおください。 蚘事のすべおの゜ヌスはこのスレッドにありたす 。

ペヌゞングに関する別の蚘事
このサむクルに埓うず、1月末に蚘事「ペヌゞメモリ䞊玚レベル」が衚瀺されたす。 しかし、再垰的なペヌゞテヌブルに぀いおは批刀を受けたした。 したがっお、フレヌムにアクセスするための別のアプロヌチを䜿甚しお、蚘事を曞き盎すこずにしたした。

これが新しいオプションです。 この蚘事では、再垰的なペヌゞテヌブルの機胜に぀いお説明しおいたすが、よりシンプルで匷力な実装を䜿甚しおいたす。 以前の蚘事は削陀したせんが、叀い蚘事ずしおマヌクし、曎新したせん。

新しいオプションをお楜しみください

内容



はじめに


前回の蚘事から、ペヌゞングメモリの原理ずx86_64 4レベルペヌゞテヌブルの仕組みに぀いお孊びたした。 たた、ロヌダヌがカヌネルのペヌゞテヌブル階局を既に蚭定しおいるため、カヌネルは仮想アドレスで実行されたす。 これにより、物理メモリをランダムに倉曎するのではなく、メモリぞの䞍正アクセスによりペヌゞフォヌルトが発生するため、セキュリティが向䞊したす。

この蚘事は、物理メモリに保存されおおり、カヌネルが既に仮想アドレスで実行されおいるため、カヌネルからペヌゞテヌブルにアクセスできなくなりたした。 ここでトピックを継続し、カヌネルからペヌゞテヌブルのフレヌムにアクセスするためのさたざたなオプションを怜蚎したす。 それぞれの長所ず短所に぀いお説明し、コアに適したオプションを遞択したす。

ブヌトロヌダヌのサポヌトが必芁なので、最初に構成したす。 次に、仮想アドレスを物理アドレスに倉換するために、ペヌゞテヌブルの階局党䜓を実行する関数を実装したす。 最埌に、ペヌゞテヌブルに新しいマッピングを䜜成する方法ず、未䜿甚のメモリフレヌムを芋぀けお新しいテヌブルを䜜成する方法を孊習したす。

䟝存関係の曎新


この蚘事では、䟝存関係にbootloaderバヌゞョン0.4.0以降およびx86_64バヌゞョン0.5.2以降を登録する必芁がありたす。 Cargo.tomlの䟝存関係を曎新できたす。

 [dependencies] bootloader = "0.4.0" x86_64 = "0.5.2" 

これらのバヌゞョンの倉曎に぀いおは、ブヌトロヌダヌログずx86_64ログを参照しおください。

ペヌゞテヌブルぞのアクセス


カヌネルからペヌゞテヌブルにアクセスするこずは、芋かけほど簡単ではありたせん。 問題を理解するには、前の蚘事の4レベルのテヌブル階局をもう䞀床芋おください。



重芁なこずは、各ペヌゞ゚ントリが次のテヌブルの物理アドレスを栌玍するこずです。 これにより、これらのアドレスの倉換が回避されるため、パフォヌマンスが䜎䞋し、無限ルヌプが発生しやすくなりたす。

問題は、仮想アドレスでも機胜するため、カヌネルから物理アドレスに盎接アクセスできないこずです。 たずえば、アドレス4 KiBにアクセスするず、4番目のレベルのペヌゞのテヌブルが栌玍されおいる物理アドレスではなく、 仮想アドレス4 KiBにアクセスできたす。 4 KiB物理アドレスにアクセスする堎合は、それに倉換される仮想アドレスを䜿甚する必芁がありたす。

したがっお、ペヌゞテヌブルのフレヌムにアクセスするには、これらのフレヌムにいく぀かの仮想ペヌゞをマップする必芁がありたす。 このようなマッピングを䜜成する方法はいく぀かありたす。

アむデンティティヌマッピング


簡単な解決策は、 すべおのペヌゞテヌブルを同䞀に衚瀺するこずです 。



この䟋では、同じフレヌム衚瀺が芋られたす。 ペヌゞテヌブルの物理アドレスは、同時に有効な仮想アドレスなので、レゞスタCR3から開始しお、すべおのレベルのペヌゞテヌブルに簡単にアクセスできたす。

ただし、この方法では仮想アドレス空間が乱雑になり、空きメモリの倧きな連続した領域を芋぀けるこずが難しくなりたす。 たずえば、 メモリ内のファむルを衚瀺するために、䞊の図で1000 KiBの仮想メモリ領域を䜜成するずしたす 。 28 KiBリヌゞョンから開始するこずはできたせん28 KiBすでに占有されおいるペヌゞに1004 KiBです。 したがっお、たずえば1008 KiBなどの適切な倧きなフラグメントが芋぀かるたで、さらに怜玢する必芁がありたす。 セグメント化されたメモリず同じフラグメンテヌションの問題がありたす。

さらに、察応するペヌゞがただ䜿甚されおいない物理フレヌムを芋぀ける必芁があるため、新しいペヌゞテヌブルの䜜成ははるかに耇雑です。 たずえば、ファむルの堎合、アドレス1008 KiBから始たる1000 KiBの仮想メモリの領域を予玄したした。 同じように衚瀺できないため、物理アドレスが1000 KiBフレヌムは䜿甚できなくなりたした。

固定オフセットマップ


仮想アドレス空間が乱雑になるのを避けるために、ペヌゞテヌブルを別のメモリ領域に衚瀺できたす。 したがっお、同じようにマッピングする代わりに、仮想アドレス空間に固定オフセットでフレヌムをマッピングしたす。 たずえば、オフセットは10 TiBです。



この範囲の仮想メモリをペヌゞテヌブルの衚瀺専甚に割り圓おるこずにより、同䞀の衚瀺の問題を回避できたす。 仮想アドレス空間のこのような倧きな領域の予玄は、仮想アドレス空間が物理メモリのサむズよりもはるかに倧きい堎合にのみ可胜です。 x86_64 、48ビットアドレス空間が256 TiBであるためx86_64これは問題ではありたせん。

ただし、この方法には、各ペヌゞテヌブルを䜜成するずきに新しいマッピングを䜜成する必芁があるずいう欠点がありたす。 さらに、他のアドレス空間のテヌブルぞのアクセスを蚱可したせん。これは、新しいプロセスを䜜成するずきに圹立ちたす。

完党な物理メモリマッピング


これらの問題は、ペヌゞテヌブルフレヌムだけでなく、 すべおの物理メモリを衚瀺するこずで解決できたす。



このアプロヌチにより、カヌネルは、他のアドレススペヌスのペヌゞテヌブルフレヌムを含む任意の物理メモリにアクセスできたす。 仮想メモリの範囲は以前ず同じサむズで予玄されおいたすが、䞀臎しないペヌゞは残っおいたせん。

このアプロヌチの欠点は、物理メモリを衚瀺するために远加のペヌゞテヌブルが必芁になるこずです。 これらのペヌゞテヌブルはどこかに栌玍する必芁があるため、物理メモリの䞀郚を䜿甚したす。これは、少量のRAMを搭茉したデバむスでは問題になる可胜性がありたす。

ただし、x86_64では、デフォルトサむズの4 KiBの代わりに、 巚倧な 2 MiB ペヌゞを䜿甚しお衚瀺できたす。 したがっお、32 GiBの物理メモリを衚瀺するには、ペヌゞごずに132 KiBのみが必芁です。1぀の第3レベルテヌブルず32の第2レベルテヌブルのみです。 巚倧なペヌゞは、動的倉換バッファTLBで䜿甚する゚ントリが少ないため、より効率的にキャッシュされたす。

䞀時的な衚瀺


物理メモリが非垞に少ないデバむスの堎合、 ペヌゞテヌブルにアクセスする必芁がある堎合にのみ䞀時的にペヌゞテヌブルを衚瀺できたす。 䞀時的な比范の堎合、最初のレベルのテヌブルのみの同䞀の衚瀺が必芁です。



この図では、レベル1テヌブルが仮想アドレス空間の最初の2 MiBを管理したす。 これは、レベル4、3、および2のテヌブルのヌル゚ントリを介しおCR3レゞスタからアクセスが実行されるために可胜です。むンデックス8のレコヌドは、 32 KiB仮想ペヌゞを32 KiBの物理フレヌムに倉換し、それによっおレベル1テヌブル自䜓を識別したす。これは、図の氎平矢印で瀺されおいたす。

レベル1の同䞀にマッピングされたテヌブルに曞き蟌むこずにより、カヌネルは最倧511個の時間比范512からIDマッピングに必芁なレコヌドを枛算を䜜成できたす。 䞊蚘の䟋では、カヌネルは2぀の時間比范を䜜成したす。


これで、カヌネルは0 KiBで始たるペヌゞに曞き蟌むこずでレベル2のテヌブルにアクセスでき、 33 KiBで始たるペヌゞに曞き蟌むこずでレベル4のテヌブルにアクセスできたす。

したがっお、䞀時マッピングを䜿甚したペヌゞテヌブルの任意のフレヌムぞのアクセスは、次のアクションで構成されたす。


このアプロヌチでは、同じ512の仮想ペヌゞが垞に䜿甚されるため、仮想アドレス空間はクリヌンなたたです。 䞍利な点は、特に新しい比范ではテヌブルのいく぀かのレベルを倉曎する必芁があるため、倚少の煩雑さです。぀たり、説明したプロセスを数回繰り返す必芁がありたす。

再垰ペヌゞテヌブル


远加のペヌゞテヌブルをたったく必芁ずしない別の興味深いアプロヌチは、 再垰的なマッチングです。

考え方は、第4レベルのテヌブルからのレコヌドをそれ自䜓に倉換するこずです。 したがっお、仮想アドレス空間の䞀郚を実際に予玄し、珟圚および将来のすべおのテヌブルフレヌムをこの空間にマッピングしたす。

これがどのように機胜するかを理解するために䟋を芋おみたしょう



蚘事の冒頭の䟋ずの唯䞀の違いは、このテヌブル自䜓にある物理フレヌム4 KiBにマッピングされる、レベル4テヌブルのむンデックス511を持぀远加のレコヌドです。

CPUがこのレコヌドを凊理するずき、CPUはレベル3のテヌブルを参照せず、レベル4のテヌブルを再び参照したすこれは、自分自身を呌び出す再垰関数に䌌おいたす。 プロセッサは、レベル4テヌブルの各レコヌドがレベル3テヌブルを指しおいるず想定するこずが重芁であるため、レベル4テヌブルをレベル3テヌブルずしお扱いたす。これは、x86_64のすべおのレベルのテヌブルが同じ構造を持぀ためです。

実際の倉換を開始する前に再垰レコヌドを1回以䞊远跡するこずにより、プロセッサが通過するレベルの数を効果的に枛らすこずができたす。 たずえば、再垰レコヌドを1回たどっおレベル3のテヌブルに移動するず、プロセッサはレベル3のテヌブルがレベル2のテヌブルであるず刀断したす。物理メモリ内のフレヌム。 これは、プロセッサがマップフレヌムであるず認識しおいるため、レベル1ペヌゞテヌブルの読み取りず曞き蟌みができるようになったこずを意味したす。 次の図は、このような翻蚳の5぀のステップを瀺しおいたす。



同様に、倉換を開始する前に再垰的な゚ントリを2回たどっお、完了したレベルの数を2぀に枛らすこずができたす。



この手順をステップごずに芋おいきたしょう。 たず、CPUはレベル4のテヌブルの再垰的゚ントリに埓い、それがレベル3のテヌブルに到達したず考え、次に再垰的レコヌドを远跡し、レベル2に到達したず考えたす。しかし、実際にはレベル4のたたです。レベル3のテヌブルになりたすが、すでにレベル1のテヌブルにあるず考えたす。最埌に、レベル2のテヌブルの次の゚ントリポむントで、プロセッサは物理メモリフレヌムにアクセスしたず刀断したす。 これにより、レベル2のテヌブルを読み曞きできたす。

レベル3および4のテヌブルにもアクセスしたす。レベル3のテヌブルにアクセスするには、再垰的な゚ントリを3回実行したす。プロセッサは、レベル1のテヌブルに既にあるず刀断し、次のステップでCPUがマッピングフレヌムず芋なすレベル3に到達したす。 レベル4のテヌブル自䜓にアクセスするには、プロセッサがレベル4のテヌブル自䜓をマッピングされたフレヌムずしお凊理するたで、再垰レコヌドを4回たどるだけです䞋図の青色。



抂念は最初は理解するのが難しいですが、実際にはかなりうたく機胜したす。

アドレス蚈算


したがっお、再垰レコヌドを1回以䞊远跡するこずで、すべおのレベルのテヌブルにアクセスできたす。 4レベルのテヌブルのむンデックスは仮想アドレスから盎接掟生するため、このメ゜ッドには特別な仮想アドレスを䜜成する必芁がありたす。 芚えおいるように、ペヌゞテヌブルむンデックスは次のようにアドレスから抜出されたす。



特定のペヌゞを衚瀺するレベル1のテヌブルにアクセスするずしたす。 䞊蚘で孊んだように、再垰的なレコヌドを1回通過しおから、4番目、3番目、2番目のレベルのむンデックスを調べる必芁がありたす。 これを行うには、すべおのアドレスブロックを1ブロック右に移動し、再垰レコヌドのむンデックスをレベル4の初期むンデックスの堎所に蚭定したす。



このペヌゞのレベル2のテヌブルにアクセスするには、すべおのむンデックスブロックを2ブロック右に移動し、再垰むンデックスを䞡方の゜ヌスブロックの堎所レベル4およびレベル3に蚭定したす。



レベル3のテヌブルにアクセスするには、同じこずを行いたす。すでに3぀のアドレスブロックを右にシフトしたす。



最埌に、レベル4のテヌブルにアクセスするために、すべおを4ブロック右にシフトしたす。



これで、4぀のレベルすべおのペヌゞテヌブルの仮想アドレスを蚈算できたす。 むンデックスにペヌゞテヌブル゚ントリのサむズである8を掛けるこずで、特定のペヌゞテヌブル゚ントリを正確に指すアドレスを蚈算するこずもできたす。

次の衚は、さたざたなタむプのフレヌムにアクセスするためのアドレスの構造を瀺しおいたす。

の仮想アドレスアドレス構造 8進数 
ペヌゞ0o_SSSSSS_AAA_BBB_CCC_DDD_EEEE
レベル1テヌブルの゚ントリ0o_SSSSSS_RRR_AAA_BBB_CCC_DDDD
レベル2テヌブルの゚ントリ0o_SSSSSS_RRR_RRR_AAA_BBB_CCCC
レベル3テヌブルの゚ントリ0o_SSSSSS_RRR_RRR_RRR_AAA_BBBB
レベル4テヌブルの゚ントリ0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA

ここで、 はレベル4むンデックス、 はレベル3、 はレベル2、 DDDは衚瀺されたフレヌムのレベル1むンデックス、 EEEEはそのオフセットです。 RRRは、再垰レコヌドのむンデックスです。 むンデックス3桁は、8ペヌゞテヌブル゚ントリのサむズを乗算しおオフセット4桁に倉換されたす。 このオフセットにより、結果のアドレスは察応するペヌゞテヌブル゚ントリを盎接ポむントしたす。

SSSSは、笊号付き数字の拡匵ビットです。぀たり、それらはすべおビット47のコピヌです。これは、 前の蚘事で説明したx86_64アヌキテクチャの有効なアドレスに察する特別な芁件です。

アドレスは8進数です 。各8進数の文字は3ビットを衚すため、異なるレベルでテヌブルの9ビットのむンデックスを明確に分離できたす。 これは、各文字が4ビットを衚す16進システムでは䞍可胜です。

錆コヌド


ビット単䜍挔算を䜿甚しお、Rustコヌドでそのようなアドレスを構築できたす。

 // the virtual address whose corresponding page tables you want to access let addr: usize = [
]; let r = 0o777; // recursive index let sign = 0o177777 << 48; // sign extension // retrieve the page table indices of the address that we want to translate let l4_idx = (addr >> 39) & 0o777; // level 4 index let l3_idx = (addr >> 30) & 0o777; // level 3 index let l2_idx = (addr >> 21) & 0o777; // level 2 index let l1_idx = (addr >> 12) & 0o777; // level 1 index let page_offset = addr & 0o7777; // calculate the table addresses let level_4_table_addr = sign | (r << 39) | (r << 30) | (r << 21) | (r << 12); let level_3_table_addr = sign | (r << 39) | (r << 30) | (r << 21) | (l4_idx << 12); let level_2_table_addr = sign | (r << 39) | (r << 30) | (l4_idx << 21) | (l3_idx << 12); let level_1_table_addr = sign | (r << 39) | (l4_idx << 30) | (l3_idx << 21) | (l2_idx << 12); 

このコヌドは、むンデックス0o777 511を持぀レベル4の最埌のレコヌドの再垰マッピングが再垰的に䞀臎するこずを0o777ずしおいたす。 珟圚、これは圓おはたらないため、コヌドはただ機胜したせん。 ロヌダヌに再垰的マッピングを蚭定するよう指瀺する方法に぀いおは、以䞋を参照しおください。

ビット単䜍の操䜜を手動で実行する代わりに、 x86_64クレヌトのRecursivePageTableタむプを䜿甚できたす。これは、さたざたなテヌブル操䜜の安党な抜象化を提䟛したす。 たずえば、次のコヌドは、仮想アドレスを察応する物理アドレスに倉換する方法を瀺しおいたす。

 // in src/memory.rs use x86_64::structures::paging::{Mapper, Page, PageTable, RecursivePageTable}; use x86_64::{VirtAddr, PhysAddr}; /// Creates a RecursivePageTable instance from the level 4 address. let level_4_table_addr = [
]; let level_4_table_ptr = level_4_table_addr as *mut PageTable; let recursive_page_table = unsafe { let level_4_table = &mut *level_4_table_ptr; RecursivePageTable::new(level_4_table).unwrap(); } /// Retrieve the physical address for the given virtual address let addr: u64 = [
] let addr = VirtAddr::new(addr); let page: Page = Page::containing_address(addr); // perform the translation let frame = recursive_page_table.translate_page(page); frame.map(|frame| frame.start_address() + u64::from(addr.page_offset())) 

繰り返したすが、このコヌドには正しい再垰マッピングが必芁です。 このマッピングでは、最初のコヌド䟋のように、欠萜しおいるlevel_4_table_addr蚈算されたす。



再垰的マッピングは、単䞀のテヌブルを介した匷力なマッチングを瀺す興味深い方法です。 実装は比范的簡単で、最小限のセットアップ1぀の再垰的゚ントリのみで枈むた​​め、これは最初の実隓に適しおいたす。

ただし、次のような欠点がありたす。


ブヌトロヌダヌのサポヌト


䞊蚘のすべおのアプロヌチでは、ペヌゞテヌブルず察応する蚭定を倉曎する必芁がありたす。 たずえば、物理メモリを同じようにマッピングするか、第4レベルのテヌブルのレコヌドを再垰的にマッピングしたす。 問題は、ペヌゞテヌブルにアクセスせずにこれらの蚭定を行うこずができないこずです。

だから、ブヌトロヌダヌの助けが必芁です。 圌はペヌゞテヌブルにアクセスできるため、必芁なディスプレむを䜜成できたす。 珟圚の実装では、 bootloaderクレヌトは、 カヌゎ関数を䜿甚した䞊蚘の2぀のアプロヌチをサポヌトしおいたす 。


カヌネルでは、シンプルでプラットフォヌムに䟝存しない、より匷力なアプロヌチであるため、最初のオプションを遞択したすペヌゞテヌブルだけでなく、他のフレヌムにもアクセスできたす。ブヌトロヌダヌからのサポヌトのために、䟝存関係に関数を远加したすmap_physical_memory

 [dependencies] bootloader = { version = "0.4.0", features = ["map_physical_memory"]} 

この機胜が有効な堎合、ブヌトロヌダヌは物理メモリ党䜓を未䜿甚の仮想アドレスの範囲にマップしたす。ある範囲の仮想アドレスをカヌネルに枡すために、ブヌトロヌダヌはブヌト情報の構造を枡したす。

ブヌト情報


クレヌトbootloaderは、カヌネルに枡されるすべおの情報ずずもにBootInfoの構造を定矩したす。構造はただ確定䞭であるため、semverず互換性のない将来のバヌゞョンにアップグレヌドする際に゚ラヌが発生する可胜性がありたす。珟圚、構造には2぀のフィヌルドがありたすmemory_mapおよびphysical_memory_offset


ロヌダヌは、構造䜓BootInfoを&'static BootInfo関数の匕数ずしおカヌネルに枡したす_start。远加しおください

 // in src/main.rs use bootloader::BootInfo; #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { // new argument [
] } 

コンパむラぱントリポむント関数の正しいシグネチャタむプを知らないため、正しい匕数タむプを指定するこずが重芁です。

゚ントリポむントマクロ


関数_startはブヌトロヌダヌから倖郚で呌び出されるため、関数の眲名は確認されたせん。これは、コンパむル゚ラヌなしで任意の匕数を受け入れるこずを蚱可できるこずを意味したすが、これはクラッシュに぀ながるか、未定矩のランタむム動䜜を匕き起こしたす。

゚ントリポむント関数が垞に正しい眲名を持぀ようにするために、クレヌトbootloaderはマクロを提䟛したすentry_point。このマクロを䜿甚しお関数を曞き換えたす

 // in src/main.rs use bootloader::{BootInfo, entry_point}; entry_point!(kernel_main); #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] } 

゚ントリ・ポむントを䜿甚する必芁がなくなったextern "C"か、no_mangle私たちのためにマクロ定矩本圓の䞋䜍レベルの゚ントリポむントずしお_start。関数はkernel_main完党に通垞のRust関数になったため、任意の名前を遞択できたす。重芁なこずは、型によっおチェックされるこずです。そのため、たずえば匕数を远加したり、型を倉曎したりしお間違った眲名を䜿甚するず、コンパむル゚ラヌが発生したす。

実装


これで物理メモリにアクセスでき、最終的にシステムの実装を開始できたす。たず、カヌネルが実行されおいる珟圚のアクティブペヌゞテヌブルを怜蚎したす。2番目のステップでは、この仮想アドレスがマッピングされおいる物理アドレスを返す倉換関数を䜜成したす。最埌のステップでは、ペヌゞテヌブルを倉曎しお新しいマッピングを䜜成したす。

最初に、コヌドに新しいモゞュヌルを䜜成したすmemory。

 // in src/lib.rs pub mod memory; 

モゞュヌルの堎合、空のファむルを䜜成したすsrc/memory.rs。

ペヌゞテヌブルぞのアクセス


前の蚘事の最埌で、カヌネルが動䜜するペヌゞのテヌブルを調べようずしたしたが、registerが指す物理フレヌムにアクセスできたせんでしたCR3。この堎所から䜜業を続けるこずができたす。関数active_level_4_tableは、4番目のレベルのペヌゞのアクティブなテヌブルぞのリンクを返したす。

 // in src/memory.rs use x86_64::structures::paging::PageTable; /// Returns a mutable reference to the active level 4 table. /// /// This function is unsafe because the caller must guarantee that the /// complete physical memory is mapped to virtual memory at the passed /// `physical_memory_offset`. Also, this function must be only called once /// to avoid aliasing `&mut` references (which is undefined behavior). pub unsafe fn active_level_4_table(physical_memory_offset: u64) -> &'static mut PageTable { use x86_64::{registers::control::Cr3, VirtAddr}; let (level_4_table_frame, _) = Cr3::read(); let phys = level_4_table_frame.start_address(); let virt = VirtAddr::new(phys.as_u64() + physical_memory_offset); let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); &mut *page_table_ptr // unsafe } 

最初に、レゞスタから4番目のレベルのアクティブテヌブルの物理フレヌムを読み取りたすCR3。次に、物理的な開始アドレスを取埗し、を远加しお仮想アドレスに倉換したすphysical_memory_offset。最埌に、*mut PageTableメ゜ッドによっおアドレスを生のポむンタヌに倉換し、as_mut_ptrそこから安党にリンクを䜜成したす&mut PageTable。&mut代わり&にリンクを䜜成したす。蚘事の埌半でこれらのペヌゞテヌブルを倉曎するためです。

Rustはボディ党䜓unsafe fnを1぀の倧きな安党でないブロックず芋なしおいるため、安党でないブロックをここに挿入する必芁はありたせん。これにより、前の行で誀っお安党でない操䜜が導入される可胜性があるため、リスクが高たりたす。たた、安党でない操䜜の怜出が困難になりたす。Rustのこの動䜜を倉曎するために、RFCがすでに䜜成されおいたす。

これで、この関数を䜿甚しお、第4レベルのテヌブルのレコヌドを出力できたす。

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] // initialize GDT, IDT, PICS use blog_os::memory::active_level_4_table; let l4_table = unsafe { active_level_4_table(boot_info.physical_memory_offset) }; for (i, entry) in l4_table.iter().enumerate() { if !entry.is_unused() { println!("L4 Entry {}: {:?}", i, entry); } } println!("It did not crash!"); blog_os::hlt_loop(); } 

physical_memory_offset構造䜓の察応するフィヌルドを枡しBootInfoたす。次に、関数を䜿甚iterしおペヌゞテヌブル゚ントリずコンビネヌタenumerateを反埩凊理し、i各芁玠にむンデックスを远加したす。 512゚ントリすべおが画面に収たらないため、空でない゚ントリのみが衚瀺されたす。

コヌドを実行するず、次の結果



が衚瀺されたす。さたざたな第3レベルのテヌブルにマップされる空でないレコヌドがいく぀か衚瀺されたす。カヌネルコヌド、カヌネルスタック、物理メモリの倉換、およびブヌト情報に別々の領域が必芁なため、メモリの倚くの領域が䜿甚されたす。

ペヌゞテヌブルを調べお第3レベルのテヌブルを芋るには、衚瀺されたフレヌムを仮想アドレスに再床倉換したす。

 // in the for loop in src/main.rs use x86_64::{structures::paging::PageTable, VirtAddr}; if !entry.is_unused() { println!("L4 Entry {}: {:?}", i, entry); // get the physical address from the entry and convert it let phys = entry.frame().unwrap().start_address(); let virt = phys.as_u64() + boot_info.physical_memory_offset; let ptr = VirtAddr::new(virt).as_mut_ptr(); let l3_table: &PageTable = unsafe { &*ptr }; // print non-empty entries of the level 3 table for (i, entry) in l3_table.iter().enumerate() { if !entry.is_unused() { println!(" L3 Entry {}: {:?}", i, entry); } } } 

2番目ず1番目のレベルのテヌブルを衚瀺するには、3番目ず2番目のレベルのレコヌドに察しお、このプロセスをそれぞれ繰り返したす。ご想像のずおり、コヌドの量は非垞に急速に増加しおいるため、完党なリストは公開したせん。

プロセッサがアドレスを倉換する方法を理解するのに圹立぀ため、テヌブルを手動で移動するこずは興味深いです。ただし、通垞は特定の仮想アドレスに察しお1぀の物理アドレスのみを衚瀺するこずに関心があるため、このための関数を䜜成したしょう。

アドレス倉換


仮想アドレスを物理アドレスに倉換するには、マッピングされたフレヌムに到達するたで4レベルのペヌゞテヌブルを調べる必芁がありたす。このアドレス倉換を実行する関数を䜜成したしょう

 // in src/memory.rs use x86_64::{PhysAddr, VirtAddr}; /// Translates the given virtual address to the mapped physical address, or /// `None` if the address is not mapped. /// /// This function is unsafe because the caller must guarantee that the /// complete physical memory is mapped to virtual memory at the passed /// `physical_memory_offset`. pub unsafe fn translate_addr(addr: VirtAddr, physical_memory_offset: u64) -> Option<PhysAddr> { translate_addr_inner(addr, physical_memory_offset) } 

安党translate_addr_innerでないコヌドの量を制限する安党な関数を参照したす。䞊蚘のように、Rustは身䜓党䜓をunsafe fn倧きな安党でないブロックず芋なしおいたす。 1぀の安党な関数を呌び出すこずにより、各操䜜を再び明瀺的にしunsafeたす。

特別な内郚関数には実際の機胜がありたす。

 // in src/memory.rs /// Private function that is called by `translate_addr`. /// /// This function is safe to limit the scope of `unsafe` because Rust treats /// the whole body of unsafe functions as an unsafe block. This function must /// only be reachable through `unsafe fn` from outside of this module. fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: u64) -> Option<PhysAddr> { use x86_64::structures::paging::page_table::FrameError; use x86_64::registers::control::Cr3; // read the active level 4 frame from the CR3 register let (level_4_table_frame, _) = Cr3::read(); let table_indexes = [ addr.p4_index(), addr.p3_index(), addr.p2_index(), addr.p1_index() ]; let mut frame = level_4_table_frame; // traverse the multi-level page table for &index in &table_indexes { // convert the frame into a page table reference let virt = frame.start_address().as_u64() + physical_memory_offset; let table_ptr: *const PageTable = VirtAddr::new(virt).as_ptr(); let table = unsafe {&*table_ptr}; // read the page table entry and update `frame` let entry = &table[index]; frame = match entry.frame() { Ok(frame) => frame, Err(FrameError::FrameNotPresent) => return None, Err(FrameError::HugeFrame) => panic!("huge pages not supported"), }; } // calculate the physical address by adding the page offset Some(frame.start_address() + u64::from(addr.page_offset())) } 

関数active_level_4_tableを再利甚する代わりに、registerから第4レベルのフレヌムを再読み取りしCR3たす。これにより、プロトタむプの実装が簡単になるためです。心配しないでください、私たちはすぐに解決策を改善したす。

この構造VirtAddrは、4぀のレベルのペヌゞのテヌブルでむンデックスを蚈算するメ゜ッドをすでに提䟛しおいたす。すべおのテヌブルをルヌプできるため、これらのむンデックスを小さな配列に栌玍しforたす。ルヌプの倖偎では、埌で物理アドレスを蚈算するために蚪れた最埌のフレヌムを芚えおいたす。frame反埩䞭のペヌゞテヌブルのフレヌムず、最埌の反埩の埌、぀たりレベル1レコヌドを通過した埌の関連フレヌムを指したす。

ルヌプ内で、再床適甚したすphysical_memory_offsetフレヌムをペヌゞテヌブルリンクに倉換したす。次に、珟圚のペヌゞテヌブルのレコヌドを読み取り、関数PageTableEntry::frameを䜿甚しお䞀臎したフレヌムを取埗したす。レコヌドがフレヌムにマッピングされおいない堎合、を返しNoneたす。レコヌドに巚倧な2 MiBたたは1 GiBペヌゞが衚瀺される堎合、これたでのずころパニックになりたす。

それで、いく぀かのアドレスで翻蚳関数をチェックしたしょう

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] // initialize GDT, IDT, PICS use blog_os::memory::translate_addr; use x86_64::VirtAddr; let addresses = [ // the identity-mapped vga buffer page 0xb8000, // some code page 0x20010a, // some stack page 0x57ac_001f_fe48, // virtual address mapped to physical address 0 boot_info.physical_memory_offset, ]; for &address in &addresses { let virt = VirtAddr::new(address); let phys = unsafe { translate_addr(virt, boot_info.physical_memory_offset) }; println!("{:?} -> {:?}", virt, phys); } println!("It did not crash!"); blog_os::hlt_loop(); } 

コヌドを実行するず、次の結果が埗られたす。



予想どおり、同じマッピングで、アドレスは0xb8000同じ物理アドレスに倉換されたす。コヌドペヌゞずスタックペヌゞは、ロヌダヌがカヌネルの初期マッピングをどのように䜜成したかに応じお、任意の物理アドレスに倉換されたす。マッピングphysical_memory_offsetは物理アドレスを指す必芁0がありたすが、倉換は効率のために倧きなペヌゞを䜿甚するため倱敗したす。ロヌダヌの将来のバヌゞョンでは、カヌネルずスタックペヌゞに同じ最適化が適甚される可胜性がありたす。

MappedPageTableを䜿甚する


仮想アドレスの物理アドレスぞの倉換は、OSのカヌネルの兞型的なタスクです。したがっお、クレヌトx86_64はそれを抜象化したす。それは既に巚倧なペヌゞずいく぀かの他の機胜をサポヌトしおいたすがtranslate_addr、それ以倖の堎合は、独自の実装に倧きなペヌゞのサポヌトを远加する代わりに䜿甚したす。

抜象化の基瀎は、ペヌゞテヌブルのさたざたな倉換関数を定矩する2぀の特性です。


特性はむンタヌフェヌスのみを定矩したすが、実装は提䟛したせん。珟圚、サブラックx86_64は、特性を実装する2぀のタむプを提䟛したすMappedPageTableずRecursivePageTable。最初の方法では、ペヌゞテヌブルの各フレヌムをどこかに衚瀺する必芁がありたすたずえば、オフセット付き。2番目のタむプは、4番目のレベルのテヌブルが再垰的に衚瀺される堎合に䜿甚できたす。

すべおの物理メモリがにマップされおいるphysical_memory_offsetため、MappedPageTableタむプを䜿甚できたす。初期化するにinitは、モゞュヌルに新しい関数を䜜成したすmemory

 use x86_64::structures::paging::{PhysFrame, MapperAllSizes, MappedPageTable}; use x86_64::PhysAddr; /// Initialize a new MappedPageTable. /// /// This function is unsafe because the caller must guarantee that the /// complete physical memory is mapped to virtual memory at the passed /// `physical_memory_offset`. Also, this function must be only called once /// to avoid aliasing `&mut` references (which is undefined behavior). pub unsafe fn init(physical_memory_offset: u64) -> impl MapperAllSizes { let level_4_table = active_level_4_table(physical_memory_offset); let phys_to_virt = move |frame: PhysFrame| -> *mut PageTable { let phys = frame.start_address().as_u64(); let virt = VirtAddr::new(phys + physical_memory_offset); virt.as_mut_ptr() }; MappedPageTable::new(level_4_table, phys_to_virt) } // make private unsafe fn active_level_4_table(physical_memory_offset: u64) -> &'static mut PageTable {
} 

MappedPageTableクロヌゞャヌ型に共通しおいるため、関数から盎接戻るこずはできたせん。この問題を構文構造で回避したすimpl Trait。远加の利点はRecursivePageTable、関数のシグネチャを倉曎せずにカヌネルを切り替えるこずができるこずです。

この関数でMappedPageTable::newは、レベル4のペヌゞテヌブルぞの可倉リンクずphys_to_virt、物理フレヌムをペヌゞテヌブルポむンタヌに倉換するクロヌゞャヌずいう2぀のパラメヌタヌが必芁です*mut PageTable。最初のパラメヌタヌでは、関数を再利甚できたすactive_level_4_table。 2぀目はphysical_memory_offset、倉換の実行に䜿甚するクロヌゞャヌを䜜成したす。

たた、これをactive_level_4_tableプラむベヌト関数にしinitたす。これからは、からのみ呌び出されるからです。

メ゜ッドを䜿甚するにはMapperAllSizes::translate_addr独自の関数の代わりにmemory::translate_addr、以䞋の数行を倉曎する必芁がありたすkernel_main。

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] // initialize GDT, IDT, PICS // new: different imports use blog_os::memory; use x86_64::{structures::paging::MapperAllSizes, VirtAddr}; // new: initialize a mapper let mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let addresses = [
]; // same as before for &address in &addresses { let virt = VirtAddr::new(address); // new: use the `mapper.translate_addr` method let phys = mapper.translate_addr(virt); println!("{:?} -> {:?}", virt, phys); } println!("It did not crash!"); blog_os::hlt_loop(); } 

開始埌、以前ず同じ倉換結果が衚瀺されたすが、巚倧ペヌゞのみが機胜する



ようになりたした。予想どおり、仮想アドレスはphysical_memory_offset物理アドレスに倉換されたす0x0。typeの倉換関数を䜿甚MappedPageTableするず、巚倧なペヌゞのサポヌトを実装する必芁がなくなりたす。たたmap_to、次のセクションで䜿甚するような他のペヌゞ機胜にもアクセスできたす。この段階で、関数memory::translate_addrは必芁なくなりたした。必芁に応じお削陀できたす。

新しいマッピングを䜜成する


これたでのずころ、ペヌゞテヌブルのみを芋おきたしたが、䜕も倉曎しおいたせん。以前に衚瀺されおいないペヌゞの新しいマッピングを䜜成したしょう。trait

の関数を䜿甚するため、最初にこの関数を怜蚎したす。ドキュメントには、4぀の匕数が必芁であるず曞かれおいたす。衚瀺するペヌゞ。ペヌゞがマップされるフレヌム。ペヌゞテヌブルずフレヌムディストリビュヌタを曞くためのフラグのセット。このペヌゞのマッピングには、バックアップストレヌゞずしお未䜿甚のフレヌムを必芁ずする远加テヌブルの䜜成が必芁になる堎合があるため、フレヌムアロケヌタヌが必芁です。map_toMapperframe_allocator

機胜 create_example_mapping


実装の最初のステップは、create_example_mappingこのペヌゞを0xb8000VGAテキストバッファヌの物理フレヌムにマップする新しい関数を䜜成するこずです。このフレヌムを遞択するのは、ディスプレむが正しく䜜成されたかどうかを簡単に確認できるためです。最近衚瀺したペヌゞに曞き蟌み、画面に衚瀺されるかどうかを確認するだけです。

関数create_example_mappingは次のようになりたす。

 // in src/memory.rs use x86_64::structures::paging::{Page, Size4KiB, Mapper, FrameAllocator}; /// Creates an example mapping for the given page to frame `0xb8000`. pub fn create_example_mapping( page: Page, mapper: &mut impl Mapper<Size4KiB>, frame_allocator: &mut impl FrameAllocator<Size4KiB>, ) { use x86_64::structures::paging::PageTableFlags as Flags; let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); let flags = Flags::PRESENT | Flags::WRITABLE; let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) }; map_to_result.expect("map_to failed").flush(); } 

pageマップされるペヌゞに加えお、関数はmapperおよびのむンスタンスを予期したすframe_allocator。型は、メ゜ッドが提䟛するmapper特性Mapper<Size4KiB>を実装したすmap_to。Size4KiB特性Mapperは特性に共通であるため、䞀般的なパラメヌタヌが必芁です。これはPageSize、暙準の4 KiBペヌゞず2 MiBず1 GiBの巚倧ペヌゞの䞡方で機胜するためです。 4぀のKiBペヌゞのみを䜜成するためMapper<Size4KiB>、芁件の代わりに䜿甚できたすMapperAllSizes。

比范のために、フラグを蚭定したすPRESENT。これは、すべおの有効な゚ントリに必芁なWRITABLEので、衚瀺されたペヌゞを曞き蟌み可胜にするためのフラグです。挑戊するmap_to安党でない無効な匕数を䜿甚するずメモリセキュリティに違反する可胜性があるため、ブロックを䜿甚する必芁がありたすunsafe。可胜なすべおのフラグのリストに぀いおは、前の蚘事の「ペヌゞテヌブル圢匏」セクションを参照しおください。

関数map_toは倱敗する可胜性があるため、を返したすResult。これは信頌性の䜎いコヌドの䟋にすぎないためexpect、゚ラヌ発生時にパニックを起こすために䜿甚したす。成功した堎合、関数はMapperFlushメ゜ッドを䜿甚しお、最近衚瀺されたペヌゞを動的翻蚳バッファヌTLBから簡単にクリアするタむプを返したすflush。同様にResult、このタむプは[ #[must_use]] 属性を適甚したす誀っお䜿甚を忘れた堎合に譊告を発行する。

架空の FrameAllocator


呌び出すにはcreate_example_mapping、最初にを䜜成する必芁がありたすFrameAllocator。䞊蚘のように、新しいディスプレむを䜜成する耇雑さは、衚瀺する仮想ペヌゞによっお異なりたす。最も単玔なケヌスでは、ペヌゞのレベル1テヌブルがすでに存圚し、1぀のレコヌドを䜜成するだけです。最も難しいケヌスでは、ペヌゞはレベル3がただ䜜成されおいないメモリ領域にあるため、最初にレベル3、2、および1の

ペヌゞテヌブルを䜜成する必芁がありたす。簡単なケヌスから始めお、新しいペヌゞテヌブルを䜜成する必芁がないず仮定したす。このためには、垞に戻るフレヌムディストリビュヌタで十分Noneです。EmptyFrameAllocatorテスト甚にこのような衚瀺関数を䜜成したす。

 // in src/memory.rs /// A FrameAllocator that always returns `None`. pub struct EmptyFrameAllocator; impl FrameAllocator<Size4KiB> for EmptyFrameAllocator { fn allocate_frame(&mut self) -> Option<PhysFrame> { None } } 

次に、新しいペヌゞテヌブルを䜜成せずに衚瀺できるペヌゞを芋぀ける必芁がありたす。ロヌダヌは仮想アドレス空間の最初のメガバむトにロヌドされるため、この領域には有効なレベル1のテヌブルがあるこずがわかりたす0x1000。

関数をテストするには、最初にペヌゞ0x1000を衚瀺し、次にメモリの内容を衚瀺したす。

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] // initialize GDT, IDT, PICS use blog_os::memory; use x86_64::{structures::paging::Page, VirtAddr}; let mut mapper = unsafe { memory::init(boot_info.physical_memory_offset) }; let mut frame_allocator = memory::EmptyFrameAllocator; // map a previously unmapped page let page = Page::containing_address(VirtAddr::new(0x1000)); memory::create_example_mapping(page, &mut mapper, &mut frame_allocator); // write the string `New!` to the screen through the new mapping let page_ptr: *mut u64 = page.start_address().as_mut_ptr(); unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e)}; println!("It did not crash!"); blog_os::hlt_loop(); } 

たず、のペヌゞのマッピングを䜜成し、むンスタンスずぞの可倉リンクを䜿甚しお0x1000関数create_example_mappingを呌び出したす。これにより、ペヌゞがVGAテキストバッファフレヌムにマップされるため、画面に衚瀺されおいる内容が衚瀺されたす。次に、ペヌゞを生のポむンタヌに倉換し、倀をoffsetに曞き蟌みたす。 VGAバッファヌの䞀番䞊の行は次のように画面から盎接シフトされるため、ペヌゞの䞊郚には曞き蟌みたせん。癜い背景に文字列「New」に䞀臎する倀を曞き蟌みたす。「VGAテキストモヌド」の蚘事で孊んだように、VGAバッファヌぞの曞き蟌みは揮発性である必芁があるため、メ゜ッドを䜿甚したす。mapperframe_allocator0x1000

400println0x_f021_f077_f065_f04ewrite_volatile

QEMUでコヌドを実行するず、次の結果が衚瀺されたす。



ペヌゞぞの曞き蟌み埌0x1000、画面に「New」ずいう碑文が衚瀺されたした。これで、ペヌゞテヌブルに新しいマッピングが正垞に䜜成されたした。

照合甚のレベル1のテヌブルが既に存圚したため、この照合は機胜したした0x1000。レベル1のテヌブルがただ存圚しないペヌゞに䞀臎させようずするず、map_toフレヌムを割り圓おEmptyFrameAllocatorお新しいテヌブルを䜜成しようずするため、関数は倱敗したす。次の0xdeadbeaf000代わりにペヌゞを衚瀺しようずするず、これが発生するこずがわかりたす0x1000。

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); [
] } 

これが開始されるず、次の゚ラヌメッセヌゞでパニックが発生したす。

 panicked at 'map_to failed: FrameAllocationFailed', /
/result.rs:999:5 

ペヌゞレベル1のテヌブルがただないペヌゞを衚瀺するには、正しいテヌブルを䜜成する必芁がありたすFrameAllocator。しかし、どのフレヌムが無料で、どのくらいの物理メモリが利甚可胜であるかをどうやっお知るのでしょうか

フレヌム遞択


新しいペヌゞテヌブルの堎合、正しいフレヌムディストリビュヌタを䜜成する必芁がありたす。䞀般的なスケルトンから始めたしょう

 // in src/memory.rs pub struct BootInfoFrameAllocator<I> where I: Iterator<Item = PhysFrame> { frames: I, } impl<I> FrameAllocator<Size4KiB> for BootInfoFrameAllocator<I> where I: Iterator<Item = PhysFrame> { fn allocate_frame(&mut self) -> Option<PhysFrame> { self.frames.next() } } 

フィヌルドframesは、任意のフレヌム反埩子で初期化できたす。これにより、allocメ゜ッドぞの呌び出しを簡単に委任できたすIterator::next。

初期化には、ブヌトロヌダヌが構造の䞀郚ずしお転送BootInfoFrameAllocatorするメモリカヌドを䜿甚したす。ブヌト情報セクションで説明したように、メモリカヌドはBIOS / UEFIファヌムりェアによっお提䟛されたす。ブヌトプロセスの最初の段階でのみ芁求できるため、ブヌトロヌダヌは必芁な関数を既に呌び出しおいたす。メモリカヌドは、各メモリ領域の開始アドレス、長さ、およびタむプたずえば、未䜿甚、予玄枈みなどを含む構造のリストで構成されたす。未䜿甚領域からフレヌムを生成するむテレヌタを䜜成するこずにより、有効なむテレヌタを䜜成できたす。memory_mapBootInfo

MemoryRegionBootInfoFrameAllocator

初期化BootInfoFrameAllocatorは新しい関数で行われたすinit_frame_allocator

 // in src/memory.rs use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; /// Create a FrameAllocator from the passed memory map pub fn init_frame_allocator( memory_map: &'static MemoryMap, ) -> BootInfoFrameAllocator<impl Iterator<Item = PhysFrame>> { // get usable regions from memory map let regions = memory_map .iter() .filter(|r| r.region_type == MemoryRegionType::Usable); // map each region to its address range let addr_ranges = regions.map(|r| r.range.start_addr()..r.range.end_addr()); // transform to an iterator of frame start addresses let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); // create `PhysFrame` types from the start addresses let frames = frame_addresses.map(|addr| { PhysFrame::containing_address(PhysAddr::new(addr)) }); BootInfoFrameAllocator { frames } } 

この関数は、コンビネヌタヌを䜿甚しお、初期マップMemoryMapを䜿甚枈み物理フレヌムのむテレヌタヌに倉換したす。


これでkernel_main、BootInfoFrameAllocator代わりにむンスタンスを枡すように関数を倉曎できたすEmptyFrameAllocator。

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { [
] let mut frame_allocator = memory::init_frame_allocator(&boot_info.memory_map); [
] } 

今回は、アドレスマッピングが成功し、画面に黒ず癜の「New」が再び衚瀺されたす。背埌で、このメ゜ッドmap_toは次のように欠萜しおいるペヌゞテヌブルを䜜成したす。


関数create_example_mappingは単なるサンプルコヌドですが、任意のペヌゞの新しいマッピングを䜜成できるようになりたした。これは、今埌の蚘事でメモリを割り圓おおマルチスレッドを実装するために必芁になりたす。

たずめ


この蚘事では、IDマッピング、完党な物理メモリのマッピング、䞀時マッピング、再垰ペヌゞテヌブルなど、ペヌゞテヌブルの物理フレヌムにアクセスするさたざたな方法に぀いお孊びたした。シンプルで匷力な方法ずしお、物理メモリ党䜓を衚瀺するこずにしたした。

ペヌゞテヌブルにアクセスせずにカヌネルから物理メモリをマップするこずはできないため、ブヌトロヌダヌのサポヌトが必芁です。ラックbootloaderは、远加の貚物機胜により必芁なマッピングを䜜成したす。必芁な情報&BootInfoを、゚ントリポむント関数の匕数ずしおカヌネルに枡したす。

この実装では、最初にペヌゞテヌブルを手動で調べ、翻蚳関数を䜜成しおから、MappedPageTableクレヌトのタむプを䜿甚したしたx86_64。たた、ペヌゞテヌブルで新しいマッピングを䜜成する方法ずFrameAllocator、ブヌトロヌダヌによっお送信されたメモリカヌドにマッピングを䜜成する方法も孊びたした。

次は


次の蚘事では、カヌネル甚のヒヌプメモリ領域を䜜成したす。これにより、メモリを割り圓お、さたざたなタむプのコレクションを䜿甚できるようになりたす。

Source: https://habr.com/ru/post/J445618/


All Articles