# Introduction

Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.

Dans ce didacticiel, vous allez manipuler le processeur depuis une extension.

Le temps de lecture de ce didacticiel est estimé à 25 minutes.

# Mise en place

Pour commencer, créez le canevas de l'extension dans le fichier processeur.svm_plugin en utilisant ce code :

    PLUGIN cpu

    DEFINE

# Registres

## Pointeur d'instruction

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.current -> SYM
    %{
    	return ::svm_processor_get_currentinstruction(svm,CURRENT(kernel));
    %}

    INSTRUCTION cpu.next -> SYM
    %{
    	return ::svm_processor_get_nextinstruction(svm,CURRENT(kernel));
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application affichant le contenur des deux registres pointeur d'instruction.

Notez que la modification du registre d'instruction courante est impossible. La modification du pointeur de prochaine instruction existe, et s'appelle un saut. Nous aborderons les sauts juste après la gestion de l'autre registre.

## Pointeur de mémoire

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.get_p -> PTR
    %{
    	return ::svm_processor_get_currentpointer(svm,CURRENT(kernel));
    %}

    INSTRUCTION cpu.set_p PTR
    %{
    	SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[0]);
    	::svm_processor_set_currentpointer(svm,CURRENT(kernel),p);
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application modifiant le pointeur mémoire et l'utilisant pour référencer un tableau. Comparez également le retour de l'instruction `:cpu.get_p` avec `P`.

- Les registres pointeur d'instruction et de mémoire sont accessibles depuis l'interface programmatique.
- Le registre pointeur d'instruction courante est en lecture seule, et les autres peuvent être modifiés.

# Exécution de code

## Sauts

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.local INT
    %{
    	auto a = ARGV_VALUE(0,integer);
    	::svm_processor_jump_local(svm,CURRENT(kernel),a);
    %}

    INSTRUCTION cpu.global ( SYM | LIB INT )
    %{
    	SVM_Value_Symbol s = nullptr;
    	if(argc==1)
    	{
    		s = ::svm_parameter_value_get(svm,argv[0]);
    	}
    	else
    	{
    		SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]);
    		SVM_Address a = ARGV_VALUE(1,integer);
    		s = ::svm_value_symbol_new(svm,l,a);
    	}
    	::svm_processor_jump_global(svm,CURRENT(kernel),s);
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application utilisant les deux instructions de l'extension pour écrire des petits algorithmes tel des parcours de tableaux sans utiliser d'étiquette ou de symbole.

