IX. Assembleur : mode protégé - Retrouver ses interruptions▲
Relu par ClaudeLELOUP.
Parce que quand même, ce n'est pas chic, c'était bien pratique, les interruptions.
IX-1. Au fait, pourquoi a-t-on inventé 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. -6e procédure. -STOP ! 6e procédure. -6e procédure faite. -OK, j'y retourne." Et la 6e 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.
IX-2. Et une interruption, c'est quoi ?▲
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.
IX-3. Comment j'en fais ?▲
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'interruption, 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
Notez l'instruction LIDT qui fait exactement la même chose que LGDT mais pour l'IDT, et le rétablissement des interruptions l'instant d'après. Notez aussi que nous travaillons en 32 bits, gloria alleluia.
IX-4. Du PIC▲
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 (Programable Interruption Controller), le contrôleur d'interruption 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 :
IX-4-a. Du port d'entrée/sortie▲
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.
IX-4-b. ICW1▲
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 |
- IC4: 0 = Pas de ICW4, 1 = ICW4 requis.
- SNGL: 1 = Un seul PIC, 0 = D'autres PIC sont montés en cascade.
- ADI: Intervalle d'adressage. Utilisé uniquement dans les 8085, pas dans les 8086 (donc pas pour nous).
- LTIM: Mode de déclenchement des interruptions: 1 = toutes les lignes de requête d'interruption sont déclenchées par niveau. 0 = elles sont déclenchées par front. Pour nous, 0 c'est bien.
- A5-A7: Utilisé uniquement dans les 8085.
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
IX-4-c. ICW2▲
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.
?fine INTERRUPTION_PIC_MAITRE 0x20
?fine 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
IX-4-d. ICW3▲
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
IX-4-e. ICW4▲
ICW4 est sur la seconde adresse.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Valeur | 0 | SFNM | BUF | M/S | AEOI | Mode |
- SFNM: 1 = Special Fully Nested Mode, 0 = Fully Nested Mode. Pas de spécial pour moi, merci.
- M/S: 1 = Maître, 0 = eSclave
- AEOI: 1 = Auto End of Interrupt (fin automatique d'interruption), 0 = Normal.
- Mode: 0 = 8085, 1 = 8086
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
IX-4-f. OCW1▲
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érationnelle. 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
IX-4-g. Fin de traitement d'interruption▲
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:
mov
al
,0x20
; EOI (End Of Interrupt)
out
0x20
,al
; qu'on envoie au PIC
iret
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.
Une fois tout ceci fait, on a des interruptions que l'on peut utiliser. Malheureusement, il n'y a rien dedans.