Les interruptions et leur gestionnaire

Eric Sigoillot

Comment appeler une interruption ?

Comment créer ou remplacer un gestionnaire ?

Commentez

- Titre : Les interruptions et leur gestionnaire

- Auteur : Eric Sigoillot

- Parution : 27 mars 2004

- Mise à jour : 22 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2004 Eric Sigoillot. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Avant-propos

- I. Qu'est-ce qu'une interruption ?

- I-A. Les interruptions matérielles

- I-B. Les interruptions logicielles

- I-C. Les gestionnaires d'interruption

- II. Comment appeler une interruption ?

- II-A. Les moyens offerts par le Pascal

- II-A-1. L'interruption Dos 21h

- II-A-2. Accéder à toutes les interruptions

- II-A-3. Remplacer la procédure MsDos

- II-A-4. Les interruptions qui nécessitent une zone mémoire

- II-A-4-a. Si la zone mémoire est statique

- II-A-4-b. Si la zone mémoire est dynamique

- II-B. Utiliser l'assembleur

- II-B-1. Les registres à sauver

- II-B-2. L'utilisation des buffers

- II-B-2-a. Les buffers statiques

- II-B-2-a-i. Variable globale

- II-B-2-a-ii. Variable locale

- II-B-2-b. Les buffers dynamiques

- II-B-3. Les opérateurs segment et ofs

- III. Ce qu'il faut savoir avant de modifier un gestionnaire d'interruption

- III-A. Les modes d'appel

- III-B. La directive interrupt

- III-C. Interruption matérielle ou non ?

- III-C-1. La notification de fin de traitement

- III-C-2. Le registre Flags

- III-C-3. Résumé

- IV. Comment créer ou remplacer un gestionnaire d'interruption ?

- IV-A. Récupérer ou modifier l'adresse d'un vecteur d'interruption

- IV-A-1. Retrouver l'adresse d'un vecteur d'interruption

- IV-A-2. Remplacer un vecteur d'interruption

- IV-B. Rassemblement des connaissances

- Conclusion

Avant-propos

Dans cet article vous apprendrez ce que sont les interruptions, comment les appeler, les gérer, et remplacer leurs gestionnaires par les vôtres.

Les interruptions relèvent de la programmation système. Comme tout ce qui a trait à ce domaine, il convient de rester prudent et de ne surtout pas jouer à l'apprenti. Toute erreur peut rapidement se révéler catastrophique et résulter en une perte de données, si ce n'est une perte matérielle.

Bien que tout ait été fait pour éviter le moindre problème, l'auteur ne saurait être tenu responsable d'une quelconque perte logicielle ou matérielle suite à l'utilisation des informations contenues dans cet article.

I - Qu'est-ce qu'une interruption ?

Les interruptions, comme leur nom l'indique, permettent d'interrompre le processeur alors qu'il est en train d'effectuer une tâche quelconque. Le système est ainsi doté de 256 interruptions matérielles et logicielles, toutes pouvant requérir un moment d'attention de la part du système.

Il existe deux types d'interruptions : les interruptions dites matérielles, et les interruptions dites logicielles.

I-A - Les interruptions matérielles

Il existe en standard 16 interruptions matérielles : les IRQ, ou Interruption Request.

Les IRQ sont assignées au matériel : le processeur, la mémoire, les cartes d'extension, les périphériques, etc. Elle lui donne la possibilité d'interrompre le processeur lorsqu'il requiert un traitement immédiat. Par exemple, l'horloge système peut demander au processeur de mettre à jour l'heure, ou le clavier peut signifier l'appui sur une touche.

Étant en quantité limitée, les IRQ ont longtemps été la cause de conflits matériels, car une IRQ ne peut être partagée que sous des conditions très strictes, et la plupart du temps, un tel partage résulte en un plantage du système.

Les utilisateurs de Windows peuvent se faire une idée du l'utilisation de leurs IRQ en allant dans les Propriétés système, puis en ouvrant le Gestionnaire de périphériques. Recherchez par exemple votre clavier dans la liste, puis choisissez Propriétés et regardez l'onglet Ressources.

Il est à noter que chaque interruption matérielle est associée à une interruption logicielle.

I-B - Les interruptions logicielles