## Fonctions

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.call ( INT | LIB INT ) PTR
    %{
    	if(argc==2)
    	{
    		SVM_Address a = ARGV_VALUE(0,integer);
    		SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[1]);
    		::svm_processor_call_local(svm,CURRENT(kernel),a,p);
    	}
    	else
    	{
    		SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]);
    		SVM_Address a = ARGV_VALUE(1,integer);
    		SVM_Value_Symbol s = ::svm_value_symbol_new(svm,l,a);
    		SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[2]);
    		::svm_processor_call_global(svm,CURRENT(kernel),s,p);
    	}
    %}

    INSTRUCTION cpu.return
    %{
    	::svm_processor_return(svm,CURRENT(kernel));
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application utilisant les deux instructions de l'extension pour écrire un calcul de la suite de Fibonacci récursif sans aucune étiquette ou symbole.

## Arrêt

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.shutdown
    %{
    	::svm_processor_current_shutdown(svm);
    %}

Vous pouvez générer et compiler l'extension pour tester cette fonction.

Notez cependant que cette fonction de l'interface programmatique ne peut s'appliquer que sur le processeur courant, alors que les autres fonctions ont la capacité de modifier un autre processeur que le processeur courant !

## Pause

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.pause INT INT ?
    %{
    	SVM_Value_Integer s = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Value_Integer ms = nullptr;
    	if(argc==2)
    	{
    		ms = ::svm_parameter_value_get(svm,argv[1]);
    	}
    	else
    	{
    		ms = ::svm_value_integer_new(svm,0);
    	}
    	::svm_processor_current_sleep(svm,s,ms,TRUE);
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application pour tester cette instruction.

Dans la documentation de cette fonction d'interface, il est précisé de n'utiliser que cette fonction pour réaliser un temps d'attente dans du code d'extension. Toute autre fonction de l'interface du système d'exploitation est à proscrire.

- L'interface programmatique permet de réaliser des sauts, des appels de fonction et des retours de fonction sur n'importe quel processeur de la machine.
- Elle permet également d'arrêter le processeur courant.
- Enfin, elle contient les seules fonctions admises pour réaliser une attente passive dans du code d'extension.

# Interruptions

## Gestionnaires d'interruption

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.irq_local SYM
    %{
    	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
    	while(*li)
    	{
    		::svm_processor_interruptionhandler_set_local_global(svm,CURRENT(kernel),*li,s,FALSE);
    		++li;
    	}
    %}

    INSTRUCTION cpu.irq_cascade SYM
    %{
    	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
    	while(*li)
    	{
    		::svm_processor_interruptionhandler_set_local_global(svm,CURRENT(kernel),*li,s,TRUE);
    		++li;
    	}
    %}

    INSTRUCTION cpu.irq_global SYM
    %{
    	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
    	while(*li)
    	{
    		::svm_processor_interruptionhandler_set_global_global(svm,CURRENT(kernel),*li,s);
    		++li;
    	}
    %}

Générez puis compilez l'extension. Ensuite, écrivez une petite application pour tester ces instructions. Vous pouvez également améliorer ce code pour accepter des gestionnaires d'interruptions locaux.

## Interruptions en attente

Le processeur possède la liste des interruptions reçues durant l'exécution d'une instruction. Grâce à l'interface programmatique de la machine virtuelle, il est possible de récupérer, de modifier cette liste d'interruption et même de bloquer le traitement des interruptions.

### Lecture et modification des interruptions en attente

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.keep_hw_irq
    %{
    	SVM_Value_Interruption *li = ::svm_processor_list_interruption(svm,CURRENT(kernel));
    	::svm_processor_clear_interruption(svm,CURRENT(kernel));
    	while(*li)
    	{
    		if(::svm_value_interruption_get_kind(svm,*li)==HARDWARE)
    		{
    			::svm_processor_add_interruption(svm,CURRENT(kernel),*li);
    		}
    		++li;
    	}
    %}

Générez puis compilez l'extension.

En l'état, cette instruction n'est pas très utile, car il est très rare que le processeur reçoive plusieurs interruptions durant l'exécution d'une instruction.

### Blocage du traitement des interruptions

Modifiez le code de l'extension pour ajouter deux instructions :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.keep_hw_irq
    %{
    	SVM_Value_Interruption *li = ::svm_processor_list_interruption(svm,CURRENT(kernel));
    	::svm_processor_clear_interruption(svm,CURRENT(kernel));
    	while(*li)
    	{
    		if(::svm_value_interruption_get_kind(svm,*li)==HARDWARE)
    		{
    			::svm_processor_add_interruption(svm,CURRENT(kernel),*li);
    		}
    		++li;
    	}
    %}

    SYSTEM INSTRUCTION cpu.hold
    %{
    	::svm_processor_hold_interruption(svm,CURRENT(kernel));
    %}

    SYSTEM INSTRUCTION cpu.release
    %{
    	::svm_processor_release_interruption(svm,CURRENT(kernel));
    %}

Crééz un fichier exécutable avec ce code :

    #!/usr/bin/env svm
    LOG
    DEBUG "processor"
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
    PROCESS "cpu"
    	CODE "main" INLINE
    		:interruption GLOBAL FAILURE f
    		:interruption GLOBAL DEVICE d
    		:interruption GLOBAL TERMINATE t
    		:debug BREAK
    		:run.trace 1
    		:run.interrupt FAILURE
    		:run.trace 2
    		:cpu.hold
    		:run.trace 3
    		:run.interrupt FAILURE
    		:run.trace 4
    		:run.interrupt DEVICE
    		:run.trace 5
    		:run.interrupt TERMINATE
    		:run.trace 6
    		:cpu.release
    		:run.trace 7
    		:shutdown
    	:label f
    		:run.trace "FAILURE"
    		:return
    	:label d
    		:run.trace "DEVICE"
    		:return
    	:label t
    		:run.trace "TERMINATE"
    		:return
    	END
    END

Lancez cette application :

    ./processor.svm
    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    1

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    FAILURE

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    2

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    3

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    4

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    5

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    6

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    TERMINATE

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    DEVICE

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    FAILURE

    ### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
    7

La trace obtenue suggère bien que les interruptions ont été retenues par le processeur entre les instructions `:cpu.hold` et `:cpu.release`.

Ajoutez dans cette application l'instruction `:cpu.keep_hw_irq` juste avant `:cpu.release` puis lancez l'application à nouveau en mode débugueur. Lors de l'exécution, pendant que les interruptions sont bloquées, envoyez des signaux SIGTERM à la machine virtuelle :

    kill -15 $(pidof svm)

Cette fois, les interruptions qui ont été levées par les instructions `:run.interrupt` sont carrément ignorées, mais pas celles générées par les signaux SIGTERM.

## Erreurs

Les fonctions de l'interface programmatique vu précédemment permettent d'ajouter une interruption au processeur, mais n'arrêtent pas l'exécution de l'instruction courante.

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.error IRQ
    %{
    	SVM_Value_Interruption i = ::svm_parameter_value_get(svm,argv[0]);
    	::svm_machine_trace__raw(svm,"1");
    	::svm_processor_current_raise_error(svm,i);
    	::svm_machine_trace__raw(svm,"2");
    %}

Modifiez le code de l'application pour appeller l'instruction `:cpu.error`, et constater en lançant l'application que la trace numéro 2 de l'instruction n'est pas exécutée !

Notez que des variantes de la fonction `svm_processor_current_raise_error` est utilisée dans les macros `ERROR_INTERNAL` et `ERROR_EXTERNAL` définies par le générateur d'extension.

- L'interface programmatique du processeur permet de manipuler les gestionnaires d'interruptions comme le fait l'instruction `:interruption`.
- Elle permet également de gérer la liste des interruptions en attente, ainsi que de mettre en attente la gestion des interruptions.
- Enfin, elle permet de lever facilement des interruptions logicielles correspondant à des erreurs survenant lors de l'exécution des instructions.

# Drapeaux

## Modification

Modifiez le code de l'extension :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.flag [ 'SET' 'RESET' ] STR:flag BLN:cascade
    %{
    	auto a = ARGV_KEYWORD(0);
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[1]);
    	auto b = ARGV_VALUE(2,boolean);
    	if(a=="SET")
    	{
    		::svm_processor_set_flag(svm,CURRENT(kernel),f,b);
    	}
    	else
    	{
    		::svm_processor_reset_flag(svm,CURRENT(kernel),f,b);
    	}
    %}

Générez et compilez l'extension, comme habituellement. Puis, crééz une petite application qui vous permet de tester cette instruction : vous constaterez qu'elle permet de retirer un drapeau, ce qui n'est pas possible dans le jeu d'instructions de base de la machine.

## Test

Modifiez le code de l'extension pour ajouter une instruction :

    PLUGIN cpu

    DEFINE

    INSTRUCTION cpu.flag [ 'SET' 'RESET' ] STR:flag BLN:cascade
    %{
    	auto a = ARGV_KEYWORD(0);
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[1]);
    	auto b = ARGV_VALUE(2,boolean);
    	if(a=="SET")
    	{
    		::svm_processor_set_flag(svm,CURRENT(kernel),f,b);
    	}
    	else
    	{
    		::svm_processor_reset_flag(svm,CURRENT(kernel),f,b);
    	}
    %}

    INSTRUCTION cpu.test STR -> BLN
    %{
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
    	return NEW_VALUE(boolean,::svm_processor_has_flag(svm,CURRENT(kernel)));
    %}

