OS1: un núcleo primitivo en Rust para x86. Parte 3. Tarjeta de memoria, excepción de falla de página, montón y asignaciones

Primera parte
Segunda parte


El tema de la conversación de hoy es trabajar con la memoria. Hablaré sobre la inicialización del directorio de la página, el mapeo de la memoria física, la administración virtual y el montón de mi organización para el asignador.


Como dije en el primer artículo, decidí usar páginas de 4 MB para simplificar mi vida y no tener que lidiar con tablas jerárquicas. En el futuro, espero ir a páginas de 4 KB, como la mayoría de los sistemas modernos. Podría usar uno listo (por ejemplo, un asignador de bloques ), pero escribir el mío fue un poco más interesante y quería entender un poco más cómo vive la memoria, así que tengo algo que decirte.


La última vez que me decidí por el método setup_pd dependiente de la arquitectura y quería continuar con él, sin embargo, había un detalle más que no cubrí en el artículo anterior: salida VGA usando Rust y la macro println estándar. Como su implementación es trivial, la eliminaré debajo del spoiler. El código está en el paquete de depuración.


Macro println
#[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(); } 

Ahora, con la conciencia tranquila, vuelvo a la memoria.


Inicialización del directorio de páginas


Nuestro método kmain tomó tres argumentos como entrada, uno de los cuales es la dirección virtual de la tabla de páginas. Para usarlo más tarde para la asignación y la administración de la memoria, debe designar la estructura de registros y directorios. Para x86, el directorio de la página y la tabla de la página se describen bastante bien, por lo que me limitaré a una pequeña introducción. La entrada del directorio de la página es una estructura de tamaño de puntero, para nosotros es de 4 bytes. El valor contiene una dirección física de 4KB de la página. El byte menos significativo del registro está reservado para banderas. El mecanismo para convertir una dirección virtual en una física se ve así (en el caso de mi granularidad de 4 MB, el cambio se produce en 22 bits. Para otras granularidades, el cambio será diferente y se usarán tablas jerárquicas):


Dirección virtual 0xC010A110 -> Obtenga el índice en el directorio moviendo la dirección 22 bits a la derecha -> índice 0x300 -> Obtenga la dirección física de la página por índice 0x300, verifique los indicadores y el estado -> 0x1000000 -> Tome los 22 bits inferiores de la dirección virtual como un desplazamiento, agregue a la dirección física de la página -> 0x1000000 + 0x10A110 = dirección física en memoria 0x110A110

Para acelerar el acceso, el procesador utiliza TLB, el búfer de traducción, que almacena en caché las direcciones de las páginas.


Entonces, así es como se describe mi directorio y sus entradas, y se implementa el método setup_pd. Para escribir una página, se implementa el método "constructor", que garantiza la alineación en 4 KB y la configuración de banderas, y un método para obtener la dirección física de la página. Un directorio es solo una matriz de 1024 entradas de cuatro bytes. El directorio puede asociar una dirección virtual con una página utilizando el método 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); } 

Muy torpemente hice que la inicialización estática inicial fuera una dirección inexistente, por lo que le agradecería que me escribiera cómo es habitual en la comunidad de Rust hacer tales inicializaciones con la reasignación de enlaces.


Ahora que podemos administrar páginas desde código de alto nivel, podemos pasar a compilar la inicialización de memoria. Esto sucederá en dos etapas: procesando la tarjeta de memoria física e inicializando el administrador virtual


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

Tarjeta de memoria GRUB y tarjeta de memoria física OS1


Para obtener una tarjeta de memoria de GRUB, en la etapa de arranque configuré la bandera correspondiente en el encabezado, y GRUB me dio la dirección física de la estructura. Lo porté de la documentación oficial a la notación Rust, y también agregué métodos para iterar cómodamente sobre la tarjeta de memoria. La mayor parte de la estructura de GRUB no se completará, y en esta etapa no es muy interesante para mí. Lo principal es que no quiero determinar la cantidad de memoria disponible manualmente.


Al inicializar a través de Multiboot, primero convertimos la dirección física a virtual. Teóricamente, GRUB puede colocar la estructura en cualquier lugar, por lo que si la dirección se extiende más allá de la página, debe asignar una página virtual en el directorio de la página. En la práctica, la estructura casi siempre se encuentra al lado del primer megabyte, que ya hemos asignado en la etapa de arranque. Por si acaso, verificamos la bandera de que la tarjeta de memoria está presente y procedemos a su análisis.


 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"); } } 

Una tarjeta de memoria es una lista vinculada para la cual la dirección física inicial se especifica en la estructura básica (no olvide traducir todo en virtuales) y el tamaño de la matriz en bytes. Debe recorrer la lista en función del tamaño de cada elemento, ya que, en teoría, sus tamaños pueden diferir. Así es como se ve la iteración:


 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) } } 

Al analizar una tarjeta de memoria, iteramos a través de la estructura GRUB y la convertimos en un mapa de bits, con el cual OS1 trabajará para administrar la memoria física. Decidí limitarme a un pequeño conjunto de valores disponibles para el control: libre, ocupado, reservado, no disponible, aunque GRUB y BIOS ofrecen más opciones. Entonces, iteramos sobre las entradas del mapa y convertimos su estado de valores GRUB / BIOS a valores para 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 memoria física se administra en el módulo memory :: physical, para el cual llamamos al método de mapa anterior, pasándole la dirección de la región, su longitud y estado. Los 4 GB de memoria potencialmente disponibles para el sistema y divididos en páginas de cuatro megabytes están representados por dos bits en un mapa de bits, lo que le permite almacenar 4 estados para 1024 páginas. En total, esta construcción toma 256 bytes. Un mapa de bits conduce a una terrible fragmentación de la memoria, pero es comprensible y fácil de implementar, lo cual es lo más importante para mi propósito.


