Kami menulis sistem operasi di Rust. Halaman organisasi memori

Pada artikel ini, kami menyajikan halaman , skema manajemen memori yang sangat umum yang juga kami terapkan di OS kami. Artikel ini menjelaskan mengapa isolasi memori diperlukan, bagaimana segmentasi bekerja, apa memori virtual itu, dan bagaimana halaman memecahkan masalah fragmentasi. Kami juga menjelajahi skema tabel halaman bertingkat dalam arsitektur x86_64.

Blog ini diposting di GitHub . Jika Anda memiliki pertanyaan atau masalah, buka permintaan terkait di sana.

Perlindungan memori


Salah satu tugas utama sistem operasi adalah mengisolasi program dari satu sama lain. Misalnya, browser tidak boleh mengganggu editor teks. Ada berbagai pendekatan tergantung pada implementasi perangkat keras dan OS.

Sebagai contoh, beberapa prosesor ARM Cortex-M (dalam embedded system) memiliki unit proteksi memori (MPU) yang mendefinisikan sejumlah kecil (misalnya, 8) area memori dengan izin akses yang berbeda (misalnya, tidak ada akses, hanya baca, baca dan catatan). Setiap kali memori diakses, MPU memastikan bahwa alamatnya berada di area dengan izin yang benar, jika tidak maka akan melempar pengecualian. Dengan mengubah ruang lingkup dan izin akses, OS memastikan bahwa setiap proses hanya memiliki akses ke memorinya untuk mengisolasi proses dari satu sama lain.

Pada x86, dua pendekatan berbeda untuk melindungi memori didukung: segmentasi dan paging .

Segmentasi


Segmentasi diterapkan kembali pada tahun 1978, awalnya untuk meningkatkan jumlah memori addressable. Pada saat itu, CPU hanya mendukung alamat 16-bit, yang membatasi jumlah memori yang dapat dialamatkan hingga 64 KB. Untuk meningkatkan volume ini, register segmen tambahan diperkenalkan, yang masing-masing berisi alamat offset. CPU secara otomatis menambahkan offset ini pada setiap akses memori, sehingga menangani memori hingga 1 MB.

CPU secara otomatis memilih register segmen tergantung pada jenis akses memori: register segmen kode CS digunakan untuk menerima instruksi, dan register segmen stack SS digunakan untuk operasi stack (push / pop). Instruksi lain menggunakan register segmen data DS atau register segmen ES opsional. Kemudian, dua register segmen tambahan FS dan GS ditambahkan secara gratis.

Dalam versi pertama segmentasi, register secara langsung berisi offset dan kontrol akses tidak dilakukan. Dengan munculnya mode terlindungi, mekanismenya telah berubah. Saat CPU beroperasi dalam mode ini, deskriptor segmen menyimpan indeks dalam tabel deskriptor lokal atau global, yang selain alamat offset berisi ukuran segmen dan izin akses. Dengan memuat tabel deskriptor global / lokal yang terpisah untuk setiap proses, OS dapat mengisolasi proses satu sama lain.

Dengan mengubah alamat memori sebelum akses aktual, segmentasi menerapkan metode yang sekarang digunakan hampir di mana-mana: itu adalah memori virtual .

Memori virtual


Ide memori virtual adalah untuk mengabstraksi alamat memori dari perangkat fisik. Alih-alih langsung mengakses perangkat penyimpanan, langkah konversi terlebih dahulu dilakukan. Dalam hal segmentasi, alamat offset segmen aktif ditambahkan pada tahap terjemahan. Bayangkan sebuah program yang mengakses alamat memori 0x1234000 dalam sebuah segmen dengan offset 0x1111000 : pada kenyataannya, alamatnya menjadi 0x2345000 .

