En un
artículo anterior, aprendimos cómo ejecutar el núcleo Hello World y escribimos un par de funciones para trabajar con cadenas. Ahora es el momento de expandir la biblioteca C para que pueda implementar kprintf y otras funciones necesarias. Vamos!
Tabla de contenidos
- Sistema de construcción (marca, gcc, gas). Arranque inicial (arranque múltiple). Lanzamiento (qemu). Biblioteca C (strcpy, memcpy, strext).
- Biblioteca C (sprintf, strcpy, strcmp, strtok, va_list ...). Creación de la biblioteca en modo kernel y modo de aplicación de usuario.
- El registro del sistema del núcleo. Memoria de video Salida a la terminal (kprintf, kpanic, kassert).
- Memoria dinámica, montón (kmalloc, kfree).
- Organización de memoria y manejo de interrupciones (GDT, IDT, PIC, syscall). Excepciones
- Memoria virtual (directorio de páginas y tabla de páginas).
- Proceso. Planificador Multitarea Sistema de llamadas (kill, exit, ps).
- El sistema de archivos del kernel (initrd), elf y sus componentes internos. Sistema de llamadas (exec).
- Controladores de dispositivos de caracteres. Llamadas del sistema (ioctl, fopen, fread, fwrite). Biblioteca C (fopen, fclose, fprintf, fscanf).
- Shell como un programa completo para el kernel.
- Modo de protección del usuario (anillo3). Segmento de estado de la tarea (tss).
Biblioteca C
Primero necesita implementar tipos con dimensiones explícitas.
Dado que recopilaremos en una sola plataforma, hasta que nuestras definiciones e implementaciones sean correctas. No es universal, pero es por eso que es legible y simple. Considero que a veces es mejor hacer algo que no sea universal, pero que cumpla con los requisitos actuales, porque apoyar soluciones universales es extremadamente 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;
No está de más introducir el tipo booleano.
#pragma once /* types */ typedef int bool; #define true 1 #define false 0 #define null 0
Además, un par de macros para trabajar con bytes no nos harán dañ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)
Las siguientes macros son necesarias para trabajar con un número variable de argumentos. Este enfoque solo funciona si la función sigue la convención de llamada del lenguaje C, en la que los argumentos de la función se pasan a través de la pila a partir de la ú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++))
Por supuesto, uno podría ir para otro lado. En lugar de definir sus propias funciones, intente usar la biblioteca incorporada y reemplace las funciones que accederán al núcleo a través de LD_PRELOAD. Pero me gusta controlar el proceso por completo, así que dejemos esta opción como una idea para aquellos que comienzan a escribir su sistema operativo en este tutorial.
Además, en el
video tutorial, consideraremos la implementación de las siguientes funciones de biblioteca. La implementación no pretende ser óptima y completa, pero creo que afirma ser simple y legible. Solo noto que estamos utilizando una implementación segura para subprocesos de la función strtok, que se llama strtok_r. Y encontramos las funciones strinv y strext en la última lección. Si está familiarizado con el lenguaje C, creo que estará familiarizado con casi todas las funciones enumeradas a continuación.
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);
La rutina queda eliminada. El final del código repetitivo. La próxima lección será mucho más complicada e interesante. Si desea patrocinar un proyecto, puede ofrecer una implementación óptima de las funciones de la biblioteca.
Referencias
Desarrollo de un sistema operativo monolítico similar a Unix: cómo comenzarVideo tutorial para este artículoCódigo fuente (necesita una rama de la lección 2)Referencias
- James Molloy Haga rodar su propio sistema operativo de clones UNIX de juguete.
- Zubkov Ensamblador para DOS, Windows, Unix
- Kalashnikov. ¡Ensamblador es fácil!
- Tanenbaum Sistemas operativos Implementación y desarrollo.
- Robert Love Kernel de Linux Descripción del proceso de desarrollo.