Il existe en tout 256 interruptions logicielles, dont 16 liées à une IRQ (voir tableau). Tout comme les interruptions matérielles, les interruptions logicielles permettent d'interrompre le processeur à tout moment pour requérir son attention.

Toutefois, les interruptions logicielles sont souvent utilisées pour les API (Application Programming Interface). Elles offrent un accès à un panel de routines effectuant diverses actions. On peut ainsi prendre l'exemple de l'API Dos, interruption 21h, ou bien de l'API Vidéo, interruption 10h.

Pour plus d'informations sur les interruptions existantes, je vous renvoie à l'excellente bibliothèque de Ralph Brown, la Ralph Brown's Interrupt List.

Tableau récapitulatif IRQ/Interruptions logicielles

IRQ

Interruption

IRQ0 (timer système)

08h

IRQ1 (clavier)

09h

IRQ2 (port LPT2)

0Ah

IRQ3 (port COM2)

0Bh

IRQ4 (port COM1)

0Ch

IRQ5 (disque dur)

0Dh

IRQ6 (lecteur de disquettes)

0Eh

IRQ7 (port LPT1)

0Fh

IRQ8 (horloge temps réel)

70h

IRQ9 (asservissement PIC1)

71h

IRQ10

72h

IRQ11

73h

IRQ12 (souris PS/2)

74h

IRQ13 (erreur du coprocesseur)

75h

IRQ14 (contrôleur de disques IDE 1)

76h

IRQ15 (contrôleur de disques IDE 2)

77h

I-C - Les gestionnaires d'interruption

Chaque interruption est liée à un gestionnaire d'interruption (ou vecteur d'interruption), un petit programme qui est exécuté à chaque fois qu'une interruption est déclenchée. De fait, on confond souvent l'interruption en elle-même et son gestionnaire associé. Toutes les adresses des gestionnaires d'interruption sont stockées au tout début de la mémoire de l'ordinateur, en 0000:0000 : c'est la table des vecteurs d'interruption.

