0x02 – Organiser notre projet

Cet article fait parti de la série Créer un système d'exploitation pour Raspberry Pi ( 3 / 5 )

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.

Navigation<< 0x01 – Le premier boot0x03 – La mémoire >>

What do you think?

23 points
Upvote Downvote

commentaires

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Chargement & hellip;

Intelligence artificielle : c’est quoi ?

0x03 – La mémoire