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 pertes 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.

1. 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'interruption : les interruptions dites matérielles, et les interruptions dites logicielles.

1.1. 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.

Etant 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èmes, 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.

1.2. 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'information sur les interruptions existantes, je vous renvoie à l'excellente librairie 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

1.3. 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èmes.

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.

2. Comment appeler une interruption ?

Le langage Pascal, bien que puissant, n'inclus 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 sorties.

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...

2.1. 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.

2.1.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 allocation mémoire, d'effectuer des opérations sur les fichiers, ou encore récupérer les entrées du clavier.

Du fait de son importance, 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 :

 
Sélectionnez

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é. Un 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èmes 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.

 
Sélectionnez

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

2.1.2. Accéder à toutes les interruptions

Bien entendu, on ne saurait se restreindre à l'interruption 21h lorsque l'on veut atteindre le coeur 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 fout 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.

 
Sélectionnez

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

2.1.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 :

 
Sélectionnez

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

2.1.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 :

 
Sélectionnez

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...

2.1.4.1. 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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

2.1.4.2. 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 :

 
Sélectionnez

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...

2.2. Utiliser l'assembleur

Il est très courant d'avoir recourt à 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 :

 
Sélectionnez

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.

2.2.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.

 
Sélectionnez

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

2.2.2. L'utilisation des buffers

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

2.2.2.1. 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
2.2.2.1.1. Variable globale

Lorsqu'une varaible 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. A 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 :

 
Sélectionnez

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.

2.2.2.1.2. 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 :

 
Sélectionnez

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

2.2.2.2. Les buffers dynamiques

Les buffers dynamiques, qu'il 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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

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

 
Sélectionnez

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

2.2.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. Il renverront alors l'adresse du pointeur vers le buffer, et non celle du buffer.

 
Sélectionnez

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.

3. 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 aura créé lui-même. Avant de s'attaquer à ce problème, concentrons-nous sur les éventuels problèmes existant.

3.1. 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-}.

3.2. La directive interrupt

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

 
Sélectionnez

{$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èmes 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épercution 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 :

 
Sélectionnez

{$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 un d'entre eux.

3.3. 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.

3.3.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 :

 
Sélectionnez

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 PICs. 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.

3.3.2. Le registre Flags

Les interruptions matérielles utilisent normalement le registre Flags. Ceci n'est pas gênant en soit, 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 :

 
Sélectionnez

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

3.3.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 :

 
Sélectionnez

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

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

4.1. 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.

 
Sélectionnez

program Test;

uses
  Dos;

...
				

4.1.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 @ :

 
Sélectionnez

var
  IntVec: procedure;
  
begin
  GetIntVec($09, @IntVec); 
  ... 					
					

4.1.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.

 
Sélectionnez

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

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

4.2. 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 gestionnaire 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.

 
Sélectionnez

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 important. 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 exclus 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 quoique ce soit (nécessité de protéger et de vérouiller le code en mémoire, etc...).

Bonne programmation !