Desenvolvendo um sistema operacional monolítico semelhante a Unix - C Library (2)

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


  1. Construa o sistema (marca, gcc, gás). Inicialização inicial (inicialização múltipla). Iniciar (qemu). Biblioteca C (strcpy, memcpy, strext).
  2. Biblioteca C (sprintf, strcpy, strcmp, strtok, va_list ...). Construindo a biblioteca no modo kernel e no modo de aplicativo do usuário.
  3. O log do sistema do kernel. Memória de vídeo Saída para o terminal (kprintf, kpanic, kassert).
  4. Memória dinâmica, heap (kmalloc, kfree).
  5. Organização da memória e manipulação de interrupções (GDT, IDT, PIC, syscall). Exceções
  6. Memória virtual (diretório e tabela de páginas).
  7. Processo. Planejador Multitarefa. Chamadas do sistema (interrupção, saída, ps).
  8. O sistema de arquivos do kernel (initrd), elf e seus internos. Chamadas do sistema (exec).
  9. Drivers de dispositivo de caracteres. Chamadas do sistema (ioctl, fopen, fread, fwrite). Biblioteca C (fopen, fclose, fprintf, fscanf).
  10. Shell como um programa completo para o kernel.
  11. 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ção
Tutorial em vídeo deste artigo
Código fonte (você precisa de um ramo da lição2)

Referências


  1. James Molloy. Role seu próprio sistema operacional clone do UNIX de brinquedo.
  2. Zubkov. Assembler para DOS, Windows, Unix
  3. Kalashnikov. Assembler é fácil!
  4. Tanenbaum. Sistemas operacionais. Implementação e desenvolvimento.
  5. Robert Love. Kernel Linux Descrição do processo de desenvolvimento.

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


All Articles