OS1: un noyau primitif sur Rust pour x86. Partie 3. Carte mémoire, exception de défaut de page, tas et allocations

Première partie
Deuxième partie


Le sujet de la conversation d'aujourd'hui est de travailler avec la mémoire. Je vais parler de l'initialisation du répertoire des pages, du mappage de la mémoire physique, de la gestion du virtuel et de mon tas d'organisation pour l'allocateur.


Comme je l'ai dit dans le premier article, j'ai décidé d'utiliser des pages de 4 Mo pour me simplifier la vie et ne pas avoir à gérer de tableaux hiérarchiques. À l'avenir, j'espère aller sur des pages de 4 Ko, comme la plupart des systèmes modernes. Je pourrais en utiliser un prêt à l'emploi (par exemple, un tel allocateur de blocs ), mais écrire le mien était un peu plus intéressant et je voulais comprendre un peu plus comment vit la mémoire, alors j'ai quelque chose à vous dire.


La dernière fois que j'ai opté pour la méthode setup_pd dépendante de l'architecture et que je voulais continuer, il y avait un autre détail que je n'avais pas couvert dans l'article précédent - la sortie VGA utilisant Rust et la macro println standard. Étant donné que son implémentation est triviale, je vais le supprimer sous le spoiler. Le code est dans le package de débogage.


Impression macro
#[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::debug::_print(format_args!($($arg)*))); } #[macro_export] macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } #[cfg(target_arch = "x86")] pub fn _print(args: core::fmt::Arguments) { use core::fmt::Write; use super::arch::vga; vga::VGA_WRITER.lock().write_fmt(args).unwrap(); } #[cfg(target_arch = "x86_64")] pub fn _print(args: core::fmt::Arguments) { use core::fmt::Write; use super::arch::vga; // vga::VGA_WRITER.lock().write_fmt(args).unwrap(); } 

Maintenant, avec une conscience claire, je reviens à la mémoire.


Initialisation du répertoire de pages


Notre méthode kmain a pris trois arguments en entrée, dont l'un est l'adresse virtuelle de la table des pages. Pour l'utiliser ultérieurement pour l'allocation et la gestion de la mémoire, vous devez désigner la structure des enregistrements et des répertoires. Pour x86, le répertoire Page et la table Page sont assez bien décrits, je vais donc me limiter à une petite introduction. L'entrée du répertoire Page est une structure de taille de pointeur, pour nous, elle est de 4 octets. La valeur contient une adresse physique de 4 Ko de la page. L'octet le moins significatif de l'enregistrement est réservé aux drapeaux. Le mécanisme de conversion d'une adresse virtuelle en adresse physique ressemble à ceci (dans le cas de ma granularité de 4 Mo, le décalage se produit sur 22 bits. Pour les autres granularités, le décalage sera différent et des tableaux hiérarchiques seront utilisés!):


Adresse virtuelle 0xC010A110 -> Obtenez l'index dans le répertoire en déplaçant l'adresse 22 bits vers la droite -> index 0x300 -> Obtenez l'adresse physique de la page par l'index 0x300, vérifiez les drapeaux et l'état -> 0x1000000 -> Prenez les 22 bits inférieurs de l'adresse virtuelle comme décalage, ajoutez à l'adresse physique de la page -> 0x1000000 + 0x10A110 = adresse physique en mémoire 0x110A110

Pour accélérer l'accès, le processeur utilise TLB - tampon de traduction de cache, qui met en cache les adresses de page.