Après regénération et recompilation de l'extension, modifiez votre application de test pour utiliser cette instruction. Notez que la détection est insensible au fait que le drapeau soit cascadé ou local.

- L'interface programmatique permet de placer ou de retirer un drapeau local ou cascadé.
- Elle permet aussi de détecter si un drapeau est actif.

# Manipulations de la pile de retour

## Recherche d'états

L'interface programmatique permet de rechercher les niveaux de pile de retour contenant un certain drapeau. Modifiez le code de l'extension :

    PLUGIN cpu

    includes:
    %{
    #include <iostream>
    %}

    DEFINE

    INSTRUCTION cpu.find STR
    %{
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
    	unsigned long int l = 0;
    	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
    	for(;;)
    	{
    		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
    		if(l==s) break;
    		std::cout << "Found at level " << l << std::endl;
    		++l;
    	}
    %}

Après regénération et recompilation de l'extension, modifiez votre application de test avec ce code :

    #!/usr/bin/env svm
    LOG
    DEBUG "processor"
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
    PROCESS "cpu"
    	CODE "main" INLINE
    		:flag "a"
    		:call f1 P
    		:shutdown
    	:label f1
    		:flag "a"
    		:call f2 P
    		:return
    	:label f2
    		:call f3 P
    		:return
    	:label f3
    		:call f4 P
    		:return
    	:label f4
    		:flag "a"
    		:call f5 P
    		:return
    	:label f5
    		:flag "a"
    		:cpu.find "a"
    		:debug BREAK
    		:return
    	END
    END