Untuk membedakan antara dua jenis alamat, alamat sebelum konversi disebut virtual , dan alamat setelah konversi disebut fisik . Ada satu perbedaan penting di antara mereka: alamat fisik adalah unik dan selalu merujuk ke lokasi unik yang sama dalam memori. Alamat virtual, di sisi lain, bergantung pada fungsi terjemahan. Dua alamat virtual yang berbeda mungkin merujuk ke alamat fisik yang sama. Selain itu, alamat virtual yang identik dapat merujuk ke berbagai alamat fisik setelah konversi.

Contoh penggunaan yang berguna dari properti ini adalah peluncuran paralel dari program yang sama dua kali:



Di sini, program yang sama berjalan dua kali, tetapi dengan fungsi konversi yang berbeda. Contoh pertama memiliki offset segmen 100, sehingga alamat virtualnya 0-150 dikonversi ke alamat fisik 100-250. Contoh kedua memiliki offset 300, yang menerjemahkan alamat virtual 0-150 menjadi alamat fisik 300-450. Ini memungkinkan kedua program untuk mengeksekusi kode yang sama dan menggunakan alamat virtual yang sama tanpa mengganggu satu sama lain.

Keuntungan lain adalah bahwa sekarang program dapat ditempatkan di tempat sewenang-wenang di memori fisik. Dengan demikian, OS menggunakan seluruh jumlah memori yang tersedia tanpa perlu mengkompilasi ulang program.

Fragmentasi


Perbedaan antara alamat virtual dan fisik adalah pencapaian segmentasi yang nyata. Tapi ada masalah. Bayangkan kita ingin menjalankan salinan ketiga dari program yang kita lihat di atas:



Meskipun ada lebih dari cukup ruang dalam memori fisik, salinan ketiga tidak cocok di mana pun. Masalahnya adalah ia membutuhkan fragmen memori yang berkelanjutan dan kami tidak dapat menggunakan bagian bebas yang terpisah.

Salah satu cara untuk memerangi fragmentasi adalah menghentikan sementara eksekusi program, memindahkan bagian memori yang digunakan lebih dekat satu sama lain, memperbarui konversi, dan kemudian melanjutkan eksekusi:



Sekarang ada cukup ruang untuk meluncurkan instance ketiga.

Kerugian dari defragmentasi ini adalah kebutuhan untuk menyalin sejumlah besar memori, yang mengurangi kinerja. Prosedur ini harus dilakukan secara teratur sampai memori menjadi terlalu terfragmentasi. Kinerja menjadi tidak dapat diprediksi, program berhenti kapan saja dan dapat berhenti merespons.

Fragmentasi adalah salah satu alasan mengapa segmentasi tidak digunakan di sebagian besar sistem. Bahkan, itu tidak lagi didukung bahkan dalam mode 64-bit pada x86. Alih-alih segmentasi, halaman digunakan yang sepenuhnya menghilangkan masalah fragmentasi.

Halaman organisasi memori


Idenya adalah untuk membagi ruang memori virtual dan fisik menjadi blok kecil dengan ukuran tetap. Blok memori virtual disebut halaman, dan blok ruang alamat fisik disebut frame. Setiap halaman secara individual dipetakan ke sebuah frame, yang memungkinkan Anda untuk membagi area memori yang besar antara frame fisik yang tidak berdekatan.

Keuntungannya menjadi jelas jika Anda mengulangi contoh dengan ruang memori yang terfragmentasi, tetapi kali ini menggunakan halaman alih-alih segmentasi:



Dalam contoh ini, ukuran halaman adalah 50 byte, yaitu, masing-masing area memori dibagi menjadi tiga halaman. Setiap halaman dipetakan ke frame yang terpisah, sehingga wilayah memori virtual yang berdekatan dapat dipetakan ke frame fisik yang terisolasi. Ini memungkinkan Anda untuk menjalankan instance ketiga program tanpa defragmentasi.

Fragmentasi tersembunyi


Dibandingkan dengan segmentasi, organisasi paging menggunakan banyak area memori berukuran kecil dan bukannya beberapa area berukuran variabel. Setiap frame memiliki ukuran yang sama, jadi fragmentasi karena frame terlalu kecil tidak dimungkinkan.

