Betriebssystem auf Rust. Seitenspeicher: Erweitert

In diesem Artikel wird erläutert, wie der Kernel des Betriebssystems auf physische Speicherrahmen zugreifen kann. Wir werden die Funktion zum Konvertieren virtueller Adressen in physische Adressen untersuchen. Wir werden auch herausfinden, wie Sie neue Zuordnungen in Seitentabellen erstellen.

Dieser Blog ist auf GitHub veröffentlicht . Wenn Sie Fragen oder Probleme haben, öffnen Sie dort das entsprechende Ticket. Alle Quellen für den Artikel finden Sie hier .

Einführung


Im letzten Artikel haben wir die Prinzipien des Paging-Speichers und die Funktionsweise der vierstufigen Seitentabellen unter x86_64 kennengelernt. Wir haben auch festgestellt, dass der Loader bereits die Seitentabellenhierarchie für unseren Kernel eingerichtet hat, sodass der Kernel auf virtuellen Adressen ausgeführt wird. Dies verbessert die Sicherheit, aber das Problem tritt auf: Wie kann auf reale physische Adressen zugegriffen werden, die in Seitentabelleneinträgen oder im CR3 gespeichert sind?

Im ersten Abschnitt des Artikels werden wir das Problem und verschiedene Lösungsansätze diskutieren. Anschließend implementieren wir eine Funktion, die sich durch die Hierarchie der Seitentabellen schleicht, um virtuelle Adressen in physische Adressen umzuwandeln. Schließlich erfahren Sie, wie Sie neue Zuordnungen in Seitentabellen erstellen und nicht verwendete Speicherrahmen zum Erstellen neuer Tabellen finden.

Abhängigkeitsaktualisierungen


Zum Arbeiten benötigen Sie x86_64 Version 0.4.0 oder höher. Aktualisieren Sie die Abhängigkeit in unserer Cargo.toml :

 [dependencies] x86_64 = "0.4.0" # or later 

Zugriff auf Seitentabellen


Der Zugriff auf Seitentabellen über den Kernel ist nicht so einfach, wie es scheint. Um das Problem zu verstehen, werfen Sie einen weiteren Blick auf die vierstufige Tabellenhierarchie aus dem vorherigen Artikel:



Wichtig ist, dass in jedem Seiteneintrag die physikalische Adresse der nächsten Tabelle gespeichert wird. Dies vermeidet die Übersetzung dieser Adressen, was die Leistung verringert und leicht zu Endlosschleifen führt.

Das Problem ist, dass wir vom Kernel nicht direkt auf physische Adressen zugreifen können, da dies auch für virtuelle Adressen funktioniert. Wenn wir beispielsweise zur Adresse 4 KiB , erhalten wir Zugriff auf die virtuelle Adresse 4 KiB und nicht auf die physische Adresse, an der die Seitentabelle der 4. Ebene gespeichert ist. Wenn wir auf die physische Adresse von 4 KiB zugreifen möchten, müssen wir eine virtuelle Adresse verwenden, die in diese übersetzt wird.

Um auf die Frames der Seitentabellen zuzugreifen, müssen Sie diesen Frames einige virtuelle Seiten zuordnen. Es gibt verschiedene Möglichkeiten, solche Zuordnungen zu erstellen.

1. Eine einfache Lösung ist die identische Anzeige aller Seitentabellen .



In diesem Beispiel sehen wir die identische Anzeige von Frames. Die physischen Adressen der Seitentabellen sind gleichzeitig gültige virtuelle Adressen, so dass wir ab dem Register CR3 problemlos auf die Seitentabellen aller Ebenen zugreifen können.

Dieser Ansatz überfüllt jedoch den virtuellen Adressraum und macht es schwierig, große zusammenhängende Bereiche des freien Speichers zu finden. Angenommen, wir möchten in der obigen Abbildung einen virtuellen Speicherbereich von 1000 KB erstellen, um beispielsweise eine Datei im Speicher anzuzeigen . Wir können nicht mit der 28 KiB Region beginnen, da sie bei 1004 KiB auf einer bereits belegten Seite liegt. Daher müssen Sie weiter suchen, bis wir ein geeignetes großes Fragment finden, beispielsweise mit 1008 KiB . Es gibt das gleiche Fragmentierungsproblem wie im segmentierten Speicher.

