Desain OS seperti Unix - Ruang Alamat Virtual (6)

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.

 /* * Api - init kernel page directory * Here assumed each entry addresses 4Kb */ extern void mmu_init() { memset(&kpage_directory, 0, sizeof(struct page_directory_entry_t)); /* set kernel page directory */ 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; /* 4KB */ kpage_directory.present = 1; /* kernel pages always in memory */ kpage_directory.read_write = 1; /* read & write */ kpage_directory.user_supervisor = 1; /* kernel mode pages */ kpage_directory.write_through = 1; kpage_directory.page_table_addr = (size_t)kpage_table >> 12; /* set kernel table */ 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; /* kernel pages always in memory */ kpage_table[i].read_write = 1; /* read & write */ kpage_table[i].user_supervisor = 1; /* kernel mode pages */ kpage_table[i].write_through = 1; kpage_table[i].page_phys_addr = (i * 4096) >> 12; /* assume 4Kb pages */ } } 

Saat kami mengunggah file elf, kami perlu membuat direktori halaman untuk proses pengguna. Anda dapat melakukan ini dengan fungsi berikut:

 /* * Api - Create user page directory */ 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; /* 4KB */ upage_dir->present = 1; upage_dir->read_write = 1; /* read & write */ upage_dir->user_supervisor = 0; /* user mode pages */ upage_dir->write_through = 1; upage_dir->page_table_addr = (size_t)page_table >> 12; /* assume 4Kb pages */ 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.

 /* * Api - Create user page table */ 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); /* share kernel pages */ memcpy(upage_table, kpage_table, sizeof(struct page_table_entry_t) * MMU_KERNEL_PAGES_COUNT); /* fill user pages */ 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; /* not present as so as there is no user pages yet */ current->read_write = 1; /* read & write */ current->user_supervisor = 0; /* user mode page */ current->write_through = 1; current->page_phys_addr = 0; /* page is not present */ } 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:

 /* * Api - Occupy user page */ 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) { /* page is buzy */ 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; /* read & write */ current->user_supervisor = 0; /* user mode page */ current->write_through = 1; current->page_phys_addr = (size_t)phys_addr >> 12; /* assume 4Kb pages */ 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.

 /* * Api - allocate pages */ extern void* mm_phys_alloc_pages(u_int count) { /* find free pages */ 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) { /* occupy */ 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; } /* * Api - free page */ 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); /* find page */ for (int i = 0; i < MM_DYNAMIC_PAGES_COUNT; ++i) { size_t addr = mm_get_addr(i); if (addr == address) { /* free pages */ 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.

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


All Articles