<-Précédent | Retour à l'accueil | Contact : etienne"point"sauvage"at"gmail.com | Suivant-> |
Parce que quand même, ce n'est pas chic, c'était bien pratique, les interruptions.
C'est vrai, ça, pourquoi les interruptions ? La raison en est toute baignée de considérations calculatoires. Si le processeur devait regarder régulièrement si, par exemple, un caractère est en train d'être entré au clavier, mes pauvres enfants, on ne s'en sortirait pas et on aurait encore bien du mal à faire fonctionner un simulateur de calculette. Pour gagner énormément de temps et pour qu'aucun programmeur n'oublie de vérifier l'entrée clavier, c'est l'inverse que l'on fait : c'est le contrôleur clavier qui interrompt le processeur en lui disant : "Je ne sais pas si cela vous intéresse, mon bon ami, mais j'ai ici une touche qui vient d'être frappée." Ca, c'était pour la poésie. Dans la vraie vie, ça se passe plutôt comme "Touche. -6ème procédure. -STOP ! 6ème procédure. -6ème procédure faite. -OK, j'y retourne." Et la 6ème procédure en question, c'est l'interruption clavier. Et si un contrôleur de clavier peut appeler une interruption, alors un programme aussi. C'est ce détournement que nous avons utilisé précédemment, et c'est même prévu pour être utilisé à outrance.
C'est un programme, sous-programme ou routine, peu importe le nom, dont l'exécution interrompt le cours normal d'un autre programme. Un peu comme quand l'exécution automatique d'un CD se déclenche quand vous êtes en train de rédiger votre site, par exemple. On est interrompu. Cette routine a ceci de particulier qu'elle se termine par l'instruction IRET, comme Interrupt RETurn, instruction qu'on ne trouve qu'en langage assembleur (ou alors d'autres langages parfaitement exotiques). Voici donc notre première routine de gestion d'interruption : entreeInterruption : IRET
. Donc, pour appeler une routine définie comme interruption, on ne peut pas utiliser CALL. Ce sera INT.
Comme d'habitude chez nous, on va faire dans le simple, quitte à bulldozer un brin. Alors, en premier, on doit pratiquer la même chose que pour le passage en mode protégé. C'est-à-dire que l'on doit créer un IDT (Interrupt Descriptor Table), tableau de descripteurs d'interruptions, contenant, comme c'est étrange, des descripteurs d'interruption. L'instruction INT prend un opérande de 8 bits, on peut donc avoir 256 descripteurs d'interruption. Voici comment je fais :
mov eax, IDTBASE ; Adresse de l'IDT mov ebx, interruptionParDefaut ; On va remplir toute la table avec l'interruption par défaut : bx contient les bits de poids faible mov ecx, IDTSIZE ; Nombre de vecteurs d'interruption call metInterruptionNFois lidt [idtptr] ; charge l'idt sti ;;Met l'interruption n fois dans l'idt metInterruptionNFois: mov edx, ebx shr edx, 16 mov [descripteurInterruption], bx ;bx contient les bits de poids faible mov [descripteurInterruption + 6], dx; et dx ceux de poids fort mov edi, eax .remplitIDTPIC: mov esi, descripteurInterruption movsd movsd loop .remplitIDTPIC ret interruptionParDefaut: iret idtptr: dw IDTSIZE << 3 ; limite dd IDTBASE ; base descripteurInterruption: dw 0, 1 * 8, INTGATE, 0
Les périphériques d'un ordinateur compatible PC ne sont pas branchés directement sur le processeur. Ils sont branchés sur un élément qu'on appelle le PIC (Programmable Interruption Controller), le contrôleur d'interruptions programmable. Il y en a deux, l'un piloté par l'autre. Le pilote s'appelle le maître, l'autre l'esclave. La raison d'être du PIc est donc de déclencher des interruptions. Or, il se trouve que dans son nom, il y a "Programable", ce qui doit signifier qu'on peut faire quelques configurations. Le PIC n'étant ni la mémoire ni le processeur, pour y accéder, on va parler :
Notre ordinateur ne fait pas qu'écrire et lire de la mémoire. Il interagit aussi avec des choses, qui sont des appareils électroniques. Cette interaction se fait au travers de 8 fils qui relient tous ces appareils. Le processeur peut écrire et lire sur ce port par les instructions OUT et IN. Et il écrit et lit un octet. L'adresse de l'appareil connecté est donnée en premier paramètre : il faut la connaître. Pour le PIC, il s'agit de 0x20 et 0x21 pour le maître et 0xA0 et 0xA1 pour l'esclave.
C'est la première valeur à envoyer au PIC pour le configurer, notamment pour qu'il pointe vers nos interruptions. Première signifie ici le numéro d'ordre dans la séquence. C'est obligatoirement celle-là. ICW signifie Initialisation Command Word, mot de la commande d'initialisation. Voici sa structure :
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Valeur | A5-A7 | 1 | LTIM | ADI | SNGL | IC4 |
Le PIC occupe deux adresses de port, ICW1 est sur la première.
Source : http://www.thesatya.com/8259.html
Ca va nous donner : 00010001b pour le maître, et 00010001b pour l'esclave.
mov al, 0x11 ; Initialisation de ICW1
out 0x20, al ; maître
out 0xA0, al ; esclave
ICW2 est sur la seconde adresse, et correspond à l'index de la première interruption dédiée au PIC dans l'IDT. Il est indiqué nécessairement après ICW1.
%define INTERRUPTION_PIC_MAITRE 0x20
%define INTERRUPTION_PIC_ESCLAVE 0x70
mov al, INTERRUPTION_PIC_MAITRE ; Initialisation de ICW2
out 0x21, al ; maître, vecteur de départ = 32
mov al, INTERRUPTION_PIC_ESCLAVE
out 0xA1, al ; esclave, vecteur de départ = 96
Pour le PIC maître, chaque bit x mis à 1 indique que l'IRQx est utilisée par un PIC esclave. Pour l'esclave, cela indique le numéro de l'IRQ qu'il utilise chez son maître. ICW3 est sur la seconde adresse. Le standard est de mettre l'esclave sur l'IRQ2.
mov al, 0x04 ; initialisation de ICW3
out 0x21, al
mov al, 0x02 ; esclave
out 0xA1, al
ICW4 est sur la seconde adresse.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Valeur | 0 | SFNM | BUF | M/S | AEOI | Mode |
Dans notre cas, ce sera donc 5 pour le maître et 1 pour l'esclave.
mov al, 0x05 ; initialisation de ICW4
out 0x21, al
mov al, 0x01 ; esclave
out 0xA1, al
Notons que c'est l'ordre des opérations qui permet au PIC de comprendre de quoi on cause. Une fois l'initiation faite, on peut envoyer des OCW, Operational Command Word, mot de commande opérationelle. OCW1 est sur la seconde adresse, et détermine quelles sont les interruptions masquées. Pour utiliser toutes les interruptions :
xor al, al ; masquage des interruptions
out 0x21, al
out 0xA1, al
Lorsqu'un PIC déclenche une interruption, il faut lui dire qu'elle s'est bien passée. Il y a 8 interruptions par PIC, que l'on va traiter ainsi :
interruptionPICParDefaut:
Avant de quitter l'interruption, si elle a été déclenchée par un PIC, on écrit sur le port 0x20 l'octet 0x20, qui signifie "fin de l'interruption" et qui s'appelle EOI.
mov al,0x20 ; EOI (End Of Interrupt)
out 0x20,al ; qu'on envoie au PIC
iret
Une fois tout ceci fait, on a des interruptions que l'on peut utiliser. Malheureusement, il n'y a rien dedans.