Darüber hinaus ist die Erstellung neuer Seitentabellen viel komplizierter, da physische Frames gefunden werden müssen, deren entsprechende Seiten noch nicht verwendet werden. Für unsere Datei haben wir beispielsweise einen Bereich von 1000 KB virtuellem Speicher reserviert, beginnend bei der Adresse 1008 KiB . Jetzt können wir keinen Frame mit einer physischen Adresse zwischen 1000 KiB und 2008 KiB , da dieser nicht identisch angezeigt werden kann.

2. Eine andere Möglichkeit besteht darin , Seitentabellen nur vorübergehend zu senden, wenn Sie darauf zugreifen müssen. Für temporäre Vergleiche ist eine identische Anzeige nur der Tabelle der ersten Ebene erforderlich:



In dieser Abbildung verwaltet eine Tabelle der Ebene 1 die ersten 2 MiB des virtuellen Adressraums. Dies ist möglich, weil der Zugriff vom CR3-Register über Null-Einträge in den Tabellen der Ebenen 4, 3 und 2 erfolgt. Der Datensatz mit Index 8 übersetzt die virtuelle Seite mit 32 KiB in einen physischen Rahmen mit 32 KiB , wodurch die Tabelle der Ebene 1 selbst identifiziert wird. In der Abbildung ist dies durch einen horizontalen Pfeil dargestellt.

Durch Schreiben in die identisch zugeordnete Tabelle der Ebene 1 kann unser Kernel bis zu 511 Zeitvergleiche erstellen (512 abzüglich des für die Identitätszuordnung erforderlichen Datensatzes). Im obigen Beispiel hat der Kernel den Nulldatensatz einer Tabelle der Ebene 1 mit einem Frame bei 24 KiB abgeglichen. Dies erzeugte eine temporäre Zuordnung der virtuellen Seite bei 0 KiB zu dem physischen Rahmen der Seitentabelle der Seite 2, der durch den gepunkteten Pfeil angezeigt wird. Jetzt kann der Kernel auf die Tabelle der Ebene 2 zugreifen, indem er auf eine Seite schreibt, die bei 0 KiB beginnt.

Der Zugriff auf einen beliebigen Rahmen der Seitentabelle mit temporären Zuordnungen besteht daher aus den folgenden Aktionen:

  • Suchen Sie einen freien Eintrag in der identisch angezeigten Tabelle der Ebene 1.
  • Ordnen Sie diesen Eintrag dem physischen Rahmen der Seitentabelle zu, auf die wir zugreifen möchten.
  • Greifen Sie über die dem Eintrag zugeordnete virtuelle Seite auf diesen Frame zu.
  • Setzen Sie den Datensatz wieder auf nicht verwendet, wodurch die temporäre Zuordnung entfernt wird.

Bei diesem Ansatz bleibt der virtuelle Adressraum sauber, da ständig dieselben 512 virtuellen Seiten verwendet werden. Der Nachteil ist eine gewisse Umständlichkeit, insbesondere da für einen neuen Vergleich möglicherweise mehrere Tabellenebenen geändert werden müssen, dh der beschriebene Vorgang mehrmals wiederholt werden muss.

3. Obwohl beide oben genannten Ansätze funktionieren, gibt es eine dritte Methode: rekursive Seitentabellen . Es kombiniert die Vorteile beider Ansätze: Es vergleicht ständig alle Frames der Seitentabellen, ohne temporäre Vergleiche zu erfordern, und hält auch benachbarte Seiten nebeneinander, um eine Fragmentierung des virtuellen Adressraums zu vermeiden. Dies ist die Methode, die wir verwenden werden.

Rekursive Seitentabellen


Die Idee ist, einige Datensätze aus der Tabelle der vierten Ebene in sie selbst zu übersetzen. Daher reservieren wir tatsächlich einen Teil des virtuellen Adressraums und ordnen diesem Raum alle aktuellen und zukünftigen Tabellenrahmen zu.

Schauen wir uns ein Beispiel an, um zu verstehen, wie das alles funktioniert:



Der einzige Unterschied zum Beispiel am Anfang des Artikels besteht in einem zusätzlichen Datensatz mit dem Index 511 in der Tabelle der Ebene 4, der dem physischen Frame 4 KiB , der sich in dieser Tabelle selbst befindet.