Eliminaré la implementación de mapa de bits debajo del spoiler para no saturar el artículo. La estructura puede contar el número de clases y memoria libre, marcar páginas por índice y dirección, y también buscar páginas libres (esto será necesario en el futuro para implementar el montón). La tarjeta en sí es una matriz de 64 elementos u32, para aislar los dos bits (bloques) necesarios, se utiliza la conversión al llamado fragmento (índice en la matriz, empaque de 16 bloques) y el bloque (posición de bit en el fragmento).


Mapa de bits de memoria física
 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 } } 

Y ahora llegamos al análisis de un elemento del mapa. Si un elemento del mapa describe un área de memoria de menos de una página de 4 MB o igual, marcamos esta página como un todo. Si es más, batir en pedazos de 4 MB y marcar cada pieza por separado a través de la recursión. En la etapa de inicialización del mapa de bits, consideramos que todas las secciones de la memoria son inaccesibles, de modo que cuando la tarjeta se agota, por ejemplo, a 128 MB, las secciones restantes se marcan como inaccesibles.


 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); } } 

Heap y manejándola


Actualmente, la administración de memoria virtual se limita solo a la administración de almacenamiento dinámico, ya que el núcleo no sabe mucho más. En el futuro, por supuesto, será necesario administrar toda la memoria, y este pequeño administrador se reescribirá. Sin embargo, por el momento, todo lo que necesito es memoria estática, que contiene el código ejecutable y la pila, y memoria dinámica de montón, donde asignaré las estructuras para subprocesamiento múltiple. Asignamos memoria estática en la etapa de arranque (y hasta ahora tenemos 4 MB limitados, porque el núcleo encaja en ellos) y, en general, no hay problemas con eso ahora. Además, en esta etapa, no tengo dispositivos DMA, por lo que todo es extremadamente simple, pero comprensible.


Le di 512 MB del espacio de memoria superior del núcleo (0xE0000000) al montón, almaceno el mapa de uso del montón (0xDFC00000) 4 MB más bajo. Utilizo un mapa de bits para describir el estado, al igual que para la memoria física, pero solo contiene 2 estados: ocupado / libre. El tamaño del bloque de memoria es de 64 bytes; esto es mucho para pequeñas variables como u32, u8, pero, tal vez, es óptimo para almacenar estructuras de datos. Aún así, es poco probable que necesitemos almacenar variables individuales en el montón, ahora su propósito principal es almacenar estructuras de contexto para la multitarea.


Los bloques de 64 bytes se agrupan en estructuras que describen el estado de una página completa de 4 MB, por lo que podemos asignar pequeñas y grandes cantidades de memoria a varias páginas. Utilizo los siguientes términos: fragmento - 64 bytes, paquete - 2 KB (uno u32 - 64 bytes * 32 bits por paquete), página - 4 MB.


 #[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], } 

Cuando solicito memoria de un asignador, considero tres casos, dependiendo de la granularidad:


  • Una solicitud de memoria de menos de 2 KB vino del asignador. Debe encontrar un paquete en el que estará libre [tamaño / 64, cualquier resto distinto de cero agrega uno] fragmentos seguidos, marque estos fragmentos como ocupados, devuelva la dirección del primer fragmento.
  • El asignador solicitó una memoria de menos de 4 MB pero más de 2 KB. Debe encontrar una página que tenga paquetes gratuitos [tamaño / 2048, cualquier resto distinto de cero agrega uno] seguidos. Marque los paquetes [tamaño / 2048] como ocupados; si hay un resto, marque los fragmentos [restantes] en el último paquete como ocupados.
  • Una solicitud de memoria de más de 4 MB vino del asignador. Encuentre [tamaño / 4 Mi, cualquier saldo distinto de cero agrega una] páginas seguidas, marque las páginas [tamaño / 4 Mi] como ocupadas, si hay un saldo - marque los paquetes [saldo] como ocupados. En el último paquete, marque el resto de los fragmentos como ocupados.

La búsqueda de áreas libres también depende de la granularidad: se selecciona una matriz para iteración o máscaras de bits. Cada vez que vas al extranjero, OOM sucede. Cuando se desasigna, se usa un algoritmo similar, solo para el marcado lanzado. La memoria liberada no se restablece. Todo el código es grande, lo pondré debajo del spoiler.


Mapa de bits de memoria virtual
 //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); } } } 

Asignación y fallo de página


Para usar el montón, necesita un asignador. Agregarlo nos abrirá un vector, árboles, tablas hash, cajas y más, sin los cuales es casi imposible vivir. Tan pronto como conectemos el módulo de asignación y declaremos un asignador global, la vida se volverá más fácil de inmediato.


La implementación del asignador es muy simple: simplemente se refiere al mecanismo descrito anteriormente.


 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); } } 

El asignador en lib.rs se activa de la siguiente manera:


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

Y cuando tratamos de asignarnos de tal manera, obtenemos una excepción de falla de página, porque todavía no hemos resuelto la asignación de memoria virtual. Bueno, como es eso! Bueno, debe volver al material del artículo anterior y agregar excepciones. Decidí implementar una asignación diferida de memoria virtual, es decir, que la página se asignó no en el momento de la solicitud de memoria, sino en el momento de intentar acceder a ella. Afortunadamente, el procesador x86 permite e incluso fomenta esto. 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()); 

:
Montón OS1


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


IRQ 1 — Alt + PrntScrn :)


, , Rust — , — , !


, .


Gracias por su atencion!

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


All Articles