Dans un
article précédent, nous avons appris à exécuter le noyau Hello World et écrit quelques fonctions pour travailler avec des chaînes. Il est maintenant temps d'étendre la bibliothèque C afin de pouvoir implémenter kprintf et d'autres fonctions nécessaires. C'est parti!
Table des matières
- Construisez le système (make, gcc, gas). Démarrage initial (multiboot). Lancez (qemu). Bibliothèque C (strcpy, memcpy, strext).
- Bibliothèque C (sprintf, strcpy, strcmp, strtok, va_list ...). Construction de la bibliothèque en mode noyau et en mode application utilisateur.
- Le journal système du noyau. Mémoire vidéo Sortie vers le terminal (kprintf, kpanic, kassert).
- Mémoire dynamique, tas (kmalloc, kfree).
- Organisation de la mémoire et gestion des interruptions (GDT, IDT, PIC, syscall). Exceptions
- Mémoire virtuelle (répertoire de pages et table de pages).
- Processus. Planificateur Multitâche. Appels système (kill, exit, ps).
- Le système de fichiers du noyau (initrd), elf et ses composants internes. Appels système (exec).
- Pilotes de périphériques de caractères. Appels système (ioctl, fopen, fread, fwrite). Bibliothèque C (fopen, fclose, fprintf, fscanf).
- Shell comme programme complet pour le noyau.
- Mode de protection utilisateur (ring3). Segment d'état de la tâche (tss).
Bibliothèque C
Vous devez d'abord implémenter des types avec des dimensions explicites.
Puisque nous collecterons sous une seule plateforme, jusqu'à ce que nos définitions et implémentations soient correctes. Ce n'est pas universel, mais c'est pourquoi il est lisible et simple. Je pense que parfois il vaut mieux faire quelque chose qui n'est pas universel, mais qui répond aux exigences actuelles, car il est extrêmement difficile de prendre en charge des solutions universelles.
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;
Cela ne fait pas de mal d'introduire le type booléen.
#pragma once /* types */ typedef int bool; #define true 1 #define false 0 #define null 0
De plus, quelques macros pour travailler avec des octets ne nous feront pas de mal.
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)
Les macros suivantes sont nécessaires pour travailler avec un nombre variable d'arguments. Cette approche ne fonctionne que si la fonction suit la convention d'appel du langage C, dans laquelle les arguments de la fonction sont passés à travers la pile à partir de la dernière.
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++))
Bien sûr, on pourrait aller dans l'autre sens. Au lieu de définir vos propres fonctions, essayez d'utiliser la bibliothèque intégrée et remplacez les fonctions qui accéderont au noyau via LD_PRELOAD. Mais j'aime contrôler complètement le processus, alors laissons cette option comme une idée pour ceux qui commencent à écrire leur système d'exploitation sur ce tutoriel.
De plus, dans le
didacticiel vidéo, nous considérerons l'implémentation des fonctions de bibliothèque suivantes. L'implémentation ne prétend pas être optimale et complète, mais je pense qu'elle prétend être simple et lisible. Je note juste que nous utilisons une implémentation thread-safe de la fonction strtok, qui s'appelle strtok_r. Et nous avons proposé nous-mêmes les fonctions strinv et strext dans la dernière leçon. Si vous êtes familier avec le langage C, je pense que vous connaissez presque toutes les fonctions énumérées ci-dessous.
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 routine est supprimée. La fin du code passe-partout. La prochaine leçon sera beaucoup plus compliquée et intéressante. Si vous souhaitez parrainer un projet, vous pouvez proposer votre mise en œuvre optimale des fonctions de bibliothèque.
Les références
Développement d'un système d'exploitation monolithique de type Unix - Prise en mainTutoriel vidéo pour cet articleCode source (vous avez besoin d'une branche de leçon 2)Les références
- James Molloy. Faites rouler votre propre système d'exploitation jouet UNIX-clone.
- Zubkov. Assembleur pour DOS, Windows, Unix
- Kalachnikov. L'assembleur est facile!
- Tanenbaum. Systèmes d'exploitation. Mise en œuvre et développement.
- Robert Love. Noyau Linux Description du processus de développement.