Em um
artigo anterior, aprendemos como executar o kernel Hello World e escrevemos algumas funções para trabalhar com strings. Agora é hora de expandir a biblioteca C para poder implementar o kprintf e outras funções necessárias. Vamos lá!
Sumário
- Construa o sistema (marca, gcc, gás). Inicialização inicial (inicialização múltipla). Iniciar (qemu). Biblioteca C (strcpy, memcpy, strext).
- Biblioteca C (sprintf, strcpy, strcmp, strtok, va_list ...). Construindo a biblioteca no modo kernel e no modo de aplicativo do usuário.
- O log do sistema do kernel. Memória de vídeo Saída para o terminal (kprintf, kpanic, kassert).
- Memória dinâmica, heap (kmalloc, kfree).
- Organização da memória e manipulação de interrupções (GDT, IDT, PIC, syscall). Exceções
- Memória virtual (diretório e tabela de páginas).
- Processo. Planejador Multitarefa. Chamadas do sistema (interrupção, saída, ps).
- O sistema de arquivos do kernel (initrd), elf e seus internos. Chamadas do sistema (exec).
- Drivers de dispositivo de caracteres. Chamadas do sistema (ioctl, fopen, fread, fwrite). Biblioteca C (fopen, fclose, fprintf, fscanf).
- Shell como um programa completo para o kernel.
- Modo de proteção do usuário (anel3). Segmento de status da tarefa (tss).
Biblioteca C
Primeiro, você precisa implementar tipos com dimensões explícitas.
Como coletaremos sob uma plataforma, até que nossas definições e implementações estejam corretas. Não é universal, mas é por isso que é legível e simples. Eu adoto a abordagem de que, às vezes, é melhor fazer algo que não é universal, mas que atende aos requisitos atuais, porque o suporte a soluções universais é extremamente difícil.
typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef unsigned int u_long;
Não faz mal introduzir o tipo booleano.
#pragma once /* types */ typedef int bool; #define true 1 #define false 0 #define null 0
Além disso, algumas macros para trabalhar com bytes não nos prejudicarão.
typedef unsigned long size_t; #define HIGH_WORD(addr) ((addr & 0xffff0000) >> 16) #define LOW_WORD(addr) ((addr & 0xffff)) #define LOW_BYTE(addr) ((addr & 0x00ff)) #define HIGH_BYTE(addr) ((addr & 0xff00) >> 8)
As macros a seguir são necessárias para trabalhar com um número variável de argumentos. Essa abordagem funciona apenas se a função seguir a convenção de chamada da linguagem C, na qual os argumentos da função são passados pela pilha, começando pela última.
typedef size_t* va_list; #define va_start(l, a) (l = (void*)((size_t)&a) + sizeof(a)) #define va_end(l) (l = (void*)0) #define va_arg(l, s) (*(s*)(l++))
Claro, alguém poderia seguir o outro caminho. Em vez de definir suas próprias funções, tente usar a biblioteca interna e substitua as funções que acessarão o kernel através do LD_PRELOAD. Mas eu gosto de controlar o processo completamente, então vamos deixar essa opção como uma idéia para quem começa a escrever seu SO neste tutorial.
Além disso, no
tutorial em
vídeo, consideraremos a implementação das seguintes funções da biblioteca. A implementação não afirma ser ótima e completa, mas acho que afirma ser simples e legível. Acabei de observar que estamos usando uma implementação segura da thread da função strtok, chamada strtok_r. E criamos as funções strinv e strext na última lição. Se você está familiarizado com a linguagem C, acho que estará familiarizado com quase todas as funções listadas abaixo.
extern int strlen(const char* s); extern char* strcpy(char* s1, const char* s2); extern char* strncpy(char* s1, const char* s2, u_int n); extern void* memcpy(void* buf1, const void* buf2, u_int bytes); extern void* memset(void* buf1, u8 value, u_int bytes); extern int strcmp(const char* s1, const char* s2); extern int strncmp(const char* s1, const char* s2, u_int n); extern char* strcat(char* s1, const char* s2); extern char* strext(char* buf, const char* str, char sym); extern int strspn(char* str, const char* accept); extern int strcspn(char* str, const char* rejected); char* strchr(const char* str, char ch); extern char* strtok_r(char* str, const char* delims, char** save_ptr); extern char* memext(void* buff_dst, u_int n, const void* buff_src, char sym); extern char* itoa(unsigned int value, char* str, unsigned int base); extern unsigned int atou(char* str); extern char* strinv(char* str); extern unsigned int sprintf(char* s1, const char* s2, ...); extern unsigned int snprintf(char* s1, u_int n, const char* s2, ...); extern unsigned int vsprintf(char* s1, const char* s2, va_list list); extern unsigned int vsnprintf(char* s1, unsigned int n, const char* s2, va_list list);
A rotina é eliminada. O final do código padrão. A próxima lição será muito mais complicada e interessante. Se você deseja patrocinar um projeto, pode oferecer sua implementação ideal das funções da biblioteca.
Referências
Desenvolvendo um sistema operacional monolítico semelhante a Unix - IntroduçãoTutorial em vídeo deste artigoCódigo fonte (você precisa de um ramo da lição2)Referências
- James Molloy. Role seu próprio sistema operacional clone do UNIX de brinquedo.
- Zubkov. Assembler para DOS, Windows, Unix
- Kalashnikov. Assembler é fácil!
- Tanenbaum. Sistemas operacionais. Implementação e desenvolvimento.
- Robert Love. Kernel Linux Descrição do processo de desenvolvimento.