Tapi ini hanya penampilan . Bahkan, ada bentuk fragmentasi tersembunyi, yang disebut fragmentasi internal karena fakta bahwa tidak setiap area memori persis kelipatan dari ukuran halaman. Bayangkan dalam contoh di atas, program ukuran 101: masih akan membutuhkan tiga halaman ukuran 50, sehingga akan membutuhkan 49 byte lebih dari yang Anda butuhkan. Untuk kejelasan, fragmentasi karena segmentasi disebut fragmentasi eksternal .

Tidak ada yang baik dalam fragmentasi internal, tetapi seringkali itu adalah kejahatan yang lebih rendah daripada fragmentasi eksternal. Memori tambahan masih dikonsumsi, tetapi sekarang Anda tidak perlu men-defragnya, dan volume fragmentasi dapat diprediksi (rata-rata, setengah halaman per area memori).

Tabel Halaman


Kami melihat bahwa masing-masing dari jutaan halaman yang mungkin dipetakan secara individual ke sebuah bingkai. Informasi terjemahan alamat ini perlu disimpan di suatu tempat. Saat melakukan segmentasi, register segmen yang terpisah digunakan untuk setiap area memori aktif, yang tidak mungkin dalam hal halaman, karena ada lebih banyak dari mereka daripada register. Sebagai gantinya, ia menggunakan struktur yang disebut tabel halaman .

Untuk contoh di atas, tabel akan terlihat seperti ini:



Seperti yang Anda lihat, setiap instance dari program memiliki tabel halaman sendiri. Sebuah penunjuk ke tabel aktif saat ini disimpan dalam register khusus CPU. Pada x86 itu disebut CR3 . Sebelum memulai setiap instance program, sistem operasi harus memuat pointer ke tabel halaman yang benar di sana.

Setiap kali memori diakses, CPU membaca pointer tabel dari register dan mencari frame yang sesuai dalam tabel. Ini adalah fungsi perangkat keras sepenuhnya yang berjalan sepenuhnya transparan untuk program yang sedang berjalan. Untuk mempercepat proses, banyak arsitektur prosesor memiliki cache khusus yang mengingat hasil konversi terbaru.

Bergantung pada arsitektur, atribut seperti izin juga dapat disimpan di bidang bendera pada tabel halaman. Pada contoh di atas, bendera r/w membuat halaman dapat dibaca dan ditulis.

Tabel Halaman Layered


Tabel halaman sederhana memiliki masalah dengan ruang alamat yang besar: memori terbuang sia-sia. Misalnya, program ini menggunakan empat halaman virtual 0 , 1_000_000 , 1_000_050 dan 1_000_100 (kami menggunakan _ sebagai pemisah angka):



Hanya empat bingkai fisik yang diperlukan, tetapi ada lebih dari satu juta catatan dalam tabel halaman. Kami tidak dapat melewati entri kosong, karena dengan demikian CPU selama proses konversi tidak akan dapat langsung masuk ke entri yang benar (misalnya, tidak lagi dijamin bahwa halaman keempat menggunakan entri keempat).

Untuk mengurangi kehilangan memori, Anda dapat menggunakan organisasi dua tingkat . Idenya adalah kita menggunakan tabel yang berbeda untuk area yang berbeda. Tabel tambahan, disebut tabel halaman level kedua , mengkonversi antara area alamat dan tabel halaman level pertama.

Ini paling baik dijelaskan dengan contoh. Kami menetapkan bahwa setiap tabel halaman level 1 bertanggung jawab untuk area dengan ukuran 10_000 . Maka dalam contoh di atas tabel berikut akan ada:



Halaman 0 jatuh ke area pertama 10_000 byte, sehingga menggunakan catatan pertama di tabel halaman tingkat kedua. Entri ini menunjuk ke tabel halaman T1 tingkat pertama, yang menentukan bahwa halaman 0 mengacu pada frame 0.