Wenn die CPU in diesen Datensatz wechselt, bezieht sie sich nicht auf die Tabelle der Ebene 3, sondern erneut auf die Tabelle der Ebene 4. Dies ähnelt einer rekursiven Funktion, die sich selbst aufruft. Es ist wichtig, dass der Prozessor davon ausgeht, dass jeder Eintrag in der Tabelle der Ebene 4 auf eine Tabelle der Ebene 3 verweist. Daher wird die Tabelle der Ebene 4 jetzt als Tabelle der Ebene 3 behandelt. Dies funktioniert, da die Tabellen aller Ebenen in x86_64 dieselbe Struktur haben.

Indem Sie einem rekursiven Datensatz ein oder mehrere Male folgen, bevor Sie mit der eigentlichen Konvertierung beginnen, können Sie die Anzahl der Ebenen, die der Prozessor durchläuft, effektiv reduzieren. Wenn wir beispielsweise dem rekursiven Datensatz einmal folgen und dann zur Tabelle der Ebene 3 wechseln, denkt der Prozessor, dass die Tabelle der Ebene 3 eine Tabelle der Ebene 2 ist. Im weiteren Verlauf betrachtet er die Tabelle der Ebene 2 als Tabelle der Ebene 1 und die Tabelle der Ebene 1 als zugeordnet Frame im physischen Speicher. Dies bedeutet, dass wir jetzt in die Tabelle der Seitenebene 1 lesen und schreiben können, da der Prozessor dies für einen zugeordneten Frame hält. Die folgende Abbildung zeigt die fünf Schritte einer solchen Übersetzung:



Ebenso können wir einem rekursiven Eintrag zweimal folgen, bevor wir mit der Konvertierung beginnen, um die Anzahl der übergebenen Ebenen auf zwei zu reduzieren:



Lassen Sie uns diese Prozedur Schritt für Schritt durchgehen. Zuerst folgt die CPU einem rekursiven Eintrag in der Tabelle der Ebene 4 und denkt, dass sie die Tabelle der Ebene 3 erreicht hat. Dann folgt sie erneut dem rekursiven Datensatz und denkt, dass sie die Ebene 2 erreicht hat. In Wirklichkeit befindet sie sich jedoch immer noch auf Ebene 4. Dann geht die CPU zur neuen Adresse und gelangt in die Level 3-Tabelle, denkt jedoch, dass sie sich bereits in der Level 1-Tabelle befindet. Schließlich glaubt der Prozessor am nächsten Einstiegspunkt in der Level 2-Tabelle, auf den physischen Speicherrahmen zugegriffen zu haben. Dies ermöglicht uns das Lesen und Schreiben in eine Tabelle der Ebene 2.

Auf die Tabellen der Ebenen 3 und 4 wird ebenfalls zugegriffen. Um auf die Tabelle der Ebene 3 zuzugreifen, folgen wir dreimal einem rekursiven Datensatz: Der Prozessor glaubt, dass er sich bereits in der Tabelle der Ebene 1 befindet, und im nächsten Schritt erreichen wir die Ebene 3, die die CPU als zugeordneten Frame betrachtet. Um auf die Level 4-Tabelle selbst zuzugreifen, folgen wir einfach viermal dem rekursiven Datensatz, bis der Prozessor die Level 4-Tabelle selbst als zugeordneten Frame verarbeitet (in der folgenden Abbildung blau).



Das Konzept ist zunächst schwer zu verstehen, aber in der Praxis funktioniert es ziemlich gut.

Adressberechnung


Wir können also auf Tabellen aller Ebenen zugreifen, indem wir einem oder mehreren rekursiven Datensätzen folgen. Da Indizes in Tabellen mit vier Ebenen direkt von der virtuellen Adresse abgeleitet werden, müssen für diese Methode spezielle virtuelle Adressen erstellt werden. Wie wir uns erinnern, werden Seitentabellenindizes wie folgt aus der Adresse extrahiert:



Angenommen, wir möchten auf eine Tabelle der Ebene 1 zugreifen, in der eine bestimmte Seite angezeigt wird. Wie wir oben erfahren haben, müssen Sie einmal einen rekursiven Datensatz und dann die Indizes der 4., 3. und 2. Ebene durchlaufen. Dazu verschieben wir alle Adressblöcke einen Block nach rechts und setzen den Index des rekursiven Datensatzes an die Stelle des Anfangsindex der Ebene 4:



