OS1: um kernel primitivo no Rust para x86. Parte 3. Cartão de memória, exceção de falha de página, pilha e alocações

Primeira parte
Segunda parte


O tópico da conversa de hoje está trabalhando com memória. Vou falar sobre como inicializar o diretório da página, mapear a memória física, gerenciar o virtual e o heap da minha organização para o alocador.


Como disse no primeiro artigo, decidi usar páginas de 4 MB para simplificar minha vida e não ter que lidar com tabelas hierárquicas. No futuro, espero ir para páginas de 4 KB, como a maioria dos sistemas modernos. Eu poderia usar um já pronto (por exemplo, um alocador de blocos ), mas escrever o meu próprio era um pouco mais interessante e eu queria entender um pouco mais como a memória vive, então tenho algo a lhe dizer.


A última vez que me deparei com o método setup_pd, dependente da arquitetura, e queria continuar com ele, no entanto, havia mais um detalhe que não cobri no artigo anterior - saída VGA usando Rust e a macro println padrão. Como sua implementação é trivial, vou removê-lo sob o spoiler. O código está no pacote de depuração.


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

Agora, com a consciência limpa, volto à memória.


Inicialização do diretório de páginas


Nosso método kmain recebeu três argumentos como entrada, um dos quais é o endereço virtual da tabela de páginas. Para usá-lo posteriormente para alocação e gerenciamento de memória, você precisa designar a estrutura de registros e diretórios. Para x86, o diretório Page e a tabela Page são descritas muito bem, então vou me limitar a um pequeno introdutório. A entrada do diretório Page é uma estrutura de tamanho de ponteiro, para nós é de 4 bytes. O valor contém um endereço físico de 4KB da página. O byte menos significativo do registro é reservado para sinalizadores. O mecanismo para converter um endereço virtual em um físico se parece com o seguinte (no caso da minha granularidade de 4 MB, o deslocamento ocorre em 22 bits. Para outras granularidades, o deslocamento será diferente e tabelas hierárquicas serão usadas!):


Endereço virtual 0xC010A110 -> Obtenha o índice no diretório movendo o endereço 22 bits para a direita -> índice 0x300 -> Obtenha o endereço físico da página pelo índice 0x300, verifique sinalizadores e status -> 0x1000000 -> Leve os 22 bits inferiores do endereço virtual como um deslocamento, adicione para o endereço físico da página -> 0x1000000 + 0x10A110 = endereço físico na memória 0x110A110

Para acelerar o acesso, o processador usa o TLB - buffer lookaside de tradução, que armazena em cache os endereços das páginas.


Então, aqui está como meu diretório e suas entradas são descritos, e o próprio método setup_pd é implementado. Para escrever uma página, o método "construtor" é implementado, o que garante o alinhamento em 4 KB e a configuração de sinalizadores, além de um método para obter o endereço físico da página. Um diretório é apenas uma matriz de 1024 entradas de quatro bytes. O diretório pode associar um endereço virtual a uma página usando o 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); } 

Desajeitadamente, tornei a inicialização estática inicial um endereço inexistente; portanto, ficaria grato se você me escrevesse como é habitual na comunidade Rust fazer essas inicializações com a reatribuição de links.


Agora que podemos gerenciar páginas a partir de código de alto nível, podemos prosseguir para a compilação da inicialização da memória. Isso acontecerá em duas etapas: processando o cartão de memória físico e inicializando o gerenciador virtual


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

Cartão de memória GRUB e cartão de memória física OS1


Para obter um cartão de memória do GRUB, no estágio de inicialização, defino o sinalizador correspondente no cabeçalho, e o GRUB me deu o endereço físico da estrutura. Eu o transportei da documentação oficial para a notação Rust e também adicionei métodos para iterar confortavelmente no cartão de memória. A maior parte da estrutura do GRUB não será preenchida e, neste estágio, não é muito interessante para mim. O principal é que eu não quero determinar a quantidade de memória disponível manualmente.


Ao inicializar através da inicialização múltipla, primeiro convertemos o endereço físico em virtual. Teoricamente, o GRUB pode posicionar a estrutura em qualquer lugar; portanto, se o endereço se estender além da página, você precisará alocar uma página virtual no diretório Page. Na prática, a estrutura quase sempre fica próxima ao primeiro megabyte, que já alocamos no estágio de inicialização. Por precaução, verificamos a bandeira de que o cartão de memória está presente e procedemos à sua análise.


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

Um cartão de memória é uma lista vinculada para a qual o endereço físico inicial é especificado na estrutura básica (não se esqueça de converter tudo em virtual) e o tamanho da matriz em bytes. Você precisa percorrer a lista com base no tamanho de cada elemento, pois teoricamente seus tamanhos podem ser diferentes. É assim que a iteração se parece:


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

Ao analisar um cartão de memória, iteramos na estrutura do GRUB e o convertemos em um bitmap, com o qual o OS1 trabalhará para gerenciar a memória física. Decidi me limitar a um pequeno conjunto de valores disponíveis para controle - livre, ocupado, reservado, indisponível, embora o GRUB e o BIOS forneçam mais opções. Portanto, iteramos sobre as entradas do mapa e convertemos seu estado dos valores do GRUB / BIOS em 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 } } 

A memória física é gerenciada no módulo memory :: physical, para o qual chamamos o método map acima, passando o endereço da região, seu comprimento e estado. Todos os 4 GB de memória potencialmente disponíveis para o sistema e divididos em quatro páginas de megabytes são representados por dois bits em um bitmap, o que permite armazenar 4 estados para 1024 páginas. No total, essa construção leva 256 bytes. Um bitmap leva a uma terrível fragmentação da memória, mas é compreensível e fácil de implementar, o que é a principal coisa para o meu propósito.