Lancez l'application :

    ./processor.svm
    Found at level 0
    Found at level 3
    Found at level 4

Vous pouvez remarquer :

- Seuls 3 niveaux sont trouvés, alors que le drapeau est levé 4 fois : en effet l'état courant n'est pas dans la pile, et il contient le drapeau.
- Le niveau 0 correspond à la fonction f4, le 3 à la fonction f1 et le 4 au code principal, dans l'ordre inverse des appels de fonction.

## Echange

Avec l'interface programmatique, il est possible d'échanger l'état courant avec un état sauvegardé dans la pile de retour.

Modifiez le code de l'extension pour ajouter une instruction :

    PLUGIN cpu

    includes:
    %{
    #include <iostream>
    %}

    DEFINE

    INSTRUCTION cpu.find STR
    %{
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
    	unsigned long int l = 0;
    	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
    	for(;;)
    	{
    		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
    		if(l==s) break;
    		std::cout << "Found at level " << l << std::endl;
    		++l;
    	}
    %}

    INSTRUCTION cpu.swap [ INT STR ]
    %{
    	SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Size l;
    	if(::svm_value_type_is_integer(svm,v))
    	{
    		l = ::svm_value_integer_get(svm,v);
    	}
    	else
    	{
    		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),v,0);
    	}
    	::svm_processor_returnstack_swap_level(svm,CURRENT(kernel),l);
    %}

Regénérez et compilez l'extension. Ensuite, modifiez le code de l'application avec ce code :

    #!/usr/bin/env svm
    LOG
    DEBUG "processor"
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
    PROCESS "cpu"
    	CODE "main" INLINE
    		:flag "a"
    		:call f1 P
    		:run.trace "main"
    		:shutdown
    	:label f1
    		:flag "a"
    		:call f2 P
    		:run.trace "f1"
    		:return
    	:label f2
    		:call f3 P
    		:run.trace "f2"
    		:return
    	:label f3
    		:call f4 P
    		:run.trace "f3"
    		:return
    	:label f4
    		:flag "a"
    		:call f5 P
    		:run.trace "f4"
    		:return
    	:label f5
    		:flag "a"
    		:cpu.find "a"
    		:debug BREAK
    		:cpu.swap 2
    		:run.trace "f5"
    		:return
    	END
    END