Um auf die Tabelle der Ebene 2 dieser Seite zuzugreifen, verschieben wir alle Indexblöcke zwei Blöcke nach rechts und setzen den rekursiven Index an die Stelle beider Quellblöcke: Ebene 4 und Ebene 3:



Um auf die Tabelle der Ebene 3 zuzugreifen, machen wir dasselbe, wir verschieben einfach bereits drei Adressblöcke nach rechts.



Um auf die Tabelle der Ebene 4 zuzugreifen, verschieben Sie alle vier Blöcke nach rechts.



Jetzt können Sie virtuelle Adressen für Seitentabellen aller vier Ebenen berechnen. Wir können sogar eine Adresse berechnen, die genau auf einen bestimmten Seitentabelleneintrag verweist, indem wir dessen Index mit 8 multiplizieren, der Größe des Seitentabelleneintrags.

Die folgende Tabelle zeigt die Struktur der Adressen für den Zugriff auf verschiedene Arten von Frames:

Virtuelle Adresse fürAdressstruktur ( oktal )
Seite0o_SSSSSS_AAA_BBB_CCC_DDD_EEEE
Eintrag in Level 1 Tabelle0o_SSSSSS_RRR_AAA_BBB_CCC_DDDD
Eintrag in eine Level 2 Tabelle0o_SSSSSS_RRR_RRR_AAA_BBB_CCCC
Eintrag in eine Level 3 Tabelle0o_SSSSSS_RRR_RRR_RRR_AAA_BBBB
Eintrag in Level 4 Tabelle0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA

Hier ist der Level 4-Index, ist Level 3, ist Level 2 und DDD ist Level 1-Index für den angezeigten Frame, EEEE ist sein Offset. RRR ist der Index des rekursiven Datensatzes. Ein Index (drei Ziffern) wird durch Multiplikation mit 8 (der Größe des Seitentabelleneintrags) in einen Versatz (vier Ziffern) umgewandelt. Mit diesem Versatz zeigt die resultierende Adresse direkt auf den entsprechenden Seitentabelleneintrag.

SSSS sind Erweiterungsbits der vorzeichenbehafteten Ziffer, SSSS sie sind alle Kopien von Bit 47. Dies ist eine spezielle Anforderung für gültige Adressen in der x86_64-Architektur, die wir in einem vorherigen Artikel erörtert haben.

Die Adressen sind oktal , da jedes Oktalzeichen drei Bits darstellt, wodurch Sie die 9-Bit-Indizes von Tabellen auf verschiedenen Ebenen klar trennen können. Dies ist im Hexadezimalsystem nicht möglich, bei dem jedes Zeichen vier Bits darstellt.

Implementierung


Nach all dieser Theorie können wir endlich mit der Implementierung fortfahren. Praktischerweise hat der Loader nicht nur Seitentabellen generiert, sondern auch eine rekursive Anzeige im letzten Datensatz der Level 4-Tabelle. Der Loader hat dies getan, da sonst ein Henne-Ei-Problem auftreten würde: Wir müssen auf die Level 4-Tabelle zugreifen, um eine rekursive Karte zu erstellen aber wir können nicht ohne Anzeige darauf zugreifen.

Wir haben diese rekursive Zuordnung bereits am Ende des vorherigen Artikels verwendet, um über die fest codierte Adresse 0xffff_ffff_ffff_f000 auf die Tabelle der Ebene 4 0xffff_ffff_ffff_f000 . Wenn wir diese Adresse in Oktal konvertieren und sie mit der obigen Tabelle vergleichen, werden wir sehen, dass sie genau der Struktur des Datensatzes in der Tabelle der Ebene 4 mit RRR = 0o777 , AAAA = 0 und den Erweiterungsbits des Vorzeichens 1 :

  Struktur: 0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA
 Adresse: 0o_177777_777_777_777_777_0000 

Dank der Kenntnis rekursiver Tabellen können wir jetzt virtuelle Adressen erstellen, um auf alle aktiven Tabellen zuzugreifen. Und machen Sie die Broadcast-Funktion.

Adressübersetzung