Halaman 1_000_000 , 1_000_050 dan 1_000_100 termasuk dalam wilayah byte ke- 10_000 dari 10_000 , sehingga mereka menggunakan catatan ke-100 tabel tabel level 2. Catatan ini menunjuk ke tabel level pertama lain T2, yang menerjemahkan tiga halaman menjadi frame 100, 150 dan 200. Catatan bahwa alamat halaman dalam tabel di tingkat pertama tidak mengandung offset wilayah, oleh karena itu, misalnya, catatan untuk halaman 1_000_050 hanya 50 .

Kami masih memiliki 100 entri kosong di tabel tingkat kedua, tetapi ini jauh lebih sedikit daripada juta sebelumnya. Alasan penghematan adalah bahwa Anda tidak perlu membuat tabel halaman tingkat pertama untuk area memori yang 10_000 antara 10_000 dan 1_000_000 .

Prinsip tabel dua tingkat dapat diperpanjang hingga tiga, empat tingkat atau lebih. Secara umum, sistem seperti itu disebut tabel halaman bertingkat atau hierarkis .

Mengetahui organisasi halaman dan tabel multi-level, Anda dapat melihat bagaimana organisasi halaman diimplementasikan dalam arsitektur x86_64 (kami mengasumsikan bahwa prosesor berjalan dalam mode 64-bit).

Organisasi Halaman di x86_64


Arsitektur x86_64 menggunakan tabel empat tingkat dengan ukuran halaman 4 KB. Terlepas dari level, setiap tabel halaman memiliki 512 elemen. Setiap record memiliki ukuran 8 byte, sehingga ukuran tabel adalah 512 Γ— 8 byte = 4 KB.



Seperti yang Anda lihat, setiap indeks tabel berisi 9 bit, yang masuk akal, karena tabel memiliki 2 ^ 9 = 512 entri. 12 bit terbawah adalah offset halaman 4-kilobyte (2 ^ 12 byte = 4 KB). Bit 48 hingga 64 dibuang, jadi x86_64 sebenarnya bukan sistem 64-bit, tetapi hanya mendukung alamat 48-bit. Ada rencana untuk memperluas ukuran alamat menjadi 57 bit melalui tabel halaman 5 tingkat , tetapi prosesor seperti itu belum dibuat.

Meskipun bit 48 hingga 64 dibuang, mereka tidak dapat diatur ke nilai arbitrer. Semua bit dalam kisaran ini harus merupakan salinan bit 47 untuk mempertahankan alamat unik dan memungkinkan ekspansi di masa mendatang, misalnya, ke tabel halaman 5-tingkat. Ini disebut ekstensi tanda, karena sangat mirip dengan ekstensi tanda pada kode tambahan . Jika alamat tidak diperluas dengan benar, CPU melempar pengecualian.

Contoh konversi


Mari kita lihat contoh cara kerja terjemahan alamat:



Alamat fisik dari tabel halaman aktif saat ini dari level 4 halaman, yang merupakan tabel root dari halaman halaman level ini, disimpan dalam CR3 . Setiap entri tabel halaman kemudian menunjuk ke bingkai fisik dari tabel level berikutnya. Entri tabel level 1 menunjukkan bingkai yang ditampilkan. Harap perhatikan bahwa semua alamat dalam tabel halaman bersifat fisik dan bukan virtual, karena jika tidak, CPU harus mengubah alamat ini (yang dapat menyebabkan rekursi tak terbatas).

Hirarki di atas mengonversi dua halaman (berwarna biru). Dari indeks, kita dapat menyimpulkan bahwa alamat virtual dari halaman ini adalah 0x803fe7f000 dan 0x803FE00000 . Mari kita lihat apa yang terjadi ketika sebuah program mencoba membaca memori di alamat 0x803FE7F5CE . Pertama, ubah alamat menjadi biner dan tentukan indeks tabel halaman dan offset untuk alamat:



