- 0x00 – Mise en place de l’environnement de développement
- 0x01 – Le premier boot
- 0x02 – Organiser notre projet
- 0x03 – La mémoire
- 0x04 – L’Allocation Dynamique
Séparer nos fichiers
Maintenant, nous avons nos fichiers C, notre linker, nos fichier objets, et notre noyau compilé éparpiller dans le même dossier. Avant de commencer à rendre le noyau plus complexe, il serait plus judicieux de séparer les différents types de fichiers et de hiérarchiser notre projet.
Nous allons donc séparer les fichiers C, les fichiers d’ en- tête et les fichiers de compilation dans des répertoires distincts: src , include et build respectivement. src et include vont être structurés de manière très similaire.
src et include vont avoir des sous-répertoires kernel et common. kernel contiendra les fichiers qui sont exclusifs au noyau, et common les fichiers de fonctionnalités standard qui ne peuvent pas être exclusives au noyau, tels que memcpy.
build va contenir notre linker, et un makefile. Lors de la compilation, le makefile placera tous les fichiers objets, ainsi que le fichier binaire du noyau compilé dans le répertoire build .
Voici le code du fichier makefile:
# Don't use normal gcc, use the arm cross compiler CC = arm-none-eabi-gcc # Set any constants based on the raspberry pi model. Version 1 has some differences to 2 and 3 ifeq ($(RASPI_MODEL),1) CPU = arm1176jzf-s DIRECTIVES = -D MODEL_1 else CPU = cortex-a7 endif CFLAGS= -mcpu=$(CPU) -fpic -ffreestanding $(DIRECTIVES) CSRCFLAGS= -O2 -Wall -Wextra LFLAGS= -ffreestanding -O2 -nostdlib # Location of the files KER_SRC = ../src/kernel KER_HEAD = ../include COMMON_SRC = ../src/common OBJ_DIR = objects KERSOURCES = $(wildcard $(KER_SRC)/*.c) COMMONSOURCES = $(wildcard $(COMMON_SRC)/*.c) ASMSOURCES = $(wildcard $(KER_SRC)/*.S) OBJECTS = $(patsubst $(KER_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERSOURCES)) OBJECTS += $(patsubst $(COMMON_SRC)/%.c, $(OBJ_DIR)/%.o, $(COMMONSOURCES)) OBJECTS += $(patsubst $(KER_SRC)/%.S, $(OBJ_DIR)/%.o, $(ASMSOURCES)) HEADERS = $(wildcard $(KER_HEAD)/*.h) IMG_NAME=kernel.img build: $(OBJECTS) $(HEADERS) echo $(OBJECTS) $(CC) -T linker.ld -o $(IMG_NAME) $(LFLAGS) $(OBJECTS) $(OBJ_DIR)/%.o: $(KER_SRC)/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $@ $(CSRCFLAGS) $(OBJ_DIR)/%.o: $(KER_SRC)/%.S mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -c $< -o $@ $(OBJ_DIR)/%.o: $(COMMON_SRC)/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $@ $(CSRCFLAGS) clean: rm -rf $(OBJ_DIR) rm $(IMG_NAME) run: build qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel kernel.img
Explication du code
Définir les variables
CC = arm-none-eabi-gcc
Cela définit le compilateur que nous allons utiliser par le compilateur que nous avons téléchargé lors de la configuration de notre environnement de développement.
ifeq ($(RASPI_MODEL),1) CPU = arm1176jzf-s DIRECTIVES = -D MODEL_1 else CPU = cortex-a7 endif
Il existe différentes type de processeur pour les modèles de raspberry pi.Comme nous sommes entrain de développer pour le model 2, nous allons faire de celui-ci le model par défaut. Par contre, si on devait compiler pour la version 1, alors il suffit de passer RASPI_MODEL=1 comme argument à make . En plus de définir le processeur approprié, il définit également la directive préprocesseur MODEL_1. Il devient alors facile d’écrire un code pour différent plateforme en utilisant #ifdef MODEL_1 comme directive de préprocesseur.
CFLAGS= -mcpu=$(CPU) -fpic -ffreestanding $(DIRECTIVES) CSRCFLAGS= -O2 -Wall -Wextra LFLAGS= -ffreestanding -O2 -nostdlib
Ce sont exactement les mêmes paramètre passer au compilateur et au linker que nous avons utilisés auparavant, mais extraits dans des variables.
KER_SRC = ../src/kernel KER_HEAD = ../include COMMON_SRC = ../src/common
Ces lignes spécifient les dossiers où résident respectivement les fichiers source du noyau, les fichiers d’en-tête et les fichiers source communs. Ils commencent tous avec ../ car le Makefile sera invoquer depuis le répertoire build.
OBJ_DIR = objects
De même, cette ligne spécifie l’emplacement d’un dossier pour stocker tous les fichiers objets. Ce répertoire n’existe peut-être pas et vous ne devriez pas le créer. Il sera créé par make quand il le faut.
KERSOURCES = $(wildcard $(KER_SRC)/*.c) COMMONSOURCES = $(wildcard $(COMMON_SRC)/*.c) ASMSOURCES = $(wildcard $(KER_SRC)/*.S) ... HEADERS = $(wildcard $(KER_HEAD)/*.h)
Dans chacun de ces variables, nous stockons une liste de tous les fichiers sources du noyau, les fichiers source communs, les fichiers source de l’assembly et les fichiers d’en-tête, respectivement. wildcard est une commande make qui énumère tous les fichiers correspondant au pattern. $(wildcard $(KER_SRC)/*.c) énumére donc tous les fichiers du répertoire KER_SRC qui se termine par .c .
OBJECTS = $(patsubst $(KER_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERSOURCES))
OBJECTS += $(patsubst $(COMMON_SRC)/%.c, $(OBJ_DIR)/%.o, $(COMMONSOURCES))
OBJECTS += $(patsubst $(KER_SRC)/%.S, $(OBJ_DIR)/%.o, $(ASMSOURCES))
Cela prend la liste des fichiers source du noyau, les fichiers sources communes et les fichiers source en assembleur, et les ajoute tous à la liste des fichiers objets compilés qui seront créés. Elle le fait en utilisant patsubst.
patsubst remplace les occurrences du premier paramètre par le second dans la chaîne de caractères contenu dans la troisième paramètre, où les paramètres sont des expressions rationnelles.
Par exemple $(patsubst $(KER_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERSOURCES)) change tous les fichiers qui ressemblent à ../src/kernel/file.c à objects/file.o .
IMG_NAME=kernel.img
Cette variable contient le nom du fichier binaire du noyau.
Règles
Les règles constituent le cœur du Makefile. Chaque règle représente les opérations à exécuter lorsqu’un ensemble de fichiers (les « pré-requis » de la règle) ont été mis à jour depuis le dernier make. Une règle décrit par exemple comment compiler un fichier ou supprimer les fichiers temporaires. Chaque cible a une série de dépendances et une série de commandes à exécuter. Notre fichier makefile possède les cibles suivantes:
build: $(OBJECTS) $(HEADERS) echo $(OBJECTS) $(CC) -T linker.ld -o $(IMG_NAME) $(LFLAGS) $(OBJECTS)
C’est la cible principale qui compile le noyau. Il peut être invoqué par l’exécution de make build. Cela dépend de tous les fichiers objet pour les fichiers de code source respectifs, et tous les fichiers d’en-tête. Cela signifie que tous les fichiers objet doivent être compilés avant que nous exécutons le build. Sa seule tâche est de lier tous les fichiers objets ensemble dans le noyau binaire final.
$(OBJ_DIR)/%.o: $(KER_SRC)/%.c mkdir -p $(@D) $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $@ $(CSRCFLAGS)
Il s’agit de n’importe quel fichier objet, dépend d’un fichier source du noyau. Il crée le répertoire Objects s’il n’existe pas, puis compile le fichier source dans le fichier objet. -I permet aux fichiers source d’être inclus par #include <kernel/header.h> au lieu de #include <../../include/kernel/header/h> . $< est le nom du premier dépendance ( les fichiers source en C). $@ est le nom du fichier cible lui-même.
Nous avons encore deux autres cibles de make qui se resemblent, mais l’une concerne les fichiers en assembleur du noyau, et l’autre concerne les fichiers source communs.
clean: rm -rf $(OBJ_DIR) rm $(IMG_NAME)
Cette cible supprime tous les fichiers binaires compilés afin qu’aucun fichier périmé ne soit accidentellement utilisé lors de la création.
run: build qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel kernel.img
Cela garantit que le noyau est compilé, puis exécute la machine virtuelle avec ce nouveau noyau compilé.
Cela réduit la construction et le processus de test à
make build make run
Cela laisse notre structure de répertoire comme ceci:
piOs/ L__ src/ L__ common/ L__ kernel/ L__ kernel.c L__ boot.S L__ include/ L__ common/ L__ kernel/ L__ build/ L__ Makefile L__ linker.ld
Refactoring de kernel.c
Maintenant, kernel.c contient tout le code source C pour le noyau. Il contient aussi la logique pour configurer UART, et pour faire l’E/S. Reconsidérons ce code pour le rendre plus modulaire.
Tout d’abord, nous allons mettre cette grande énumération de kernel.c qui décrit le périphérique UART, et toutes les signatures de fonctions liées à UART dans include/kernel/uart.h . Ensuite, nous allons déplacer tout le code uart vers src/kernel/uart.c .
Maintenant, nous allons écrire du code de bibliothèque. Nous créons les fichiers src/common/stdlib.c et src/common/stdio.c et les fichiers d’en-tête correspondants.
Dans stdlib.c, nous définissons les fonctions memcpy et bzero, car elles seront utiles plus tard, et nous définissons itoa (integer to ascii) pour faciliter le débogage.
Dans stdio.c , nous définissons getc , putc , gets et puts comme des fonctions d’E/S générales. Nous faisons cela même si uart.c avait uart_putc et uart_puts parce que plus tard, nous allons vouloir extraire uart_putc pour une fonction qui rend le texte à un écran actuall, et il sera plus facile de remplacer un appel à uart_putc dans un fichier que dans plusieurs fichiers.
Maintenant, notre structure de répertoire ressemble à ceci:
piOs/ L__ src/ L__ common/ L__ stdio.c L__ stdlib.c L__ kernel/ L__ kernel.c L__ boot.S L__ uart.c L__ include/ L__ common/ L__ stdio.h L__ stdlib.h L__ kernel/ L__ uart.h L__ build/ L__ Makefile L__ linker.ld
Le code est publiquement disponible sur GitHub.
commentaires
Chargement & hellip;