Erstellen Sie als ersten Schritt eine Funktion, die eine virtuelle Adresse in eine physische Adresse konvertiert und dabei die Hierarchie der Seitentabellen durchläuft:

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

 // in src/memory.rs use x86_64::PhysAddr; use x86_64::structures::paging::PageTable; /// Returns the physical address for the given virtual address, or `None` if the /// virtual address is not mapped. pub fn translate_addr(addr: usize) -> Option<PhysAddr> { // introduce variables for the recursive index and the sign extension bits // TODO: Don't hardcode these values 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); // check that level 4 entry is mapped let level_4_table = unsafe { &*(level_4_table_addr as *const PageTable) }; if level_4_table[l4_idx].addr().is_null() { return None; } // check that level 3 entry is mapped let level_3_table = unsafe { &*(level_3_table_addr as *const PageTable) }; if level_3_table[l3_idx].addr().is_null() { return None; } // check that level 2 entry is mapped let level_2_table = unsafe { &*(level_2_table_addr as *const PageTable) }; if level_2_table[l2_idx].addr().is_null() { return None; } // check that level 1 entry is mapped and retrieve physical address from it let level_1_table = unsafe { &*(level_1_table_addr as *const PageTable) }; let phys_addr = level_1_table[l1_idx].addr(); if phys_addr.is_null() { return None; } Some(phys_addr + page_offset) } 

Zunächst führen wir Variablen für den rekursiven Index (511 = 0o777 ) und die Vorzeichenerweiterungsbits (jeweils 1) ein. Dann berechnen wir die Indizes der Seitentabellen und den Versatz durch bitweise Operationen, wie in der Abbildung gezeigt:



Der nächste Schritt besteht darin, die virtuellen Adressen der vier Seitentabellen zu berechnen, wie im vorherigen Abschnitt beschrieben. Als Nächstes konvertieren wir in der Funktion jede dieser Adressen in PageTable Links. Dies sind unsichere Vorgänge, da der Compiler nicht wissen kann, dass diese Adressen gültig sind.

Nach der Berechnung der Adresse verwenden wir den Indexierungsoperator, um den Datensatz in der Tabelle der Ebene 4 anzuzeigen. Wenn dieser Datensatz Null ist, gibt es keine Tabelle der Ebene 3 für diesen Datensatz der Ebene 4. Dies bedeutet, dass addr keinem physischen Speicher zugeordnet ist. Also geben wir None . Ansonsten wissen wir, dass eine Level 3-Tabelle existiert. Dann wiederholen wir den Vorgang wie auf der vorherigen Ebene.

Nachdem wir drei Seiten einer höheren Ebene überprüft haben, können wir endlich den Datensatz der Tabelle der Ebene 1 lesen, der uns den physischen Frame angibt, mit dem die Adresse zugeordnet ist. Fügen Sie als letzten Schritt den Seitenversatz hinzu - und geben Sie die Adresse zurück.

Wenn wir sicher wären, dass die Adresse zugeordnet ist, können wir direkt auf die Tabelle der Ebene 1 zugreifen, ohne auf die Seiten einer höheren Ebene zu schauen. Da wir dies jedoch nicht wissen, müssen wir zuerst prüfen, ob eine Tabelle der Ebene 1 vorhanden ist. Andernfalls gibt unsere Funktion einen Fehler für fehlende Adressen zurück.

Versuchen Sie es