Vou remover a implementação de bitmap sob o spoiler para não bagunçar o artigo. A estrutura é capaz de contar o número de classes e liberar memória, marcar páginas por índice e endereço e também procurar por páginas livres (isso será necessário no futuro para implementar o heap). O cartão em si é uma matriz de 64 elementos u32, para isolar os dois bits necessários (blocos), é usada a conversão para o chamado pedaço (índice na matriz, empacotamento de 16 blocos) e bloco (posição do bit no pedaço).


Bitmap de memória 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 } } 

E agora chegamos à análise de um elemento do mapa. Se um elemento do mapa descrever uma área de memória menor que uma página de 4 MB ou igual a ela, marcaremos essa página como um todo. Se houver mais - bata em pedaços de 4 MB e marque cada pedaço separadamente por recursão. No estágio de inicialização do bitmap, consideramos inacessíveis todas as seções da memória, para que, quando o cartão acabar, por exemplo, com 128 MB, as demais seções sejam marcadas como inacessíveis.


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

Empilhe e gerencie-a


Atualmente, o gerenciamento de memória virtual está limitado apenas ao gerenciamento de heap, pois o kernel não sabe muito mais. No futuro, é claro, será necessário gerenciar toda a memória, e esse pequeno gerente será reescrito. No entanto, no momento, tudo o que preciso é de memória estática, que contém o código executável e a pilha, e memória dinâmica de heap, onde alocarei as estruturas para multithreading. Alocamos memória estática no estágio de inicialização (e até agora limitamos 4 MB, porque o kernel se encaixa nelas) e, em geral, não há problemas com isso agora. Além disso, nesta fase, eu não tenho dispositivos DMA, então tudo é extremamente simples, mas compreensível.


Dei 512 MB do espaço de memória do kernel mais alto (0xE0000000) ao heap, armazenei o mapa de uso do heap (0xDFC00000) 4 MB mais baixo. Eu uso um bitmap para descrever o estado, assim como para a memória física, mas existem apenas 2 estados nele - ocupado / livre. O tamanho do bloco de memória é de 64 bytes - isso é muito para pequenas variáveis ​​como u32, u8, mas, talvez, seja ideal para armazenar estruturas de dados. Ainda assim, é improvável que precisamos armazenar variáveis ​​únicas no heap, agora seu principal objetivo é armazenar estruturas de contexto para multitarefa.


Blocos de 64 bytes são agrupados em estruturas que descrevem o estado de uma página inteira de 4 MB, para que possamos alocar pequenas e grandes quantidades de memória para várias páginas. Uso os seguintes termos: bloco - 64 bytes, pacote - 2 KB (um u32 - 64 bytes * 32 bits por pacote), 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], } 

Ao solicitar memória de um alocador, considero três casos, dependendo da granularidade:


  • Uma solicitação de memória com menos de 2 KB veio do alocador. Você precisa encontrar um pacote no qual ele estará livre [tamanho / 64, qualquer resto diferente de zero adiciona um] pedaços seguidos, marque esses pedaços como ocupados, retorne o endereço do primeiro pedaço.
  • Uma solicitação veio do alocador para memória com menos de 4 MB, mas com mais de 2 KB. Você precisa encontrar uma página que tenha [tamanho / 2048, qualquer restante diferente de zero adicione um] pacotes seguidos. Marque os pacotes [tamanho / 2048] como ocupados; se houver um restante, marque os pedaços [restantes] no último como ocupados.
  • Uma solicitação de memória com mais de 4 MB veio do alocador. Localize [tamanho / 4 Mi, qualquer saldo diferente de zero adiciona uma] página em uma linha, marque as páginas [tamanho / 4 Mi] como ocupadas, se houver uma marca de equilíbrio [marca] como ocupada. No último pacote, marque o restante dos pedaços como ocupado.

A pesquisa de áreas livres também depende da granularidade - uma matriz é selecionada para iteração ou máscaras de bits. Sempre que você vai para o exterior, o OOM acontece. Quando a desalocação, um algoritmo semelhante é usado, apenas para a marcação liberada. A memória liberada não é redefinida. Todo o código é grande, vou colocá-lo sob o spoiler.


Bitmap de memória 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); } } } 

Alocação e falha de página


Para usar o heap, você precisa de um alocador. Adicioná-lo abrirá para nós um vetor, árvores, tabelas de hash, caixas e muito mais, sem o qual é quase impossível viver. Assim que conectarmos o módulo de alocação e declararmos um alocador global, a vida se tornará imediatamente mais fácil.


A implementação do alocador é muito simples - refere-se simplesmente ao mecanismo descrito acima.


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

O alocador em lib.rs está ativado da seguinte maneira:


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

E quando tentamos nos alocar dessa maneira, obtemos uma exceção de falha de página, porque ainda não calculamos a alocação de memória virtual. Bem, como sim! Bem, você precisa voltar ao material do artigo anterior e adicionar exceções. Decidi implementar uma alocação lenta da memória virtual, ou seja, que a página fosse alocada não no momento da solicitação de memória, mas no momento de uma tentativa de acessá-la. Felizmente, o processador x86 permite e até incentiva isso. 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()); 

:
Pilha OS1


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


IRQ 1 — Alt + PrntScrn :)


, , Rust — , — , !


, .


Obrigado pela atenção!

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


All Articles