Alors, voici comment mon répertoire et ses entrées sont décrits, et la méthode très setup_pd est implémentée. Pour écrire une page, la méthode «constructeur» est implémentée, qui garantit l'alignement de 4 Ko et la définition des drapeaux, ainsi qu'une méthode pour obtenir l'adresse physique de la page. Un répertoire n'est qu'un tableau de 1024 entrées de quatre octets. Le répertoire peut associer une adresse virtuelle à une page à l'aide de la méthode set_by_addr.


 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PDirectoryEntry(u32); impl PDirectoryEntry { pub fn by_phys_address(address: usize, flags: PDEntryFlags) -> Self { PDirectoryEntry((address as u32) & ADDRESS_MASK | flags.bits()) } pub fn flags(&self) -> PDEntryFlags { PDEntryFlags::from_bits_truncate(self.0) } pub fn phys_address(&self) -> u32 { self.0 & ADDRESS_MASK } pub fn dbg(&self) -> u32 { self.0 } } pub struct PDirectory { entries: [PDirectoryEntry; 1024] } impl PDirectory { pub fn at(&self, idx: usize) -> PDirectoryEntry { self.entries[idx] } pub fn set_by_addr(&mut self, logical_addr: usize, entry: PDirectoryEntry) { self.set(PDirectory::to_idx(logical_addr), entry); } pub fn set(&mut self, idx: usize, entry: PDirectoryEntry) { self.entries[idx] = entry; unsafe { invalidate_page(idx); } } pub fn to_logical_addr(idx: usize) -> usize { (idx << 22) } pub fn to_idx(logical_addr: usize) -> usize { (logical_addr >> 22) } } use lazy_static::lazy_static; use spin::Mutex; lazy_static! { static ref PAGE_DIRECTORY: Mutex<&'static mut PDirectory> = Mutex::new( unsafe { &mut *(0xC0000000 as *mut PDirectory) } ); } pub unsafe fn setup_pd(pd: usize) { let mut data = PAGE_DIRECTORY.lock(); *data = &mut *(pd as *mut PDirectory); } 

J'ai très maladroitement fait de l'initialisation statique initiale une adresse inexistante, donc je vous serais reconnaissant de m'écrire comment il est habituel dans la communauté Rust de faire de telles initialisations avec une réaffectation de liens.


Maintenant que nous pouvons gérer les pages à partir de code de haut niveau, nous pouvons passer à la compilation de l'initialisation de la mémoire. Cela se fera en deux étapes: par le traitement de la carte mémoire physique et l'initialisation du gestionnaire virtuel


  match mb_magic { 0x2BADB002 => { println!("multibooted v1, yeah, reading mb info"); boot::init_with_mb1(mb_pointer); }, . . . . . . } memory::init(); 

Carte mémoire GRUB et carte mémoire physique OS1


Afin d'obtenir une carte mémoire de GRUB, au démarrage, j'ai mis le drapeau correspondant dans l'en-tête, et GRUB m'a donné l'adresse physique de la structure. Je l'ai porté de la documentation officielle à la notation Rust, et j'ai également ajouté des méthodes pour itérer confortablement sur la carte mémoire. La majeure partie de la structure GRUB ne sera pas remplie, et à ce stade, elle n'est pas très intéressante pour moi. L'essentiel est que je ne souhaite pas déterminer manuellement la quantité de mémoire disponible.


Lors de l'initialisation via Multiboot, nous convertissons d'abord l'adresse physique en virtuelle. En théorie, GRUB peut positionner la structure n'importe où, donc si l'adresse s'étend au-delà de la page, vous devez allouer une page virtuelle dans le répertoire Page. En pratique, la structure se situe presque toujours à côté du premier mégaoctet, que nous avons déjà alloué au démarrage. Au cas où, nous vérifions l'indicateur de présence de la carte mémoire et procédons à son analyse.


 pub mod multiboot2; pub mod multiboot; use super::arch; unsafe fn process_pointer(mb_pointer: usize) -> usize { //if in first 4 MB - map to kernel address space if mb_pointer < 0x400000 { arch::KERNEL_BASE | mb_pointer } else { arch::paging::allocate_page(mb_pointer, arch::MB_INFO_BASE, arch::paging::PDEntryFlags::PRESENT | arch::paging::PDEntryFlags::WRITABLE | arch::paging::PDEntryFlags::HUGE_PAGE ); arch::MB_INFO_BASE | mb_pointer } } pub fn init_with_mb1(mb_pointer: usize) { let ln_pointer = unsafe { process_pointer(mb_pointer) }; println!("mb pointer 0x{:X}", ln_pointer); let mb_info = multiboot::from_ptr(ln_pointer); println!("mb flags: {:?}", mb_info.flags().unwrap()); if mb_info.flags().unwrap().contains(multiboot::MBInfoFlags::MEM_MAP) { multiboot::parse_mmap(mb_info); println!("Multiboot memory map parsed, physical memory map has been built"); } else { panic!("MB mmap is not presented"); } } 

Une carte mémoire est une liste liée pour laquelle l'adresse physique initiale est spécifiée dans la structure de base (n'oubliez pas de tout traduire en virtuelle) et la taille du tableau en octets. Vous devez parcourir la liste en fonction de la taille de chaque élément, car en théorie, leurs tailles peuvent différer. Voici à quoi ressemble l'itération:


 impl MultibootInfo { . . . . . . pub unsafe fn get_mmap(&self, index: usize) -> Option<*const MemMapEntry> { use crate::arch::get_mb_pointer_base; let base: usize = get_mb_pointer_base(self.mmap_addr as usize); let mut iter: *const MemMapEntry = (base as u32 + self.mmap_addr) as *const MemMapEntry; for _i in 0..index { iter = ((iter as usize) + ((*iter).size as usize) + 4) as *const MemMapEntry; if ((iter as usize) - base) >= (self.mmap_addr + self.mmap_lenght) as usize { return None } else {} } Some(iter) } } 

Lors de l'analyse d'une carte mémoire, nous parcourons la structure GRUB et la convertissons en bitmap, avec lequel OS1 travaillera pour gérer la mémoire physique. J'ai décidé de me limiter à un petit ensemble de valeurs disponibles pour le contrôle - libre, occupé, réservé, indisponible, bien que GRUB et BIOS fournissent plus d'options. Ainsi, nous parcourons les entrées de la carte et convertissons leur état des valeurs GRUB / BIOS en valeurs pour OS1:


 pub fn parse_mmap(mbi: &MultibootInfo) { unsafe { let mut mmap_opt = mbi.get_mmap(0); let mut i: usize = 1; loop { let mmap = mmap_opt.unwrap(); crate::memory::physical::map((*mmap).addr as usize, (*mmap).len as usize, translate_multiboot_mem_to_os1(&(*mmap).mtype)); mmap_opt = mbi.get_mmap(i); match mmap_opt { None => break, _ => i += 1, } } } } pub fn translate_multiboot_mem_to_os1(mtype: &u32) -> usize { use crate::memory::physical::{RESERVED, UNUSABLE, USABLE}; match mtype { &MULTIBOOT_MEMORY_AVAILABLE => USABLE, &MULTIBOOT_MEMORY_RESERVED => UNUSABLE, &MULTIBOOT_MEMORY_ACPI_RECLAIMABLE => RESERVED, &MULTIBOOT_MEMORY_NVS => UNUSABLE, &MULTIBOOT_MEMORY_BADRAM => UNUSABLE, _ => UNUSABLE } } 

La mémoire physique est gérée dans le module memory :: physical, pour lequel nous appelons la méthode map ci-dessus, en lui passant l'adresse de la région, sa longueur et son état. Les 4 Go de mémoire potentiellement disponibles pour le système et divisés en quatre pages mégaoctets sont représentés par deux bits dans une image bitmap, ce qui vous permet de stocker 4 états pour 1024 pages. Au total, cette construction prend 256 octets. Un bitmap entraîne une terrible fragmentation de la mémoire, mais il est compréhensible et facile à implémenter, ce qui est la principale chose à faire.


Je vais supprimer l'implémentation bitmap sous le spoiler afin de ne pas encombrer l'article. La structure est capable de compter le nombre de classes et de mémoire libre, de marquer les pages par index et adresse, et également de rechercher des pages libres (cela sera nécessaire à l'avenir pour implémenter le tas). La carte elle-même est un tableau de 64 éléments u32, pour isoler les deux bits (blocs) nécessaires, la conversion en soi-disant bloc (index dans le tableau, emballage de 16 blocs) et bloc (position du bit dans le bloc) est utilisée.


Bitmap de mémoire physique
 pub const USABLE: usize = 0; pub const USED: usize = 1; pub const RESERVED: usize = 2; pub const UNUSABLE: usize = 3; pub const DEAD: usize = 0xDEAD; struct PhysMemoryInfo { pub total: usize, used: usize, reserved: usize, chunks: [u32; 64], } impl PhysMemoryInfo { // returns (chunk, page) pub fn find_free(&self) -> (usize, usize) { for chunk in 0..64 { for page in 0.. 16 { if ((self.chunks[chunk] >> page * 2) & 3) ^ 3 == 3 { return (chunk, page) } else {} } } (DEAD, 0) } // marks page to given flag and returns its address pub fn mark(&mut self, chunk: usize, block: usize, flag: usize) -> usize { self.chunks[chunk] = self.chunks[chunk] ^ (3 << (block * 2)); let mask = (0xFFFFFFFC ^ flag).rotate_left(block as u32 * 2); self.chunks[chunk] = self.chunks[chunk] & (mask as u32); if flag == USED { self.used += 1; } else if flag == UNUSABLE || flag == RESERVED { self.reserved += 1; } else { if self.used > 0 { self.used -= 1; } } (chunk * 16 + block) << 22 } pub fn mark_by_addr(&mut self, addr: usize, flag: usize) { let block_num = addr >> 22; let chunk: usize = (block_num / 16) as usize; let block: usize = block_num - chunk * 16; self.mark(chunk, block, flag); } pub fn count_total(& mut self) { let mut count: usize = 0; for i in 0..64 { let mut chunk = self.chunks[i]; for _j in 0..16 { if chunk & 0b11 != 0b11 { count += 1; } chunk = chunk >> 2; } } self.total = count; } pub fn get_total(&self) -> usize { self.total } pub fn get_used(&self) -> usize { self.used } pub fn get_reserved(&self) -> usize { self.reserved } pub fn get_free(&self) -> usize { self.total - self.used - self.reserved } } 

Et maintenant, nous sommes arrivés à l'analyse d'un élément de la carte. Si un élément de carte décrit une zone mémoire inférieure à une page de 4 Mo ou égale à celle-ci, nous marquons cette page dans son ensemble. Si plus - battez en morceaux de 4 Mo et marquez chaque morceau séparément par récursivité. Au stade de l'initialisation du bitmap, nous considérons toutes les sections de mémoire inaccessibles, de sorte que lorsque la carte s'épuise, par exemple à 128 Mo, les sections restantes sont marquées comme inaccessibles.


 use lazy_static::lazy_static; use spin::Mutex; lazy_static! { static ref RAM_INFO: Mutex<PhysMemoryInfo> = Mutex::new(PhysMemoryInfo { total: 0, used: 0, reserved: 0, chunks: [0xFFFFFFFF; 64] }); } pub fn map(addr: usize, len: usize, flag: usize) { // if len <= 4MiB then mark whole page with flag if len <= 4 * 1024 * 1024 { RAM_INFO.lock().mark_by_addr(addr, flag); } else { let pages: usize = len >> 22; for map_page in 0..(pages - 1) { map(addr + map_page << 22, 4 * 1024 * 1024, flag); } map(addr + (pages << 22), len - (pages << 22), flag); } } 

Tas et gestion d'elle


La gestion de la mémoire virtuelle est actuellement limitée à la gestion de tas, car le noyau n'en sait pas beaucoup plus. À l'avenir, bien sûr, il sera nécessaire de gérer toute la mémoire, et ce petit gestionnaire sera réécrit. Cependant, pour le moment, tout ce dont j'ai besoin est de la mémoire statique, qui contient le code exécutable et la pile, et de la mémoire de tas dynamique, où j'allouerai les structures pour le multithreading. Nous allouons de la mémoire statique au démarrage (et jusqu'à présent, nous avons limité 4 Mo, car le noyau y tient) et en général, il n'y a plus de problème maintenant. De plus, à ce stade, je n'ai pas d'appareils DMA, donc tout est extrêmement simple, mais compréhensible.


J'ai donné 512 Mo de l'espace mémoire du noyau le plus élevé (0xE0000000) au tas, je stocke la carte d'utilisation du tas (0xDFC00000) 4 Mo plus bas. J'utilise un bitmap pour décrire l'état, tout comme pour la mémoire physique, mais il n'y a que 2 états - occupé / libre. La taille du bloc de mémoire est de 64 octets - c'est beaucoup pour de petites variables comme u32, u8, mais, peut-être, c'est optimal pour stocker des structures de données. Pourtant, il est peu probable que nous ayons besoin de stocker des variables uniques sur le tas, maintenant son objectif principal est de stocker des structures de contexte pour le multitâche.


Les blocs de 64 octets sont regroupés en structures qui décrivent l'état d'une page entière de 4 Mo, afin que nous puissions allouer des quantités de mémoire petites et grandes à plusieurs pages. J'utilise les termes suivants: bloc - 64 octets, pack - 2 Ko (un u32 - 64 octets * 32 bits par package), page - 4 Mo.


 #[repr(packed)] #[derive(Copy, Clone)] struct HeapPageInfo { //every bit represents 64 bytes chunk of memory. 0 is free, 1 is busy //u32 size is 4 bytes, so page information total size is 8KiB pub _4mb_by_64b: [u32; 2048], } #[repr(packed)] #[derive(Copy, Clone)] struct HeapInfo { //Here we can know state of any 64 bit chunk in any of 128 4-MiB pages //Page information total size is 8KiB, so information about 128 pages requires 1MiB reserved data pub _512mb_by_4mb: [HeapPageInfo; 128], } 

Lorsque je demande de la mémoire à un allocateur, je considère trois cas, selon la granularité:


  • Une demande de mĂ©moire infĂ©rieure Ă  2 Ko est venue de l'allocateur. Vous devez trouver un pack dans lequel il sera libre [taille / 64, tout reste diffĂ©rent de zĂ©ro ajoute un] morceaux consĂ©cutifs, marquez ces morceaux comme occupĂ©s, renvoyez l'adresse du premier morceau.
  • Une demande est venue de l'allocateur pour une mĂ©moire infĂ©rieure Ă  4 Mo, mais supĂ©rieure Ă  2 Ko. Vous devez trouver une page qui a gratuitement [taille / 2048, tout reste diffĂ©rent de zĂ©ro ajoute un] packs d'affilĂ©e. Marquez les packs [taille / 2048] comme occupĂ©s; s'il y a un reste, marquez les morceaux [restants] dans le dernier pack comme occupĂ©s.
  • Une demande de mĂ©moire de plus de 4 Mo est venue de l'allocateur. Recherchez [taille / 4 Mi, tout solde diffĂ©rent de zĂ©ro ajoute une] pages de suite, marquez [taille / 4 Mi] pages comme occupĂ©es, s'il y a un solde - marquez [packs] comme Ă©tant occupĂ©. Dans le dernier pack, marquez le reste des morceaux comme occupĂ©.

La recherche de zones libres dépend également de la granularité - un tableau est sélectionné pour l'itération ou les masques de bits. Chaque fois que vous partez à l'étranger, OOM arrive. Lors de la désallocation, un algorithme similaire est utilisé, uniquement pour le marquage libéré. La mémoire libérée n'est pas réinitialisée. Tout le code est gros, je vais le mettre sous le spoiler.


Bitmap de mémoire virtuelle
 //512 MiB should be enough for kernel heap. If not - ooops... pub const KHEAP_START: usize = 0xE0000000; //I will keep 1MiB info about my heap in separate 4MiB page before heap at this point pub const KHEAP_INFO_ADDR: usize = 0xDFC00000; pub const KHEAP_CHUNK_SIZE: usize = 64; pub fn init() { KHEAP_INFO.lock().init(); } #[repr(packed)] #[derive(Copy, Clone)] struct HeapPageInfo { //every bit represents 64 bytes chunk of memory. 0 is free, 1 is busy //u32 size is 4 bytes, so page information total size is 8KiB pub _4mb_by_64b: [u32; 2048], } impl HeapPageInfo { pub fn init(&mut self) { for i in 0..2048 { self._4mb_by_64b[i] = 0; } } pub fn mark_chunks_used(&mut self, _32pack: usize, chunk: usize, n: usize) { let mask: u32 = 0xFFFFFFFF >> (32 - n) << chunk; self._4mb_by_64b[_32pack] = self._4mb_by_64b[_32pack] | mask; } pub fn mark_chunks_free(&mut self, _32pack: usize, chunk: usize, n: usize) { let mask: u32 = 0xFFFFFFFF >> (32 - n) << chunk; self._4mb_by_64b[_32pack] = self._4mb_by_64b[_32pack] ^ mask; } pub fn empty(&self) -> bool { for i in 0..2048 { if self._4mb_by_64b[i] != 0 { return false } } true } } #[repr(packed)] #[derive(Copy, Clone)] struct HeapInfo { //Here we can know state of any 64 bit chunk in any of 128 4-MiB pages //Page information total size is 8KiB, so information about 128 pages requires 1MiB reserved data pub _512mb_by_4mb: [HeapPageInfo; 128], } impl HeapInfo { pub fn init(&mut self) { for i in 0..128 { self._512mb_by_4mb[i].init(); } } // returns page number pub fn find_free_pages_of_size(&self, n: usize) -> usize { if n >= 128 { 0xFFFFFFFF } else { let mut start_page: usize = 0xFFFFFFFF; let mut current_page: usize = 0xFFFFFFFF; for page in 0..128 { if self._512mb_by_4mb[page].empty() { if current_page - start_page == n { return start_page } if start_page == 0xFFFFFFFF { start_page = page; } current_page = page; } else { start_page = 0xFFFFFFFF; current_page = 0xFFFFFFFF; } } 0xFFFFFFFF } } // returns (page number, 32pack number) pub fn find_free_packs_of_size(&self, n: usize) -> (usize, usize) { if n < 2048 { for page in 0..128 { let mut start_pack: usize = 0xFFFFFFFF; let mut current_pack: usize = 0xFFFFFFFF; for _32pack in 0..2048 { let _32pack_info = self._512mb_by_4mb[page]._4mb_by_64b[_32pack]; if _32pack_info == 0 { if current_pack - start_pack == n { return (page, start_pack) } if start_pack == 0xFFFFFFFF { start_pack = _32pack; } current_pack = _32pack; } else { start_pack = 0xFFFFFFFF; current_pack = 0xFFFFFFFF; } } } (0xFFFFFFFF, 0xFFFFFFFF) } else { (0xFFFFFFFF, 0xFFFFFFFF) } } // returns (page number, 32pack number, chunk number) pub fn find_free_chunks_of_size(&self, n: usize) -> (usize, usize, usize) { if n < 32 { for page in 0..128 { for _32pack in 0..2048 { let _32pack_info = self._512mb_by_4mb[page]._4mb_by_64b[_32pack]; let mask: u32 = 0xFFFFFFFF >> (32 - n); for chunk in 0..(32-n) { if ((_32pack_info >> chunk) & mask) ^ mask == mask { return (page, _32pack, chunk) } } } } (0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF) } else { (0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF) } } fn mark_chunks_used(&mut self, page: usize, _32pack: usize, chunk: usize, n: usize) { self._512mb_by_4mb[page].mark_chunks_used(_32pack, chunk, n); } fn mark_chunks_free(&mut self, page: usize, _32pack: usize, chunk: usize, n: usize) { self._512mb_by_4mb[page].mark_chunks_free(_32pack, chunk, n); } fn mark_packs_used(&mut self, page: usize, _32pack:usize, n: usize) { for i in _32pack..(_32pack + n) { self._512mb_by_4mb[page]._4mb_by_64b[i] = 0xFFFFFFFF; } } fn mark_packs_free(&mut self, page: usize, _32pack:usize, n: usize) { for i in _32pack..(_32pack + n) { self._512mb_by_4mb[page]._4mb_by_64b[i] = 0; } } } use lazy_static::lazy_static; use spin::Mutex; lazy_static! { static ref KHEAP_INFO: Mutex<&'static mut HeapInfo> = Mutex::new(unsafe { &mut *(KHEAP_INFO_ADDR as *mut HeapInfo) }); } fn allocate_n_chunks_less_than_pack(n: usize, align: usize) -> *mut u8 { let mut heap_info = KHEAP_INFO.lock(); let (page, _32pack, chunk) = heap_info.find_free_chunks_of_size(n); if page == 0xFFFFFFFF { core::ptr::null_mut() } else { let tptr: usize = KHEAP_START + 0x400000 * page + _32pack * 32 * 64 + chunk * 64; let res = tptr % align; let uptr = if res == 0 { tptr } else { tptr + align - res }; //check bounds: more than start and less than 4GiB - 64B //but according to chunks error should never happen if uptr >= KHEAP_START && uptr <= 0xFFFFFFFF - 64 * n { heap_info.mark_chunks_used(page, _32pack, chunk, n); uptr as *mut u8 } else { core::ptr::null_mut() } } } fn allocate_n_chunks_less_than_page(n: usize, align: usize) -> *mut u8 { let mut heap_info = KHEAP_INFO.lock(); let packs_n: usize = n / 32; let lost_chunks = n - packs_n * 32; let mut packs_to_alloc = packs_n; if lost_chunks != 0 { packs_to_alloc += 1; } let (page, pack) = heap_info.find_free_packs_of_size(packs_to_alloc); if page == 0xFFFFFFFF { core::ptr::null_mut() } else { let tptr: usize = KHEAP_START + 0x400000 * page + pack * 32 * 64; let res = tptr % align; let uptr = if res == 0 { tptr } else { tptr + align - res }; //check bounds: more than start and less than 4GiB - 64B //but according to chunks error should never happen if uptr >= KHEAP_START && uptr <= 0xFFFFFFFF - 64 * n { heap_info.mark_packs_used(page, pack, packs_n); if lost_chunks != 0 { heap_info.mark_chunks_used(page, pack + packs_to_alloc, 0, lost_chunks); } uptr as *mut u8 } else { core::ptr::null_mut() } } } //unsupported yet fn allocate_n_chunks_more_than_page(n: usize, align: usize) -> *mut u8 { let mut heap_info = KHEAP_INFO.lock(); let packs_n: usize = n / 32; let lost_chunks = n - packs_n * 32; let mut packs_to_alloc = packs_n; if lost_chunks != 0 { packs_to_alloc += 1; } let pages_n: usize = packs_to_alloc / 2048; let mut lost_packs = packs_to_alloc - pages_n * 2048; let mut pages_to_alloc = pages_n; if lost_packs != 0 { pages_to_alloc += 1; } if lost_chunks != 0 { lost_packs -= 1; } let page = heap_info.find_free_pages_of_size(pages_to_alloc); if page == 0xFFFFFFFF { core::ptr::null_mut() } else { let tptr: usize = KHEAP_START + 0x400000 * page; let res = tptr % align; let uptr = if res == 0 { tptr } else { tptr + align - res }; //check bounds: more than start and less than 4GiB - 64B * n //but according to chunks error should never happen if uptr >= KHEAP_START && uptr <= 0xFFFFFFFF - 64 * n { for i in page..(page + pages_n) { heap_info.mark_packs_used(i, 0, 2048); } if lost_packs != 0 { heap_info.mark_packs_used(page + pages_to_alloc, 0, lost_packs); } if lost_chunks != 0 { heap_info.mark_chunks_used(page + pages_to_alloc, lost_packs, 0, lost_chunks); } uptr as *mut u8 } else { core::ptr::null_mut() } } } // returns pointer pub fn allocate_n_chunks(n: usize, align: usize) -> *mut u8 { if n < 32 { allocate_n_chunks_less_than_pack(n, align) } else if n < 32 * 2048 { allocate_n_chunks_less_than_page(n, align) } else { allocate_n_chunks_more_than_page(n, align) } } pub fn free_chunks(ptr: usize, n: usize) { let page: usize = (ptr - KHEAP_START) / 0x400000; let _32pack: usize = ((ptr - KHEAP_START) - (page * 0x400000)) / (32 * 64); let chunk: usize = ((ptr - KHEAP_START) - (page * 0x400000) - (_32pack * (32 * 64))) / 64; let mut heap_info = KHEAP_INFO.lock(); if n < 32 { heap_info.mark_chunks_free(page, _32pack, chunk, n); } else if n < 32 * 2048 { let packs_n: usize = n / 32; let lost_chunks = n - packs_n * 32; heap_info.mark_packs_free(page, _32pack, packs_n); if lost_chunks != 0 { heap_info.mark_chunks_free(page, _32pack + packs_n, 0, lost_chunks); } } else { let packs_n: usize = n / 32; let pages_n: usize = packs_n / 2048; let lost_packs: usize = packs_n - pages_n * 2048; let lost_chunks = n - packs_n * 32; for i in page..(page + pages_n) { heap_info.mark_packs_free(i, 0, 2048); } if lost_packs != 0 { heap_info.mark_packs_free(page + pages_n, 0, lost_packs); } if lost_chunks != 0 { heap_info.mark_chunks_free(page + pages_n, packs_n, 0, lost_chunks); } } } 

Allocation et erreur de page


Pour utiliser le tas, vous avez besoin d'un allocateur. L'ajout nous ouvrira un vecteur, des arbres, des tables de hachage, des boîtes et plus, sans lesquels il est presque impossible de vivre. Dès que nous brancherons le module alloc et déclarerons un allocateur global, la vie deviendra immédiatement plus facile.


La mise en œuvre de l'allocateur est très simple - elle se réfère simplement au mécanisme décrit ci-dessus.


 use alloc::alloc::{GlobalAlloc, Layout}; pub struct Os1Allocator; unsafe impl Sync for Os1Allocator {} unsafe impl GlobalAlloc for Os1Allocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { use super::logical::{KHEAP_CHUNK_SIZE, allocate_n_chunks}; let size = layout.size(); let mut chunk_count: usize = 1; if size > KHEAP_CHUNK_SIZE { chunk_count = size / KHEAP_CHUNK_SIZE; if KHEAP_CHUNK_SIZE * chunk_count != size { chunk_count += 1; } } allocate_n_chunks(chunk_count, layout.align()) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { use super::logical::{KHEAP_CHUNK_SIZE, free_chunks}; let size = layout.size(); let mut chunk_count: usize = 1; if size > KHEAP_CHUNK_SIZE { chunk_count = size / KHEAP_CHUNK_SIZE; if KHEAP_CHUNK_SIZE * chunk_count != size { chunk_count += 1; } } free_chunks(ptr as usize, chunk_count); } } 

L'allocateur dans lib.rs est activé comme suit:


 #![feature(alloc, alloc_error_handler)] extern crate alloc; #[global_allocator] static ALLOCATOR: memory::allocate::Os1Allocator = memory::allocate::Os1Allocator; 

Et lorsque nous essayons de nous allouer simplement de cette manière, nous obtenons une exception de défaut de page, car nous n'avons pas encore défini l'allocation de mémoire virtuelle. Eh bien, comment ça! Eh bien, vous devez revenir à la matière de l'article précédent et ajouter des exceptions. J'ai décidé d'implémenter l'allocation différée de mémoire virtuelle, c'est-à-dire que la page a été allouée non pas au moment de la demande de mémoire, mais au moment d'une tentative d'accès. Heureusement, le processeur x86 le permet et l'encourage même. Page fault , , , — , , CR2 — , .


, . 32 ( , , 32 ), . Rust. , . , , iret , , Page fault Protection fault. Protection fault — , .


 eE_page_fault: pushad mov eax, [esp + 32] push eax mov eax, cr2 push eax call kE_page_fault pop eax pop eax popad add esp, 4 iret 

Rust , . , . . .


 bitflags! { struct PFErrorCode: usize { const PROTECTION = 1; //1 - protection caused, 0 - not present page caused const WRITE = 1 << 1; //1 - write caused, 0 - read caused const USER_MODE = 1 << 2; //1 - from user mode, 0 - from kernel const RESERVED = 1 << 3; //1 - reserved page (PAE/PSE), 0 - not const INSTRUCTION = 1 << 4; //1 - instruction fetch caused, 0 - not } } impl PFErrorCode { pub fn to_pd_flags(&self) -> super::super::paging::PDEntryFlags { use super::super::paging; let mut flags = paging::PDEntryFlags::empty(); if self.contains(PFErrorCode::WRITE) { flags.set(paging::PDEntryFlags::WRITABLE, true); } if self.contains(PFErrorCode::USER_MODE) { flags.set(paging::PDEntryFlags::USER_ACCESSIBLE, true); } flags } } #[no_mangle] pub unsafe extern fn kE_page_fault(ptr: usize, code: usize) { use super::super::paging; println!("Page fault occured at addr 0x{:X}, code {:X}", ptr, code); let phys_address = crate::memory::physical::alloc_page(); let code_flags: PFErrorCode = PFErrorCode::from_bits(code).unwrap(); if !code_flags.contains(PFErrorCode::PROTECTION) { //page not presented, we need to allocate the new one let mut flags: paging::PDEntryFlags = code_flags.to_pd_flags(); flags.set(paging::PDEntryFlags::HUGE_PAGE, true); paging::allocate_page(phys_address, ptr, flags); println!("Page frame allocated at Paddr {:#X} Laddr {:#X}", phys_address, ptr); } else { panic!("Protection error occured, cannot handle yet"); } } 

, . , . . , . , , :


  println!("memory: total {} used {} reserved {} free {}", memory::physical::total(), memory::physical::used(), memory::physical::reserved(), memory::physical::free()); use alloc::vec::Vec; let mut vec: Vec<usize> = Vec::new(); for i in 0..1000000 { vec.push(i); } println!("vec len {}, ptr is {:?}", vec.len(), vec.as_ptr()); println!("Still works, check reusage!"); let mut vec2: Vec<usize> = Vec::new(); for i in 0..10 { vec2.push(i); } println!("vec2 len {}, ptr is {:?}, vec is still here? {}", vec2.len(), vec2.as_ptr(), vec.get(1000).unwrap()); println!("Still works!"); println!("memory: total {} used {} reserved {} free {}", memory::physical::total(), memory::physical::used(), memory::physical::reserved(), memory::physical::free()); 

:
Tas OS1


, , . 3,5 + 3 , . 3,5 .


IRQ 1 — Alt + PrntScrn :)


, , Rust — , — , !


, .


Merci de votre attention!

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


All Articles