Dengan menggunakan indeks ini, kita sekarang bisa melalui hierarki tabel halaman dan menemukan bingkai yang sesuai:

  • Baca alamat tabel level keempat dari CR3 .
  • Indeks level keempat adalah 1, jadi kami melihat catatan dengan indeks 1 dalam tabel ini. Dia mengatakan tabel level 3 disimpan pada 16 KB.
  • Kami memuat tabel tingkat ketiga dari alamat ini dan melihat catatan dengan indeks 0, yang menunjuk ke tabel tingkat kedua pada 24 KB.
  • Indeks level kedua adalah 511, jadi kami mencari catatan terakhir pada halaman ini untuk mengetahui alamat tabel level pertama.
  • Dari entri dengan indeks 127 pada tabel tingkat pertama, kami akhirnya menemukan bahwa halaman tersebut sesuai dengan bingkai 12 KB atau 0xc000 dalam format heksadesimal.
  • Langkah terakhir adalah menambahkan offset ke alamat bingkai untuk mendapatkan alamat fisik: 0xc000 + 0x5ce = 0xc5ce.



Untuk halaman di tabel level pertama, r bendera ditentukan, yaitu, hanya bacaan yang diizinkan. Pengecualian akan dilemparkan ke tingkat perangkat keras jika kami mencoba merekam di sana. Izin tabel level yang lebih tinggi meluas ke level yang lebih rendah, jadi jika kita mengatur flag read-only pada level ketiga, tidak ada satu halaman berikutnya dari level yang lebih rendah yang dapat ditulisi, bahkan jika ada flag yang memungkinkan penulisan.

Meskipun contoh ini hanya menggunakan satu instance dari setiap tabel, biasanya di setiap ruang alamat ada beberapa contoh dari setiap level. Maksimal:

  • satu meja dari tingkat keempat,
  • 512 tabel dari level ketiga (karena ada 512 catatan di tabel level keempat),
  • 512 * 512 tabel tingkat kedua (karena masing-masing tabel tingkat ketiga memiliki 512 entri), dan
  • 512 * 512 * 512 tabel di tingkat pertama (512 catatan untuk setiap tabel di tingkat kedua).

Format Tabel Halaman


Dalam arsitektur x86_64, tabel halaman pada dasarnya adalah array dari 512 entri. Dalam sintaksis Rust:

 #[repr(align(4096))] pub struct PageTable { entries: [PageTableEntry; 512], } 

Seperti yang ditunjukkan pada atribut repr , tabel harus disejajarkan pada halaman, mis., Pada batas 4 KB. Persyaratan ini memastikan bahwa tabel selalu secara optimal mengisi seluruh halaman, membuat entri menjadi sangat ringkas.

Ukuran setiap catatan adalah 8 byte (64 bit) dan format berikut:

BitJudulNilai
0hadirhalaman dalam memori
1dapat dituliscatatan diizinkan
2pengguna dapat diaksesjika bit tidak diset, maka hanya kernel yang memiliki akses ke halaman
3tuliskan melalui cachingmenulis langsung ke memori
4nonaktifkan cachenonaktifkan cache untuk halaman ini
5diaksesCPU menyetel bit ini saat halaman sedang digunakan.
6kotorCPU menetapkan bit ini saat menulis ke halaman
7halaman besar / nolbit nol di P1 dan P4 menciptakan 1 halaman KB di P3, halaman 2 MB di P2
8globalhalaman tidak diisi dari cache ketika berpindah ruang alamat (bit PGE dari register CR4 harus disetel)
9-11tersediaOS dapat menggunakannya secara bebas
12-51alamat fisikalamat fisik frame-aligned 52-bit dari frame atau tabel halaman berikut
52-62tersediaOS dapat menggunakannya secara bebas
63tidak ada eksekusimelarang eksekusi kode pada halaman ini (bit NXE harus diatur dalam register EFER)

Kita melihat bahwa hanya bit 12-51 yang digunakan untuk menyimpan alamat fisik frame, dan sisanya berfungsi sebagai flag atau dapat digunakan secara bebas oleh sistem operasi. Ini dimungkinkan karena kami selalu menunjuk ke alamat rata-rata 4096-byte, atau ke halaman tabel yang selaras, atau ke awal frame yang sesuai. Ini berarti bahwa bit 0-11 selalu nol, sehingga tidak dapat disimpan, mereka cukup direset ke tingkat perangkat keras sebelum menggunakan alamat. Hal yang sama berlaku untuk bit 52-63, karena arsitektur x86_64 hanya mendukung alamat fisik 52-bit (dan hanya alamat virtual 48-bit).