Du fait de la capacité d'une interruption à interrompre le processeur à n'importe quel moment (on peut se rendre compte de la nécessité d'un tel comportement pour une défaillance matérielle, par exemple), un gestionnaire se doit de conserver le système dans le même état avant et après une interruption, et notamment de sauver le contenu des registres système.

Autre nécessité pour un gestionnaire d'interruption : la capacité à appeler le gestionnaire précédent qu'il aurait remplacé. En effet, on ne peut savoir ce qui est « branché » sur une interruption, et de fait, il convient de conserver la chaîne des gestionnaires sans quoi on pourrait mettre en danger la stabilité du système. Ainsi, à moins de vouloir créer un nouveau gestionnaire, il faudra toujours conserver la liaison avec le gestionnaire remplacé.

Il faut enfin savoir que certaines interruptions peuvent être masquées, autrement dit désactivées si besoin est, en utilisant les ports 21h et A1h (voir la Ralph Brown's Interrupt List).

Veillez à ne toucher à aucun port que vous ne connaîtriez pas. Les ports sont en communication directe avec le système. Une mauvaise manipulation peut conduire à une défaillance matérielle.

II - Comment appeler une interruption ?

Le langage Pascal, bien que puissant, n'inclut pas toutes les capacités d'un ordinateur. De plus, si les PC évoluent tout le temps, il n'en est pas de même pour les langages informatiques, pour lesquels il faut parfois attendre longtemps avant qu'ils ne supportent certaines nouveautés. Le meilleur exemple est peut-être le cas des extensions MMX apparues avec les Pentium Pro, qui n'ont été supportées par le Pascal que plusieurs années après leur sortie.

Toutefois, il ne s'agit pas ici d'accéder aux nouvelles instructions offertes par nos processeurs. Nous allons nous contenter d'appeler des interruptions, qui permettent de faire des choses que le Pascal seul ne sait pas faire : utiliser plus de mémoire, accéder à des modes graphiques évolués, se servir les ports de communication, etc.

II-A - Les moyens offerts par le Pascal

Le Pascal, s'il ne permet d'accéder de manière directe à tous les éléments de votre PC, vous offre néanmoins la possibilité de le faire vous-même, en mettant quelques outils à votre disposition.

La plupart des compilateurs Pascal disposent de deux procédures pour faire appel à une interruption. Toutefois, la deuxième n'est qu'un cas particulier de la première, et nous verrons un peu plus loin comment la remplacer rapidement.

II-A-1 - L'interruption Dos 21h

Le Dos possède une interruption privilégiée, l'interruption 21h, qui offre un accès à toute l'API du système d'exploitation. C'est elle qui permet de faire des allocations mémoire, d'effectuer des opérations sur les fichiers, ou encore récupérer les entrées du clavier.

Du fait de l'importance de cette interruption, le Pascal a créé une procédure qui lui est destinée : il s'agit de la procédure MsDos(var Reg: Registers), déclarée dans l'unité Dos. Le type Registers que vous découvrez ici est un enregistrement rassemblant tous les registres système contenant des paramètres destinés à l'interruption, et servant aux valeurs de retour de celle-ci. Il est déclaré comme ceci :

type
Registers = record
case Integer of
0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Word);
1: (AL, AH, BL, BH, CL, CH, DL, DH: Byte);
end;

Comme vous pouvez le remarquer, il s'agit d'un enregistrement conditionnel. Si vous rencontrez cette manière de déclarer un enregistrement pour la première fois, je vous conseille d'aller consulter un tutoriel sur le sujet sur le site de Cyberzoïde.

Le type Registers va vous permettre de passer vos paramètres à l'interruption de manière totalement transparente, et sans difficulté. Une fois l'interruption appelée, il vous suffira de lire les valeurs retournées dans l'enregistrement.

Comme toute API, on accède à une fonction soit par son nom, soit par son index. Comme nous ne manions que des registres système avec le système des interruptions, on utilisera donc un système d'index. Le numéro de la fonction désirée sera toujours passé dans le registre AH. Ensuite, l'utilisation des registres est spécifique à chaque fonction.

Pour une description détaillée de la plupart des interruptions et fonctions, je vous engage une fois de plus à vous procurer la Ralph Brown's Interrupt List.

L'exemple suivant recrée la procédure GetDate de l'unité Dos. Comme vous pourrez le remarquer, il est très aisé de la reprogrammer. C'est la fonction 2Ah de l'interruption 21h qui se charge de cette opération.

uses
Dos;

procedure MyGetDate(var Year, Month, Day, DayOfWeek: Word);
var
Regs: Registers;
begin
Regs.AH := $2A;   { On indique le numéro de la fonction }
MsDos(Regs);      { On appelle l'interruption 21h }
{ Pour la fonction 2Ah, les résultats sont répartis comme suit : }
{  CX: année (de 1980 à 2099)                                    }
{  DH: mois                                                      }
{  DL: jour                                                      }
{  AL: jour de la semaine (0 correspond à dimanche)              }
with Regs do
begin
Year := CX;
Month := DH;
Day := DL;
DayOfWeek := AL;
end;
end;

Télécharger l'exemple complet

II-A-2 - Accéder à toutes les interruptions

Bien entendu, on ne saurait se restreindre à l'interruption 21h lorsque l'on veut atteindre le cœur du système. Pour pouvoir appeler n'importe quelle interruption, il suffit de se servir de la procédure Intr(IntNo: Byte; var Regs: Registers). Son fonctionnement est en tout point identique à celui de MsDos, si ce n'est qu'il faut fournir le numéro de l'interruption à appeler.

En général, les interruptions servent d'interface à une API. Il faut donc fournir l'index de la fonction à appeler. Si l'habitude veut que l'on place cet index dans le registre AH, il convient de rester prudent. Ce n'est pas une règle ! Par conséquent, on se reportera toujours à la documentation.

Pour de plus amples descriptions de toutes les interruptions, consultez la Ralph Brown's Interrupt List.

L'exemple suivant initialise le mode vidéo 320x200 en 256 couleurs (le mode 13h) puis repasse en mode texte (le mode 03h). Pour ce faire, on fait appel à l'interruption vidéo 10h, fonction 00h. Le mode vidéo est passé sur le registre AL.

uses
Dos;

var
Regs: Registers;

begin
Regs.AH := $00;
Regs.AL := $13;
Intr($10, Regs);

WriteLn('Test du mode 13h... Appuyer sur Entree');
ReadLn;

Regs.AH := $00;
Regs.AL := $03;
Intr($10, Regs);
end;

Télécharger l'exemple

II-A-3 - Remplacer la procédure MsDos

Il est très simple de remplacer la procédure MsDos si celle-ci n'est pas fournie avec votre compilateur. Il suffit de se servir de la procédure Intr :

procedure MsDos(var Regs: Registers);
begin
Intr($21, Regs);
end;

II-A-4 - Les interruptions qui nécessitent une zone mémoire

Certaines interruptions ont besoin d'une zone mémoire (ou buffer) lorsqu'elles doivent fournir un nombre important d'informations qui ne peuvent toutes tenir sur les registres. On peut ainsi prendre l'exemple de la fonction 0Ah de l'interruption 21h, qui permet de lire des caractères tapés au clavier suivis de la touche Entrée. Dans ce cas, vous verrez dans la documentation des paramètres d'entrée de la forme :

DS:DX  Buffer

Cela signifie qu'il faut placer l'adresse d'une zone mémoire réservée par notre programme, en plaçant son segment dans DS, et son offset dans DX. Si vous ne savez pas ce que sont les segments et offsets, allez consulter la F.A.Q., à Qu'est-ce qu'un segment ?. Dès lors, deux cas sont à prendre en compte…

II-A-4-a - Si la zone mémoire est statique

Si vous désirez passer en mémoire un buffer statique (par exemple un tableau déclaré en tant que simple variable, pas de pointeur), alors vous vous servirez des fonctions Seg et Ofs de la manière suivante :

var
Regs: Registers;
Buffer: array[0..9] of Char;
...
begin
...
Regs.DS := Seg(Buffer);
Regs.DX := Ofs(Buffer);
...

Il est entendu qu'il s'agit ici d'un exemple. Souvent, vous devrez passer l'adresse du buffer soit dans DS:DX, soit dans ES:DI. Mais d'autres combinaisons sont possibles. De plus, le buffer doit s'adapter au format attendu par l'interruption. Dans le cas de notre exemple précédent, la fonction 0Ah de l'interruption 21h, il faudrait déclarer notre buffer de la manière suivante :

var
Buffer: record
MaxCh: Byte;
LastCh: Byte;
ReadCh: array[1..255] of Char;
end;

II-A-4-b - Si la zone mémoire est dynamique

Si la zone mémoire a été allouée dynamiquement (un pointeur, alloué avec New ou GetMem), alors il faut faire attention lors de l'utilisation des fonctions Seg et Ofs. En effet, si l'on se reporte à la F.A.Q., Qu'est-ce qu'un pointeur ?, alors il faut bien faire attention à transmettre l'adresse de la zone mémoire, et non celle du pointeur ! C'est pourquoi il faudra toujours déréférencer votre pointeur lors de l'utilisation de Seg et Ofs :

var
Regs: Registers;
P: Pointer;
...
begin
...
GetMem(P, 1024);    { On alloue par exemple 1 Ko à P }
...
Regs.DS := Seg(P^); { ATTENTION ! Il faut déréférencer le pointeur ! }
Regs.DX := Ofs(P^);
...

J'insiste encore une fois sur la nécessité du déréférencement, car c'est une source d'erreurs et de plantages très fréquente si on ne fait pas attention…

II-B - Utiliser l'assembleur

Il est très courant d'avoir recours à l'assembleur pour l'appel des interruptions. Il n'est pas prévu ici de faire un cours sur l'assembleur. Nous n'aborderons donc que les points les plus importants, principalement concernant la mise en place des registres.

Pour faire appel à une interruption, on se servira de l'opcode int suivie du numéro de l'interruption désirée :

asm
...
int IntNo
...
end;

Certains éléments importants sont à prendre en compte lors de la modification des registres. Nous allons les voir point par point.

II-B-1 - Les registres à sauver

Certains registres sont très importants du point de vue de votre programme. Il s'agit des registres :

- CS: le segment de code ;

- DS: le segment de données ;

- SS: le segment de pile ;

- BP: le pointeur de la base de la pile ;

- SP: le pointeur du haut de la pile.

Ces registres devront toujours être sauvés s'ils sont susceptibles d'être modifiés soit par vous, soit par l'interruption, et bien sûr devront être restaurés une fois l'opération terminée.

asm
...
push  bp
mov   bp, ax
...
pop   bp
...
end;

II-B-2 - L'utilisation des buffers

Comme on a pu le voir précédemment, il est courant d'avoir recours aux buffers. Toutefois, la difficulté est plus importante lorsque l'on se sert de l'assembleur.

II-B-2-a - Les buffers statiques

Lorsque l'on se sert d'un buffer statique, deux cas de figure sont à prendre en compte :

- soit le buffer est une variable globale, déclarée dans le programme principal ;

- soit le buffer est une variable locale, déclarée dans une procédure ou une fonction.

II-B-2-a-i - Variable globale

Lorsqu'une variable est globale, elle possède pour segment le segment de données, à savoir DS. Du fait de cette particularité, sa gestion est conditionnée, et il n'est pas possible de faire appel à l'instruction les, qui est pourtant courante pour ce genre d'opérations, à supposer que l'interruption attende un buffer pointé par ES:DI. À la place, on utilisera le code suivant, car il n'est pas possible de copier directement la valeur d'un registre de segment sur un autre :

var
Buffer: array[0..9] of Byte;
...

begin
...
asm
...
mov  ax, ds
mov  es, ax
lea  di, Buffer
...
end;
...
end.

Bien entendu, si l'interruption désire un buffer pointé par DS:DX par exemple, il ne sera nullement nécessaire de redéfinir ES, le registre de segment étant déjà paramétré comme il convient.

II-B-2-a-ii - Variable locale

Les variables locales ne posent pas ce genre de problème. Une instruction comme les di, Buffer sera parfaitement autorisée par le compilateur. Si besoin est, il faut savoir que les variables locales sont situées sur le segment de pile, c'est-à-dire SS :

procedure Exemple;
var
Buffer: array[0..9] of Byte;
...
begin
...
asm
...
les  di, Buffer
...
end;
...
end;

II-B-2-b - Les buffers dynamiques

Les buffers dynamiques, qu'ils soient gérés par le Pascal ou bien l'assembleur, posent toujours les mêmes problèmes. Il faut bien s'assurer de fournir l'adresse de la zone mémoire, et non celle du pointeur. En général, une instruction comme les di, Buffer ne devrait causer aucun souci :

var
Buffer: Pointer;
...
begin
GetMem(Buffer, 1024);
...
asm
...
les  di, Buffer
...
end;
...
FreeMem(Buffer, 1024);
end;

Si toutefois vous deviez rencontrer des problèmes, alors souvenez-vous que le type Pointer est constitué de deux Word, et peut être transtypé ainsi :

type
PtrRec = record
Offset, Segment: Word;
end;

Ainsi, on peut écrire le code suivant pour accéder à la zone mémoire :

asm
...
mov  es, word ptr Buffer+2
mov  di, word ptr Buffer
...
end;

II-B-3 - Les opérateurs segment et ofs

On peut noter la présence des opérateurs segment et ofs dans les blocs assembleurs pour connaître le segment et l'offset d'une variable. Attention toutefois lors de l'utilisation de ces opérateurs avec des variables dynamiques. Ils renverront alors l'adresse du pointeur vers le buffer, et non celle du buffer.

var
Buffer: array[0..9] of Byte;
...
begin
...
asm
...
mov  es, segment Buffer
mov  di, ofs Buffer
...
end;
...
end;

Si ces opérateurs n'ont pas été mentionnés plus tôt, c'est qu'ils sont spécifiques à Turbo Pascal. Si vous les utilisez, vous perdez la portabilité de votre code.

III - Ce qu'il faut savoir avant de modifier un gestionnaire d'interruption

Si chaque interruption est liée à un seul gestionnaire, rien n'empêche le programmeur de remplacer le gestionnaire en place par un qu'il aurait créé lui-même. Avant de s'attaquer à ce problème, concentrons-nous sur les éventuels problèmes existants.

III-A - Les modes d'appel

Pour une procédure, deux modes d'appel existent : le mode d'appel near, et le mode d'appel far. Si l'on se réfère à la FAQ, Appels far, appels near, qu'est-ce que cela signifie ?, il convient d'utiliser le mode d'appel far pour notre gestionnaire d'interruption, car il sera susceptible d'être appelé depuis n'importe quelle position de la mémoire.

De fait, notre procédure devra être placée entre les directives de compilation {$F+} et {$F-}.

III-B - La directive interrupt

Comme il a été dit plus haut, un vecteur d'interruption se doit de sauvegarder tous les registres système pour les restaurer à la sortie. S'il est tout à fait possible de réaliser ceci par soi-même, autant laisser le Pascal le faire pour nous. Pour cela, il suffit d'ajouter la directive interrupt à notre procédure :

{$F+}
procedure IntVector; interrupt;
begin

end;
{$F-}

Néanmoins, comme on l'a vu, les gestionnaires d'interruption sont souvent utilisés pour gérer une API. Dès lors, il devient nécessaire de savoir à quelle fonction l'utilisateur souhaite faire appel, et quels sont les paramètres de cette fonction.

Pour cela, on se sert plus facilement des registres que de la pile. Il est donc nécessaire de pouvoir accéder aux registres système sauvés lors de l'entrée dans le gestionnaire, et de pouvoir les modifier si le besoin s'en fait sentir, pour retourner un code d'erreur par exemple.

En fait, la syntaxe vue plus haut utilisant la directive interrupt est incomplète. En effet, le Pascal nous offre un accès direct aux registres en utilisant cette directive. Les registres devront alors être déclarés comme de simples paramètres de type Word, mais en respectant un ordre précis, et en prenant garde au fait que ceux-ci se comportent comme des paramètres passés par adresse, alors que leur déclaration ne le laisse pas apparaître. Ainsi, la moindre modification d'un paramètre registre entraînera une répercussion sur sa valeur réelle.

L'ordre de déclaration est : Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP. On obtient donc un gestionnaire qui a la forme suivante :

{$F+}
procedure IntVector(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP: Word); interrupt;
begin

end;
{$F-}

Les registres pourront être omis, mais devront tous figurer sitôt que vous désirez accéder à au moins l'un d'entre eux.

III-C - Interruption matérielle ou non ?

Les interruptions matérielles et logicielles ne se comportent pas de la même manière. En effet, les interruptions matérielles se doivent de signifier au système lorsqu'elles ont terminé leur traitement, et utilisent le registre Flags sur la pile.

III-C-1 - La notification de fin de traitement

Une interruption matérielle doit indiquer au système lorsque son traitement est terminé. Pour cela, il faut savoir quelle interruption matérielle a été détournée. En effet, il convient d'envoyer la valeur EOI (End Of Interrupt) = $20 au registre de contrôle des interruptions : le PIC (Programmable Interrupt Controller).

La valeur du port dépend du numéro de l'IRQ :

IRQ

Port du PIC

0..7

20h (PIC 1)

8..15

A0h (PIC 2) et 20h (PIC 1)

Ainsi, à la fin du gestionnaire, il conviendra d'ajouter :

Port[$20] := $20;

ou

Port[$A0] := $20;
Port[$20] := $20;

Remarques

- Si en cas de détournement de gestionnaire on appelle l'ancien gestionnaire, alors il ne faut pas notifier la fin de l'interruption. C'est l'ancien gestionnaire qui s'en charge !

- On peut remarquer que pour les IRQ 8 à 15, il est nécessaire d'appeler les 2 PIC. En effet, le PIC 2 est asservi au PIC 1 à travers l'IRQ 9. L'esclave se doit donc d'informer le maître de la fin d'interruption qui lui a été notifiée.

III-C-2 - Le registre Flags

Les interruptions matérielles utilisent normalement le registre Flags. Ceci n'est pas gênant en soi, mais il convient de prendre garde lors de la redirection d'un gestionnaire de bien replacer sur la pile le registre Flags avant d'appeler l'ancien gestionnaire.

Pour placer le registre Flags sur la pile, il faut faire appel à l'assembleur, et à l'instruction pushf. On appellera donc l'ancien gestionnaire de la manière suivante :

if Assigned(AncienIRQ) then
begin
asm
pushf
end;
AncienIRQ;
end;

Remarque

- Il est possible de rencontrer inline($9C); à la place du pushf. C'est tout à fait normal, car $9C est le code hexadécimal associé à pushf.

III-C-3 - Résumé

Pour résumer, lorsque l'on détourne une interruption matérielle, il faut ajouter à la fin de son gestionnaire le code :

if Assigned(AncienIRQ) then
begin
asm
pushf
end;
AncienIRQ;
end
else
begin
{Port[$A0] := $20; si IRQ >= 8 }
Port[$20] := $20;
end;

IV - Comment créer ou remplacer un gestionnaire d'interruption ?

IV-A - Récupérer ou modifier l'adresse d'un vecteur d'interruption

Les procédures nécessaires à la récupération ou la modification d'un vecteur d'interruption se situent dans l'unité Dos. On l'ajoutera donc dans notre clause uses.

program Test;

uses
Dos;

...

IV-A-1 - Retrouver l'adresse d'un vecteur d'interruption

Il faut se servir de la procédure GetIntVec(IntNo: Byte; var Vector: Pointer); où :

- IntNo : représente le numéro de l'interruption ;

- Vector : représente l'adresse du vecteur d'interruption courant.

On peut remarquer que Vector est un Pointer. Or, en général, on se sert d'une procédure. Dans ce cas, on utilisera l'opérateur @ :

var
IntVec: procedure;

begin
GetIntVec($09, @IntVec);
...

IV-A-2 - Remplacer un vecteur d'interruption

Pour remplacer un vecteur d'interruption, on se servira de SetIntVec(IntNo: Byte; vector: Pointer); où :

- IntNo : représente le numéro de l'interruption ;

- Vector : représente l'adresse du nouveau gestionnaire. L'ancien est définitivement perdu.

On remarquera ici aussi que Vector est un Pointer et non une procédure. On se servira donc de l'opérateur @ avec un gestionnaire déclaré comme interrupt et prenant en compte toutes les remarques évoquées jusqu'ici.

{$F+}
procedure IntVector; interrupt;
begin
...
end;
{$F-}

begin
SetIntVec($09, @IntVector);
...

IV-B - Rassemblement des connaissances

Nous avons à présent réuni tous les éléments nécessaires pour la rédaction de notre gestionnaire d'interruption.

L'exemple suivant est totalement fonctionnel : il concerne la surcharge du vecteur d'interruption du clavier, interruption 9, correspondant à l'IRQ 1. Il s'agit donc d'une interruption matérielle, dont le PIC est situé sur le port $20.

program TestIntClavier;

uses
Dos;

{ Pour sauver l'ancien gestionnaire }
var
OldKbdInt: procedure;

{$F+}
procedure KbdInt; interrupt;
begin
{ En rapport avec le clavier, hors de propos ici }
WriteLn('Touche : ', Port[$60]);

{ Si un ancien gestionnaire existait... }
if Assigned(OldKbdInt) then
begin
{ On place le registre de Flags sur la pile (int matérielle) }
asm
pushf
end;
{ On appelle l'ancien gestionnaire }
OldKbdInt;
end
else
{ Sinon, on notifie la fin de l'interruption (int matérielle) }
Port[$20] := $20;
end;
{$F-}

begin
{ On récupère l'ancien gestionnaire pour l'int 9 }
GetIntVec($09, @OldKbdInt);
{ On définit le nouveau gestionnaire }
SetIntVec($09, @KbdInt);

ReadLn;

{ On rétablit l'ancien gestionnaire à la fin du programme }
SetIntVec($09, @OldKbdInt);
end.

Télécharger l'exemple

Remarque

- On n'oubliera pas de restaurer l'ancien gestionnaire à la fin du programme, car sinon, l'interruption serait reliée à une procédure qui ne serait plus en mémoire : plantage assuré !

Conclusion

Vous avez appris ici à :

- appeler une interruption ;

- récupérer l'adresse d'un vecteur d'interruption et à le remplacer par le vôtre.

Comme tout ce qui a trait à la programmation système, il convient de rester prudent lors de détournements, car il est toujours possible de causer une perte matérielle. D'une manière générale, on s'interdira toute modification des interruptions matérielles, à moins d'être sûr de ce que l'on fait et d'être sûr que la modification n'entraînera pas de dommages trop importants. L'interruption 9 (IRQ 1) liée au clavier pourra être modifiée sans trop de problèmes (risque de perte du contrôle clavier).

Il est exclu de modifier l'IRQ 14 (interruption 76h) qui contrôle le disque dur. Vous risqueriez une perte irréversible de données.

Si vous programmez en Mode Protégé (DPMI) ou sous Windows, certaines précautions sont à prendre. Reportez-vous aux spécifications DPMI avant de tenter quoi que ce soit (nécessité de protéger et de verrouiller le code en mémoire, etc.).

Bonne programmation !