Versuchen wir, die Übersetzungsfunktion für virtuelle Adressen in unserer _start Funktion zu verwenden:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { […] // initialize GDT, IDT, PICS use blog_os::memory::translate_addr; // the identity-mapped vga buffer page println!("0xb8000 -> {:?}", translate_addr(0xb8000)); // some code page println!("0x20010a -> {:?}", translate_addr(0x20010a)); // some stack page println!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48)); println!("It did not crash!"); blog_os::hlt_loop(); } 


Nach dem Start sehen wir folgendes Ergebnis:



Wie erwartet wird die dem Bezeichner zugeordnete Adresse 0xb8000 in dieselbe physikalische Adresse übersetzt. Die Codepage und die Stapelseite werden in beliebige physikalische Adressen konvertiert, die davon abhängen, wie der Loader die anfängliche Zuordnung für unseren Kernel erstellt hat.

RecursivePageTable


x86_64 bietet einen RecursivePageTable Typ, der sichere Abstraktionen für verschiedene Seitentabellenoperationen implementiert. Mit diesem Typ können Sie die Funktion translate_addr viel prägnanter implementieren:

 // 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. /// /// This function is unsafe because it can break memory safety if an invalid /// address is passed. pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { let level_4_table_ptr = level_4_table_addr as *mut PageTable; let level_4_table = &mut *level_4_table_ptr; RecursivePageTable::new(level_4_table).unwrap() } /// Returns the physical address for the given virtual address, or `None` if /// the virtual address is not mapped. pub fn translate_addr(addr: u64, recursive_page_table: &RecursivePageTable) -> Option<PhysAddr> { 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())) } 

Der Typ RecursivePageTable kapselt das unsichere Crawlen von Seitentabellen vollständig ein, sodass der unsafe Code in der Funktion translate_addr nicht mehr benötigt wird. Die init Funktion bleibt unsicher, da die Richtigkeit der übergebenen level_4_table_addr garantiert werden level_4_table_addr .

Unsere _start Funktion muss aktualisiert werden, um die Funktion wie folgt neu zu signieren:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { […] // initialize GDT, IDT, PICS use blog_os::memory::{self, translate_addr}; const LEVEL_4_TABLE_ADDR: usize = 0o_177777_777_777_777_777_0000; let recursive_page_table = unsafe { memory::init(LEVEL_4_TABLE_ADDR) }; // the identity-mapped vga buffer page println!("0xb8000 -> {:?}", translate_addr(0xb8000, &recursive_page_table)); // some code page println!("0x20010a -> {:?}", translate_addr(0x20010a, &recursive_page_table)); // some stack page println!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48, &recursive_page_table)); println!("It did not crash!"); blog_os::hlt_loop(); } 

Anstatt LEVEL_4_TABLE_ADDR an translate_addr und über unsichere LEVEL_4_TABLE_ADDR auf die Seitentabellen zuzugreifen, übergeben wir jetzt Verweise auf den Typ RecursivePageTable . Somit haben wir jetzt eine sichere Abstraktion und eine klare Semantik des Eigentums. Dies stellt sicher, dass wir die Seitentabelle beim gemeinsamen Zugriff nicht versehentlich ändern können, da für deren Änderung der ausschließliche Besitz von RecursivePageTable erforderlich ist.

Diese Funktion liefert das gleiche Ergebnis wie die manuell geschriebene Originalübersetzungsfunktion.

Unsichere Funktionen sicherer machen


memory::init : unsafe , . , 4.

unsafe , unsafe . level_4_table_ptr :

 pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { let level_4_table_ptr = level_4_table_addr as *mut PageTable; let level_4_table = &mut *level_4_table_ptr; // <- this operation is unsafe RecursivePageTable::new(level_4_table).unwrap() } 

, , . , RecursivePageTable::new , . - .

, :

 // in src/memory.rs pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { /// Rust currently treats the whole body of unsafe functions as an unsafe /// block, which makes it difficult to see which operations are unsafe. To /// limit the scope of unsafe we use a safe inner function. fn init_inner(level_4_table_addr: usize) -> RecursivePageTable<'static> { let level_4_table_ptr = level_4_table_addr as *mut PageTable; let level_4_table = unsafe { &mut *level_4_table_ptr }; RecursivePageTable::new(level_4_table).unwrap() } init_inner(level_4_table_addr) } 

unsafe level_4_table_ptr , , . Rust RFC .


, — .

, . 1, . , 3, 3, 2 1.

, . , , 1. , , 0x1000 . 0xb8000 , VGA. , .

create_maping memory :

 // in src/memory.rs use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB}; pub fn create_example_mapping( recursive_page_table: &mut RecursivePageTable, frame_allocator: &mut impl FrameAllocator<Size4KiB>, ) { use x86_64::structures::paging::PageTableFlags as Flags; let page: Page = Page::containing_address(VirtAddr::new(0x1000)); let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); let flags = Flags::PRESENT | Flags::WRITABLE; let map_to_result = unsafe { recursive_page_table.map_to(page, frame, flags, frame_allocator) }; map_to_result.expect("map_to failed").flush(); } 

RecursivePageTable ( ) FrameAllocator , . map_to Mapper 0x1000 0xb8000 . , .

page frame , map_to . — . PRESENT , , WRITABLE .

, FrameAllocator . map_to , . Size4KiB , Page PhysFrame PageSize , 4 , 2 M / 1 .

map_to , Result . , , expect . MapperFlush , (TLB) flush . Result , #[must_use] , .

, 0x1000 , FrameAllocator 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 } } 

( 'method allocate_frame is not a member of trait FrameAllocator ', x86_64 0.4.0.)

:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { […] // initialize GDT, IDT, PICS use blog_os::memory::{create_example_mapping, EmptyFrameAllocator}; const LEVEL_4_TABLE_ADDR: usize = 0o_177777_777_777_777_777_0000; let mut recursive_page_table = unsafe { memory::init(LEVEL_4_TABLE_ADDR) }; create_example_mapping(&mut recursive_page_table, &mut EmptyFrameAllocator); unsafe { (0x1900 as *mut u64).write_volatile(0xf021f077f065f04e)}; println!("It did not crash!"); blog_os::hlt_loop(); } 

0x1000 , create_example_mapping RecursivePageTable . 0x1000 VGA, - .

0xf021f077f065f04e , “New!” . 0x1000 , println , 0x900 , . « VGA» , VGA , write_volatile .

QEMU, :



.

, 1 0x1000 . , , map_to , EmptyFrameAllocator . , 0xdeadbeaf000 0x1000 :

 // in src/memory.rs pub fn create_example_mapping(…) { […] let page: Page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); […] } // in src/main.rs #[no_mangle] pub extern "C" fn _start() -> ! { […] unsafe { (0xdeadbeaf900 as *mut u64).write_volatile(0xf021f077f065f04e)}; […] } 

:

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

, 1, FrameAllocator . , ?


, , VGA. BIOS UEFI , , . , . ( ) BIOS.

, _start . :

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

BootInfo , , semver . p4_table_addr , memory_map package :

  • p4_table_addr 4. 0o_177777_777_777_777_777_0000 .
  • memory_map , (, ).
  • package . , .

memory_map FrameAllocator , boot_info .

entry_point


_start , . , , .

, bootloader Rust entry_point . :

 // in src/main.rs use bootloader::{bootinfo::BootInfo, entry_point}; entry_point!(kernel_main); #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { […] // initialize GDT, IDT, PICS let mut recursive_page_table = unsafe { memory::init(boot_info.p4_table_addr as usize) }; […] // create and test example mapping println!("It did not crash!"); blog_os::hlt_loop(); } 

extern "C" no_mangle , _start . kernel_main Rust, . , , , , , .

, memory::init , boot_info.p4_table_addr . , , 4.


BIOS , . :

 // 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 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.into_iter().step_by(4096)); // create `PhysFrame` types from the start addresses let frames = frame_addresses.map(|addr| { PhysFrame::containing_address(PhysAddr::new(addr)) }); BootInfoFrameAllocator { frames } } 

:

  • iter MemoryRegion . filter , . , , (, ) , InUse . , , - .
  • map range Rust .
  • : into_iter , 4096- step_by . 4096 (4 ), . , . map flat_map , Iterator<Item = u64> Iterator<Item = Iterator<Item = u64>> .
  • PhysFrame , Iterator<Item = PhysFrame> . BootInfoFrameAllocator .

kernel_main , BootInfoFrameAllocator EmptyFrameAllocator :

 // in src/main.rs #[cfg(not(test))] fn kernel_main(boot_info: &'static BootInfo) -> ! { […] // initialize GDT, IDT, PICS use x86_64::structures::paging::{PageTable, RecursivePageTable}; let mut recursive_page_table = unsafe { memory::init(boot_info.p4_table_addr as usize) }; // new let mut frame_allocator = memory::init_frame_allocator(&boot_info.memory_map); blog_os::memory::create_mapping(&mut recursive_page_table, &mut frame_allocator); unsafe { (0xdeadbeaf900 as *mut u64).write_volatile(0xf021f077f065f04e)}; println!("It did not crash!"); blog_os::hlt_loop(); } 

— - „New!” . map_to :

  • frame_allocator .
  • . .
  • , .
  • .

create_maping — , . .

Zusammenfassung


, 4 . .

, . BIOS, .

Was weiter


, .

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


All Articles