Mari kita lihat lebih dekat bendera yang tersedia:

  • Bendera ini membedakan halaman yang ditampilkan dari yang tidak ditampilkan. Ini dapat digunakan untuk menyimpan sementara halaman ke disk ketika memori utama penuh. Saat berikutnya halaman diakses, pengecualian PageFault khusus terjadi, di mana OS merespons dengan menukar halaman dari disk - program terus bekerja.
  • Flag yang writable dan no execute menentukan apakah konten halaman dapat ditulisi atau masing-masing berisi instruksi yang dapat dieksekusi.
  • Bendera yang accessed dan dirty secara otomatis diatur oleh prosesor saat membaca atau menulis ke halaman. OS dapat menggunakan informasi ini, misalnya, jika bertukar halaman atau ketika memeriksa untuk melihat apakah isi halaman telah berubah sejak pemompaan terakhir ke disk.
  • Tulisan write through caching dan disable cache flag disable cache memungkinkan Anda mengelola cache untuk setiap halaman secara individual.
  • Bendera yang user accessible membuat halaman dapat diakses untuk kode dari ruang pengguna, jika tidak, hanya tersedia untuk kernel. Fungsi ini dapat digunakan untuk mempercepat panggilan sistem sambil mempertahankan pemetaan alamat untuk kernel saat program pengguna sedang berjalan. Namun, kerentanan Spectre memungkinkan halaman ini dibaca oleh program dari ruang pengguna.
  • global , (. TLB ) (address space switch). user accessible .
  • huge page , 2 3 . 512 : 2 = 512 Γ— 4 , 1 = 512 Γ— 2 . .

Arsitektur x86_64 mendefinisikan format tabel halaman dan rekamannya , jadi kami tidak harus membuat struktur ini sendiri.

Buffer Terjemahan Asosiatif (TLB)


- . x86_64 (TLB). , .

, TLB , . , TLB , . CPU invlpg (invalidate page), TLB, . TLB CR3yang meniru saklar ruang alamat. Kedua opsi tersedia melalui modul tlb di Rust.

Penting untuk diingat untuk membersihkan TLB setelah setiap perubahan tabel halaman, jika tidak CPU akan terus menggunakan terjemahan yang lama, yang akan menyebabkan kesalahan tak terduga yang sangat sulit untuk di-debug.

Implementasi


: . Β« RustΒ» , , 64- x86_64.

, . VGA 0xb8000 , , 0xb8000 0xb8000 .

: , . : ,

(PageFault)


PageFault, . -, IDT, :

 // in src/interrupts.rs lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); […] idt.page_fault.set_handler_fn(page_fault_handler); // new idt }; } use x86_64::structures::idt::PageFaultErrorCode; extern "x86-interrupt" fn page_fault_handler( stack_frame: &mut ExceptionStackFrame, _error_code: PageFaultErrorCode, ) { use crate::hlt_loop; use x86_64::registers::control::Cr2; println!("EXCEPTION: PAGE FAULT"); println!("Accessed Address: {:?}", Cr2::read()); println!("{:#?}", stack_frame); hlt_loop(); } 

CPU CR2 . , . Cr2::read . PageFaultErrorCode , - LLVM , . , , hlt_loop .

:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { use blog_os::interrupts::PICS; println!("Hello World{}", "!"); // set up the IDT first, otherwise we would enter a boot loop instead of // invoking our page fault handler blog_os::gdt::init(); blog_os::interrupts::init_idt(); unsafe { PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); // new let ptr = 0xdeadbeaf as *mut u32; unsafe { *ptr = 42; } println!("It did not crash!"); blog_os::hlt_loop(); } 

Setelah memulai, kita melihat bahwa penangan kesalahan halaman disebut:



Register CR2benar-benar berisi alamat yang 0xdeadbeafingin kita akses.

Pointer instruksi saat ini adalah 0x20430a, jadi kami tahu bahwa alamat ini menunjuk ke halaman kode. Halaman kode ditampilkan oleh loader read-only, jadi membaca dari alamat ini berfungsi, dan penulisan akan menyebabkan kesalahan. Coba ubah pointer 0xdeadbeafke 0x20430a:

 // Note: The actual address might be different for you. Use the address that // your page fault handler reports. let ptr = 0x20430a as *mut u32; // read from a code page -> works unsafe { let x = *ptr; } // write to a code page -> page fault unsafe { *ptr = 42; } 

Jika kami mengomentari baris terakhir, kami dapat memastikan bahwa bacaan berfungsi, dan penulisan menyebabkan kesalahan PageFault.

Akses ke tabel halaman


Sekarang lihat tabel halaman untuk kernel:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { use x86_64::registers::control::Cr3; let (level_4_page_table, _) = Cr3::read(); println!("Level 4 page table at: {:?}", level_4_page_table.start_address()); […] } 

Cr3::read x86_64 CR3 . PhysFrame Cr3Flags . .

:

Level 4 page table at: PhysAddr(0x1000)

, 0x1000 , PhysAddr . : ?

, . , β€” , 0x1000. Ini adalah masalah umum karena kernel harus secara teratur mengakses tabel halaman, misalnya, ketika mengalokasikan tumpukan untuk utas baru.

Solusi untuk masalah ini akan dijelaskan secara rinci di artikel selanjutnya. Untuk saat ini, katakan saja bahwa loader menggunakan metode yang disebut tabel halaman rekursif . Halaman terakhir dari ruang alamat virtual adalah 0xffff_ffff_ffff_f000, kami menggunakannya untuk membaca beberapa entri dalam tabel ini:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { let level_4_table_pointer = 0xffff_ffff_ffff_f000 as *const u64; for i in 0..10 { let entry = unsafe { *level_4_table_pointer.offset(i) }; println!("Entry {}: {:#x}", i, entry); } […] } 

u64 . , 8 (64 ), u64 . for 10 . offset .

:



, 0x2023 0 present , writable , accessed 0x2000 . 1 0x6e2000 , dirty. Entri 2–9 tidak ada, jadi rentang alamat virtual ini tidak dipetakan ke alamat fisik apa pun.

Alih-alih bekerja dengan pointer yang tidak aman secara langsung, Anda dapat menggunakan tipe PageTabledari x86_64:

 // in src/main.rs #[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { use x86_64::structures::paging::PageTable; let level_4_table_ptr = 0xffff_ffff_ffff_f000 as *const PageTable; let level_4_table = unsafe {&*level_4_table_ptr}; for i in 0..10 { println!("Entry {}: {:?}", i, level_4_table[i]); } […] } 

0xffff_ffff_ffff_f000 , Rust. - , , . &PageTable , , .

x86_64 , :



β€” 0 1 3. , 0x2000 0x6e5000 , . .

Ringkasan


Artikel ini menyajikan dua metode melindungi memori: segmentasi dan pengorganisasian halaman. Metode pertama menggunakan area memori berukuran variabel dan menderita fragmentasi eksternal, yang kedua menggunakan halaman berukuran tetap dan memungkinkan kontrol granular lebih besar atas hak akses.

Organisasi halaman menyimpan informasi terjemahan halaman dalam tabel-tabel dari satu level atau lebih. Arsitektur x86_64 menggunakan tabel empat tingkat dengan ukuran halaman 4 KB. Peralatan secara otomatis mem-bypass tabel halaman dan menyimpan hasil konversi dalam buffer terjemahan asosiatif (TLB). Saat mengganti tabel halaman, harus dipaksa untuk membersihkan.

, , PageFault. , , , .

Apa selanjutnya


, . , . , .

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


All Articles