Pada artikel sebelumnya, kami memeriksa dasar-dasar bekerja dalam mode yang dilindungi IA-32. Hari ini saatnya belajar cara bekerja dengan ruang alamat virtual.
Daftar isi
Membangun sistem (make, gcc, gas). Boot awal (multiboot). Luncurkan (qemu). Pustaka C (strcpy, memcpy, strext).
Pustaka C (sprintf, strcpy, strcmp, strtok, va_list ...). Membangun perpustakaan dalam mode kernel dan mode aplikasi pengguna.
Log sistem kernel. Memori video Output ke terminal (kprintf, kpanic, kassert).
Memori dinamis, tumpukan (kmalloc, kfree).
Organisasi memori dan penanganan interupsi (GDT, IDT, PIC, syscall). Pengecualian
Memori virtual (direktori halaman dan tabel halaman).Proses Perencana Multitasking. Panggilan sistem (bunuh, keluar, ps).
Sistem file kernel (initrd), elf, dan internalnya. Panggilan sistem (exec).
Driver perangkat karakter. Panggilan sistem (ioctl, fopen, fread, fwrite). Pustaka C (fopen, fclose, fprintf, fscanf).
Shell sebagai program lengkap untuk kernel.
Mode perlindungan pengguna (ring3). Segmen Status Tugas (tss).
Memori virtual
Memori virtual diperlukan agar setiap proses dapat diisolasi dari yang lain, mis. tidak bisa menghentikannya. Jika tidak ada memori virtual, kita harus memuat file elf di alamat berbeda di memori setiap kali. Tapi seperti yang Anda tahu, file yang dapat dieksekusi dapat berisi tautan ke alamat tertentu (absolut). Karena itu, ketika mengompilasi elf, sudah diketahui alamat apa yang akan dimuat di memori (lihat skrip linker). Karenanya, kami tidak dapat memuat dua file elf tanpa memori virtual. Tetapi bahkan dengan memori virtual dihidupkan, ada perpustakaan dinamis (seperti .so) yang dapat memuat di alamat apa pun. Mereka dapat diunduh di alamat mana pun karena fakta bahwa mereka memiliki bagian relokasi. Di bagian ini, semua tempat di mana pengalamatan absolut digunakan terdaftar, dan kernel, ketika memuat file elf seperti itu, harus memperbaiki alamat ini dengan pena, mis. tambahkan kepada mereka perbedaan antara alamat unduhan yang nyata dan yang diinginkan.
Kami akan mempertimbangkan bekerja dengan 4 halaman kilobyte. Dalam situasi ini, kita dapat mengatasi RAM hingga 4 megabita. Cukup bagi kami. Peta alamat akan terlihat seperti ini:
0-1 mb : jangan disentuh.
1-2 mb : kode dan data kernel.
2-3 mb : banyak kernel.
3-4 mb : halaman khusus file elf diunggah.
Alamat linear (diperoleh dari model datar) saat paging halaman diaktifkan tidak sama dengan yang fisik. Sebaliknya, alamat dibagi dengan offset (bit rendah), indeks entri dalam tabel halaman, dan indeks direktori halaman (bit tinggi). Setiap proses akan memiliki direktori halaman sendiri dan, karenanya, tabel halaman. Inilah yang memungkinkan Anda untuk mengatur ruang alamat virtual.
Entri direktori halaman terlihat seperti ini:
struct page_directory_entry_t { u8 present : 1; u8 read_write : 1; u8 user_supervisor : 1; u8 write_through : 1; u8 cache_disabled : 1; u8 accessed : 1; u8 zero : 1; u8 page_size : 1; u8 ignored : 1; u8 available : 3; u32 page_table_addr : 20; } attribute(packed);
Elemen tabel halaman terlihat seperti ini:
struct page_table_entry_t { u8 present : 1; u8 read_write : 1; u8 user_supervisor : 1; u8 write_through : 1; u8 cache_disabled : 1; u8 accessed : 1; u8 dirty : 1; u8 zero : 1; u8 global : 1; u8 available : 3; u32 page_phys_addr : 20; } attribute(packed);
Untuk kernel, kami akan menggambarkan direktori halaman dan tabel halaman sebagai variabel statis. Benar, ada persyaratan bahwa mereka harus diluruskan di perbatasan halaman.
static struct page_directory_entry_t kpage_directory attribute(aligned(4096)); static struct page_table_entry_t kpage_table[MMU_PAGE_TABLE_ENTRIES_COUNT] attribute(aligned(4096));
Kami akan pergi dengan cara sederhana dan membuat seluruh ruang alamat fisik dapat diakses oleh kernel. Level privilege dari halaman kernel haruslah seorang supervisor sehingga tidak ada yang naik ke dalamnya. Proses ringan yang harus dijalankan dalam mode kernel akan berbagi ruang alamat yang sama dengan kernel. Dengan proses ini, kita akan memiliki antrian eksekusi yang tertunda untuk menangani interupsi yang tertunda. Tetapi lebih banyak tentang itu dalam pelajaran tentang driver perangkat karakter. Kami belum menyadari multitasking sebelum mempertimbangkan topik ini.
Buat direktori halaman kernel dan tabel halaman yang sesuai. Ketika kernel diinisialisasi, itu akan aktif.
extern void mmu_init() { memset(&kpage_directory, 0, sizeof(struct page_directory_entry_t)); kpage_directory.zero = 1; kpage_directory.accessed = 0; kpage_directory.available = 0; kpage_directory.cache_disabled = 0; kpage_directory.ignored = 0; kpage_directory.page_size = 0; kpage_directory.present = 1; kpage_directory.read_write = 1; kpage_directory.user_supervisor = 1; kpage_directory.write_through = 1; kpage_directory.page_table_addr = (size_t)kpage_table >> 12; for (int i = 0; i < MMU_PAGE_TABLE_ENTRIES_COUNT; ++i) { kpage_table[i].zero = 0; kpage_table[i].accessed = 0; kpage_table[i].available = 0; kpage_table[i].cache_disabled = 0; kpage_table[i].dirty = 0; kpage_table[i].global = 1; kpage_table[i].present = 1; kpage_table[i].read_write = 1; kpage_table[i].user_supervisor = 1; kpage_table[i].write_through = 1; kpage_table[i].page_phys_addr = (i * 4096) >> 12; } }
Saat kami mengunggah file elf, kami perlu membuat direktori halaman untuk proses pengguna. Anda dapat melakukan ini dengan fungsi berikut:
extern struct page_directory_entry_t* mmu_create_user_page_directory(struct page_table_entry_t* page_table) { struct page_directory_entry_t* upage_dir; upage_dir = malloc_a(sizeof(struct page_directory_entry_t), 4096); upage_dir->zero = 1; upage_dir->accessed = 0; upage_dir->available = 0; upage_dir->cache_disabled = 0; upage_dir->ignored = 0; upage_dir->page_size = 0; upage_dir->present = 1; upage_dir->read_write = 1; upage_dir->user_supervisor = 0; upage_dir->write_through = 1; upage_dir->page_table_addr = (size_t)page_table >> 12; return upage_dir; }
Secara default, tabel halaman proses akan berisi halaman kernel dan entri kosong untuk halaman proses selanjutnya, mis. catatan dengan bendera yang sekarang dihapus dan alamat halaman fisik di 0.
extern struct page_table_entry_t* mmu_create_user_page_table() { struct page_table_entry_t* upage_table; upage_table = malloc_a(sizeof(struct page_table_entry_t) * MMU_PAGE_TABLE_ENTRIES_COUNT, 4096); memcpy(upage_table, kpage_table, sizeof(struct page_table_entry_t) * MMU_KERNEL_PAGES_COUNT); for (int i = MMU_KERNEL_PAGES_COUNT; i < MMU_PAGE_TABLE_ENTRIES_COUNT; ++i) { struct page_table_entry_t* current; current = upage_table + i; current->zero = 0; current->accessed = 0; current->available = 0; current->cache_disabled = 0; current->dirty = 0; current->global = 1; current->present = 0; current->read_write = 1; current->user_supervisor = 0; current->write_through = 1; current->page_phys_addr = 0; } return upage_table; }
Kita perlu belajar cara menambahkan halaman fisik baru ke tabel halaman proses, karena tidak akan ada secara default. Kita akan membutuhkan ini ketika memuat file elf ke dalam memori, ketika kita memuat segmen yang dijelaskan dalam header program. Fungsi ini akan membantu kami dalam hal ini:
extern bool mmu_occupy_user_page(struct page_table_entry_t* upage_table, void* phys_addr) { for (int i = MMU_KERNEL_PAGES_COUNT; i < MMU_PAGE_TABLE_ENTRIES_COUNT; ++i) { struct page_table_entry_t* current; current = upage_table + i; if (current->present) { continue; } current->zero = 0; current->accessed = 0; current->available = 0; current->cache_disabled = 0; current->dirty = 0; current->global = 1; current->present = 1; current->read_write = 1; current->user_supervisor = 0; current->write_through = 1; current->page_phys_addr = (size_t)phys_addr >> 12; return true; } return false; }
Mode paging dihidupkan dan dimatikan sedikit dalam register flag prosesor.
/* * Enable paging * void asm_enable_paging(void *page_directory) */ asm_enable_paging: mov 4(%esp),%eax # page_directory mov %eax,%cr3 mov %cr0,%eax or $0x80000001,%eax # set PE & PG bits mov %eax,%cr0 ret /* * Disable paging * void asm_disable_paging() */ asm_disable_paging: mov %eax,%cr3 mov %cr0,%eax xor $0x80000000,%eax # unset PG bit mov %eax,%cr0 ret
Setelah kita belajar cara membuat ruang alamat proses, kita perlu mengelola halaman fisik, yang mana sibuk dan mana yang bebas. Ada mekanisme bitmap untuk ini, satu bit per halaman. Kami tidak akan menjelaskan halaman hingga 3 megabyte, karena mereka milik kernel dan selalu sibuk. Kami mulai memilih halaman pengguna dari 3 hingga 4 megabita.
static u32 bitmap[MM_BITMAP_SIZE];
Halaman fisik dialokasikan dan dialokasikan menurut fungsi-fungsi berikut. Bahkan, kami cukup menemukan bit yang diinginkan di peta di alamat fisik halaman dan sebaliknya. Ketidaknyamanannya adalah kami memiliki ukuran sel memori terbatas, jadi Anda harus menggunakan dua koordinat: nomor byte dan nomor bit.
extern void* mm_phys_alloc_pages(u_int count) { for (int i = 0; i < MM_DYNAMIC_PAGES_COUNT; ++i) { bool is_found = true; for (int j = 0; j < count; ++j) { is_found = is_found && !mm_get_bit(i + j); } if (is_found) { for (int j = 0; j < count; ++j) { assert(!mm_get_bit(i + j)); mm_set_bit(i + j); } return (void *)mm_get_addr(i); } } return null; } extern bool mm_phys_free_pages(void* ptr, u_int count) { size_t address = (size_t)ptr; assert(address >= MM_AREA_START); assert(address % MM_PAGE_SIZE == 0); for (int i = 0; i < MM_DYNAMIC_PAGES_COUNT; ++i) { size_t addr = mm_get_addr(i); if (addr == address) { for (int j = 0; j < count; ++j) { assert(mm_get_bit(i + j)); mm_clear_bit(i + j); } return true; } } return false; }
Ini cukup untuk memperkenalkan dukungan penuh untuk memori virtual di kernel Anda.
Referensi
Detail dan penjelasan dalam
tutorial video .
Kode sumber
di repositori git (Anda memerlukan cabang lesson6).
Referensi
1. James Molloy. Gulung mainan Anda sendiri UNIX-clone OS.
2. Gigi. Assembler untuk DOS, Windows, Unix
3. Kalashnikov. Assembler mudah!
4. Tanenbaum. Sistem operasi. Implementasi dan pengembangan.
5. Robert Love. Kernel Linux Deskripsi proses pengembangan.