Kernel, Clavier et Interrupt Descriptor Table
Pour un projet de programmation, un collègue est moi avons crée de zéro un système d'exploitation (OS) : [OSkour](https://gitlab.aliens-lyon.fr/eharnisc/oskour/). Il s'est occupé de faire le boot loader pendant que je m'occupais de faire le noyau (Kernel).
Parmis les choses à faire dans un kernel, il y a la gestion de l'[affichage](https://wiki.osdev.org/Drawing_In_a_Linear_Framebuffer), les [librairies standards](https://wiki.osdev.org/Creating_a_C_Library) mais aussi et surtout la [gestion du clavier](http://forum.osdev.org/viewtopic.php?t=9746) qui est bien plus compliquée que ce que j'avais imaginé.
Nous nous sommes focalisés sur les processeurs [x86_64](https://en.wikipedia.org/wiki/X86-64), les choses que je vais alors raconter sont entièrement basé sur le fonctionnement de ces processeurs.
## Interruptions
Décomposons le fonctionnement d'un processeur afin de comprendre l'action d'appuyer sur une touche.
Les processeurs [x86](https://en.wikipedia.org/wiki/X86) possèdent des intructions spéciales qu'on appelle des [_interruptions_](https://wiki.osdev.org/Interrupts). Il y a exactement 256 interruptions différentes, chacune avec une signification différente. Une interruption peut être appelée par un disque dur, par un périphérique (ex: un clavier !!) ou par un programme (ex: un SegFault).
À l'appel d'une interruption, le processeur stop tout travail, regarde dans la [Interrupt Descriptor Table (IDT)](https://wiki.osdev.org/Interrupt_Descriptor_Table) l'entrée correspondant à l'interruption et exécute la fonction associée. C'est cette fonction qui décide alors si le processeur peut continuer son travail ou s'il doit s'arreter (après un SegFault par exemple).
### Remplissage de la IDT
Afin de faire fonctionner le clavier (l'appui d'une touche est associée à l'interruption 33), il faut alors mettre en place la IDT. La table contient 256 entrées, chaque entrée doit vérifier [cette décomposition](https://wiki.osdev.org/Interrupt_Descriptor_Table#Structure_on_x86-64) (dépendante fortement de la plateforme x86_64) :

Expliquons les valeurs :
- BaseLow : 16 premiers bits du pointeur vers la fonction à appeler
- Selector : Un [Segment Selector](https://wiki.osdev.org/Segment_Selector) qui pointe vers un segment de code valide de la [GDT](https://wiki.osdev.org/Global_Descriptor_Table)
- Ist : On l'initialise à 0 pour notre effet. Voir [Interrupt Stack Table](https://wiki.osdev.org/Interrupt_Descriptor_Table#Structure_on_x86-64)
- Reserved : Idem à 0
- Gate Type : 4 bits représentant le type de l'interruption, soit 0xE pour une interruption, 0xF pour un trap. Voir [Gates](https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Types)
- DPL : 2 bits pour donner le niveau de privilège pour exécuter la fonction
- P : bit de présence (dit au processeur s'il peut utiliser l'entrée)
- BaseHigh : 16 bits suivant du pointeur vers la fonction
- Offset : 32 bits restant du pointeur vers la fonction
- Zeros : 32 bits à zero
On peut alors proposer la structure C suivante modélisant une entrée :
```C
typedef struct {
uint16_t BaseLow;
uint16_t SegmentSelector;
uint8_t IstReserved;
uint8_t Flags;
uint16_t BaseHigh;
uint32_t Offset;
uint32_t Zeros;
} __attribute__((packed)) IDTEntry;
```
La partie `__attribute__((packed))` est une instruction pour GCC qui lui indique de stocker `IDTEntry` telle quelle dans la mémoire, afin donc d'avoir exactement les 128 bits décrit dans le tableau plus haut sans superflu.
Pour que la structure soit valide, il faut au moins que les 32 premières entrées de la table soient associées à une fonction valide (qui peut juste dire d'arrêter le processeur, mais elles doivent être présente).
Pour dire au procésseur où se trouve la table, il est nécessaire de faire de l'assembleur comme pour la [GDT](https://wiki.osdev.org/GDT_Tutorial) avec l'instruction `LIDT` au lieu de `LGDT` avec la pointeur vers la table :
En C :
```C
typedef struct {
uint16_t Limit;
IDTEntry* Ptr;
} __attribute__((packed)) IDTDescriptor;
IDTEntry g_IDT[256];
IDTDescriptor g_IDTDescriptor = { sizeof(g_IDT) - 1, g_IDT };
extern void IDT_load(IDTDescriptor* descriptor);
```
Et en assembleur (ce code définit une fonction `void IDT_load(IDTDescriptor*)`)
```asm
[bits 64]
global IDT_load
IDT_load:
cli
lidt [rdi]
sti
ret
```
### Les fonctions gérant les interruptions
Comme dit dans le [wiki](https://wiki.osdev.org/Interrupt_Service_Routines), il est nécessaire de faire de l'assembleur pour au moins gérer l'appel à la fonction `handler_int_k()` et la fin de son appel (cette fonction gère l'interruption _k_ après son appel). Parmis les choses importantes, il faut surtout (en x86_64) finir la fonction assembleur par un `iretq` et non `iret`.
La façon dont on gère les interruptions dans OSkour est en ayant une structure représentant les registres du CPU, que l'on passe à la fonction `handler_int_k`. Comme ça, dans celle gérant le clavier, on peut recupérer la touche activée directement dans les registres.
## Gestion de l'appuie d'une touche
Wow, maintenant qu'on a une IDT qui fonctionne (avec un peu plus de travail que ce qu'on a dit plus haut), on peut gérer le clavier.
Disons que la touche `a` a été appuyée, donc le processeur appelle la fonction `handler_int_33(regs)` avec `regs` représentant les registres du CPU à l'appel de la fonction.
On opte pour une gestion des touches via un file circulaire : si une touche est appuyée et que ce n'est pas la touche `Shift` ou `Maj` on l'ajoute dans la file, puis c'est au programme actuellement en train d'utiliser le clavier de defiler les touches afin de les traiter (traitement différent si c'est la touche `retour` ou la touche `a`).
L'aspect circulaire de la file n'a aucune réelle utilité mise à part que l'on a pas à gérer le tas via les mallocs comme avec une file en liste chainée (on n'a pas implémenté les malloc dans notre librairie C encore).
La gestion de la disposition du clavier peut être fait à partir de là, voir [ici](https://wiki.osdev.org/PS/2_Keyboard) pour la gestion de toutes les touches(lettres et touches spéciales), les lettres selon le clavier qwerty.
## Sources :
- [OSDev](https://wiki.osdev.org/Expanded_Main_Page) : Le wiki du développement de système d'exploitation
- [KeblaOS](https://github.com/baponkar/KeblaOS/) : Le code source d'un OS ayant implémenté la gestion du clavier
Le 21 décembre 2025 15:33,
modifié le 21 décembre 2025 15:34.