L'exécution du code montre que les retours de fonction ne se passent plus comme d'habitude. En effet, la fin de l'exécution de la fonction f5 (la fonction courante) est échangée avec la fonction f2 ! Une exécution dans le débugueur permet de bien mettre en évidence dans quel ordre les instructions sont exécutées.

## Déplacement

Enfin, il est possible de déplacer une portion de la pile de retour.

Modifiez le code de l'extension pour ajouter une instruction :

    PLUGIN cpu

    includes:
    %{
    #include <iostream>
    %}

    DEFINE

    INSTRUCTION cpu.find STR
    %{
    	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
    	unsigned long int l = 0;
    	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
    	for(;;)
    	{
    		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
    		if(l==s) break;
    		std::cout << "Found at level " << l << std::endl;
    		++l;
    	}
    %}

    INSTRUCTION cpu.swap [ INT STR ]
    %{
    	SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Size l;
    	if(::svm_value_type_is_integer(svm,v))
    	{
    		l = ::svm_value_integer_get(svm,v);
    	}
    	else
    	{
    		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),v,0);
    	}
    	::svm_processor_returnstack_swap_level(svm,CURRENT(kernel),l);
    %}

    INSTRUCTION cpu.move INT:start INT:size INT:target
    %{
    	auto s = ARGV_VALUE(0,integer);
    	auto ss = ARGV_VALUE(1,integer);
    	auto t = ARGV_VALUE(2,integer);
    	::svm_processor_returnstack_move_level(svm,CURRENT(kernel),s,s+ss,t);
    %}

Regénérez et compilez l'extension. Ensuite, modifiez le code de l'application avec ce code :

    #!/usr/bin/env svm
    LOG
    DEBUG "processor"
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
    PROCESS "cpu"
    	CODE "main" INLINE
    		:flag "a"
    		:call f1 P
    		:run.trace "main"
    		:shutdown
    	:label f1
    		:flag "a"
    		:call f2 P
    		:run.trace "f1"
    		:return
    	:label f2
    		:call f3 P
    		:run.trace "f2"
    		:return
    	:label f3
    		:call f4 P
    		:run.trace "f3"
    		:return
    	:label f4
    		:flag "a"
    		:call f5 P
    		:run.trace "f4"
    		:return
    	:label f5
    		:flag "a"
    		:cpu.find "a"
    		:debug BREAK
    		:cpu.move 1 2 0
    		:run.trace "f5"
    		:return
    	END
    END

L'exécution du code montre que cette fois, la fonction f5 termine bien en premier. En revanche, les fonctions f3 et f2 se terminent dans cet ordre (ce qui est normal) avant la fonction f4 ! L'instruction `:cpu.move` a littéralement déplacé une portion de la pile de retour.

- L'interface programmatique permet de chercher dans la pile de retour (mais pas l'état courant) les niveaux de pile qui contiennent un certain drapeau.
- Elle permet également d'échanger l'état courant avec un autre état de la pile, ainsi que de déplacer des portions entières de la pile à l'intérieur d'elle-même.
- Ajouter ou enlever un état de la pile n'est possible que par son sommet, grâce aux appels et aux retours de fonction.

# Conclusion

Vous venez de voir comment manipuler les processeurs de la machine virtuelle depuis les extensions.

L'interface permet une interaction étendue avec les processeurs de la machine, pour créér des sauts ou appels de fonction enrichis. Cela peut même donner des résultats parfois surprenant, surtout lorsque deux opérations de saut ou d'appel de fonction ou de retour de fonction sont appellés dans la même instruction.

La manipulation de la pile de retour peut aussi hautement changer l'ordre d'exécution, surtout lorsque plusieurs opérations sont utilisées : c'est comme cela que peuvent être implémentées les coroutines dans la machine virtuelle !

Enfin, la gestion des interruptions peut être affinée par l'intermédiaire de l'interface programmatique, notamment avec la capacité à retarder le traitement des interruptions.