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 créer et manipuler des séquenceurs de la machine virtuelle.
Le temps de lecture de ce didacticiel est estimé à 25 minutes.
Pour commencer, créez le canevas de l'extension dans le fichier sequenceurs.svm_plugin en utilisant ce code :
PLUGIN sequencer
DEFINE
Un séquenceur doit être capable de gérer plusieurs noyaux, en règle générale : pour cela, une structure de données servant à la gestion des noyaux peut être très utile. Le générateur d'extension offre des facilités pour définir une telle structure. Modifiez le code de l'extension :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
Comme pour les types extensions, le contenu de la structure du séquenceur suit la déclaration de l'objet séquenceur. Ici, la struct C/C++ générée a pour type le nom formé de la chaine fixe "sequencer_" suivie du nom de l'entrée du séquenceur (ce qui donne ici le type sequencer_instance).
Pour construire et détruire cette structure, deux fonctions doivent être définies. Modifiez le code de l'extension :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
Les options default
permettent de générer du code de construction et destruction classique.
La prochaine fonction à définir contient la politique de séquencement, et par conséquent, est la fonction centrale du séquenceur. Modifiez le code de l'extension :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
if(not object->_interrupted)
{
if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
}
else
{
if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
}
return nullptr;
%}
L'option object
permet de générer du code de conversion en C/C++ pour avoir un accès direct à l'objet du séquenceur dans la variable magique object
.
Le fonctionnement de cette fonction est plutôt simple : lorsqu'elle est invoquée, elle doit simplement retourner le prochain noyau à exécuter. Lorsque plus aucun noyau ne doit être exécuté, la fonction doit simplement renvoyer un pointeur C/C++ nul. Cela met alors fin à l'exécution du processus contenant le séquenceur.
Il est fortement conseillé d'écrire une fonction de séquencement qui soit non bloquante, sous peine de bloquer l'exécution du code dans la machine virtuelle.
Pour qu'un noyau soit pris en charge par un séquenceur, il est obligatoire de l'attacher à ce séquenceur. De même, lorsqu'un noyau arrive en fin de vie ou sur requête explicite, le noyau peut être détaché pour être soustrait au séquenceur. Modifiez le code de l'extension :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
if(not object->_interrupted)
{
if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
}
else
{
if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
}
return nullptr;
%}
attach object:
%{
if(argc==0)
{
if(object->_main) return FALSE;
VARIABLE_GLOBAL(kernel); object->_main = kernel;
return TRUE;
}
SVM_Boolean b = ARGV_VALUE(0,boolean);
if(b)
{
if(object->_success) return FALSE;
VARIABLE_GLOBAL(kernel); object->_success = kernel;
}
else
{
if(object->_failure) return FALSE;
VARIABLE_GLOBAL(kernel); object->_failure = kernel;
}
return TRUE;
%}
detach object:
%{
if(kernel==object->_main)
{
VARIABLE_LOCAL(kernel); object->_main = nullptr;
object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
return TRUE;
}
if(kernel==object->_success)
{
VARIABLE_LOCAL(kernel); object->_success = nullptr;
return TRUE;
}
if(kernel==object->_failure)
{
VARIABLE_LOCAL(kernel); object->_failure = nullptr;
return TRUE;
}
return FALSE;
%}
La fonction d'attachement a deux possibilités :
TRUE
,FALSE
.La fonction de détachement a également deux possibilités :
TRUE
,FALSE
.La dernière fonction à créer sert à indiquer l'état du séquenceur. Modifiez le code de l'extension :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
if(not object->_interrupted)
{
if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
}
else
{
if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
}
return nullptr;
%}
attach object:
%{
if(argc==0)
{
if(object->_main) return FALSE;
VARIABLE_GLOBAL(kernel); object->_main = kernel;
return TRUE;
}
SVM_Boolean b = ARGV_VALUE(0,boolean);
if(b)
{
if(object->_success) return FALSE;
VARIABLE_GLOBAL(kernel); object->_success = kernel;
}
else
{
if(object->_failure) return FALSE;
VARIABLE_GLOBAL(kernel); object->_failure = kernel;
}
return TRUE;
%}
detach object:
%{
if(kernel==object->_main)
{
VARIABLE_LOCAL(kernel); object->_main = nullptr;
object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
return TRUE;
}
if(kernel==object->_success)
{
VARIABLE_LOCAL(kernel); object->_success = nullptr;
return TRUE;
}
if(kernel==object->_failure)
{
VARIABLE_LOCAL(kernel); object->_failure = nullptr;
return TRUE;
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
oss << "Main kernel: ";
if(object->_main)
{
SVM_String m = ::svm_kernel_print(svm,object->_main);
oss << RAW_STRING(m);
}
oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
<< std::endl << "Success kernel: ";
if(object->_success)
{
SVM_String s = ::svm_kernel_print(svm,object->_success);
oss << RAW_STRING(s);
}
oss << std::endl << "Failure kernel: ";
if(object->_failure)
{
SVM_String f = ::svm_kernel_print(svm,object->_failure);
oss << RAW_STRING(f);
}
oss << std::endl;
return NEW_STRING(oss.str());
%}
Vous pouvez générer et compiler l'extension. Cependant, plutôt que d'utiliser directement l'extension, il est conseillé de regarder le code produit par le générateur d'extension.
Cette fonction doit simplement produire une chaine de caractères de type SVM_String
contenant de préférence au moins la liste des noyaux attachés au séquenceur avec leurs états.
SEQUENCER plugin.entry
, suivi par un bloc correspondant au contenu d'une structure C/C++ de type sequencer_entry
.create
et à détruire delete
la structure en C/C++ du séquenceur.current
permet d'incarner la politique de séquencement.attach
et de détachement detach
permettent à un séquenceur respectivement de gérer un nouveau noyau ou d'évincer un noyau géré.print
permet de rendre une chaine de caractère décrivant l'état interne du séquenceur.Pour pouvoir utiliser pleinement ce séquenceur, il faut ajouter une petite instruction pour attacher des noyaux supplémentaires :
PLUGIN sequencer
DEFINE
SEQUENCER sequencer.instance
%{
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
if(not object->_interrupted)
{
if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
}
else
{
if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
}
return nullptr;
%}
attach object:
%{
if(argc==0)
{
if(object->_main) return FALSE;
VARIABLE_GLOBAL(kernel); object->_main = kernel;
return TRUE;
}
SVM_Boolean b = ARGV_VALUE(0,boolean);
if(b)
{
if(object->_success) return FALSE;
VARIABLE_GLOBAL(kernel); object->_success = kernel;
}
else
{
if(object->_failure) return FALSE;
VARIABLE_GLOBAL(kernel); object->_failure = kernel;
}
return TRUE;
%}
detach object:
%{
if(kernel==object->_main)
{
VARIABLE_LOCAL(kernel); object->_main = nullptr;
object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
return TRUE;
}
if(kernel==object->_success)
{
VARIABLE_LOCAL(kernel); object->_success = nullptr;
return TRUE;
}
if(kernel==object->_failure)
{
VARIABLE_LOCAL(kernel); object->_failure = nullptr;
return TRUE;
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
oss << "Main kernel: ";
if(object->_main)
{
SVM_String m = ::svm_kernel_print(svm,object->_main);
oss << RAW_STRING(m);
}
oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
<< std::endl << "Success kernel: ";
if(object->_success)
{
SVM_String s = ::svm_kernel_print(svm,object->_success);
oss << RAW_STRING(s);
}
oss << std::endl << "Failure kernel: ";
if(object->_failure)
{
SVM_String f = ::svm_kernel_print(svm,object->_failure);
oss << RAW_STRING(f);
}
oss << std::endl;
return NEW_STRING(oss.str());
%}
INSTRUCTION sequencer.attach BLN [ LIB SYM ]
%{
SVM_Value_Boolean b = ::svm_parameter_value_get(svm,argv[0]);
SVM_Value c = ::svm_parameter_value_get(svm,argv[1]);
SVM_Kernel k = nullptr;
if(::svm_value_type_is_library(svm,c))
{
SVM_Code code = ::svm_value_library_get_code(svm,c);
k = ::svm_kernel_new_code(svm,FALSE,FALSE,FALSE,nullptr,code);
for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
{
::svm_processor_interruptionhandler_set_local_local(svm,k,*irq,0,FALSE);
}
}
else
{
k = ::svm_kernel_new_symbol(svm,FALSE,FALSE,FALSE,nullptr,c);
for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
{
::svm_processor_interruptionhandler_set_local_global(svm,k,*irq,c,FALSE);
}
}
SVM_Parameter *params = ::svm_parameter_array_new(svm,1);
params[0] = ::svm_parameter_value_new(svm,b);
if(not ::svm_process_kernel_attach(svm,CURRENT(process),k,1,params))
{
ERROR_INTERNAL(FAILURE,"Unable to attach kernel");
}
%}
Générez et compilez l'extension.
Créez un fichier exécutable pour y placer un exemple d'application permettant d'illustrer le fonctionnement de ce séquenceur :
#!/usr/bin/env svm
LOG SERVER "localhost" "8081" QUIET
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginsequencer/libsvmsequencer.so"
OPTION -i FLAG irq
PROCESS "test"
CODE "main" INLINE
:memory LIB*2/l
:library "success" ":com.message \"success\"\n" -> (l/0)
:library "failure" ":com.message \"failure\"\n" -> (l/1)
:sequencer.attach TRUE @(l/0)
:sequencer.attach FALSE @(l/1)
:debug BREAK
:com.message "main"
:shutdown :unless @&irq TRUE
:run.interrupt FAILURE
END
MEMORY irq
SEQUENCER sequencer.instance
END
Vous pouvez appeler cette application :
./sequencer.svm
main
success
./sequencer.svm -i
main
failure
Pour faire simple, ce séquenceur réalise un try ... catch
à un niveau élévé dans l'architecture de la machine virtuelle.
Le séquenceur présenté ici est relativement simple. En particulier, il ne peut accepter qu'au maximum trois noyaux.
D'autres séquenceurs peuvent avoir une politique de séquencement qui autorisent autant de noyaux que nécessaires, comme le séquenceur run.stack
qui utilise une pile (LIFO) de noyaux pour enchainer des noyaux comme des appels de fonction, ou comme le séquenceur inline.sequencer
qui utilise une liste de noyaux pour réaliser une séquence de noyaux avec un objectif commun.
Evidemment, dans l'absolu, n'importe quelle politique de séquencement peut être écrite pour satisfaire aux besoins particuliers d'une application.
Il peut être parfois utile de modifier la structure d'un séquenceur en dehors des appels au séquenceur, via une instruction. Modifiez le code de l'extension :
PLUGIN sequencer
includes:
%{
#include <mutex>
%}
DEFINE
SEQUENCER sequencer.instance
%{
mutable std::mutex _mutex;
SVM_Kernel _main;
bool _interrupted;
SVM_Kernel _success;
SVM_Kernel _failure;
%}
create default:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
object->_main = nullptr;
object->_interrupted = false;
object->_success = nullptr;
object->_failure = nullptr;
%}
delete default:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
if(object->_main) VARIABLE_LOCAL(object->_main);
if(object->_success) VARIABLE_LOCAL(object->_success);
if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
if(not object->_interrupted)
{
if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
}
else
{
if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
}
return nullptr;
%}
attach object:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
if(argc==0)
{
if(object->_main) return FALSE;
VARIABLE_GLOBAL(kernel); object->_main = kernel;
return TRUE;
}
SVM_Boolean b = ARGV_VALUE(0,boolean);
if(b)
{
if(object->_success) return FALSE;
VARIABLE_GLOBAL(kernel); object->_success = kernel;
}
else
{
if(object->_failure) return FALSE;
VARIABLE_GLOBAL(kernel); object->_failure = kernel;
}
return TRUE;
%}
detach object:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
if(kernel==object->_main)
{
VARIABLE_LOCAL(kernel); object->_main = nullptr;
object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
return TRUE;
}
if(kernel==object->_success)
{
VARIABLE_LOCAL(kernel); object->_success = nullptr;
return TRUE;
}
if(kernel==object->_failure)
{
VARIABLE_LOCAL(kernel); object->_failure = nullptr;
return TRUE;
}
return FALSE;
%}
print object:
%{
std::lock_guard<std::mutex> protection(object->_mutex);
std::ostringstream oss;
oss << "Main kernel: ";
if(object->_main)
{
SVM_String m = ::svm_kernel_print(svm,object->_main);
oss << RAW_STRING(m);
}
oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
<< std::endl << "Success kernel: ";
if(object->_success)
{
SVM_String s = ::svm_kernel_print(svm,object->_success);
oss << RAW_STRING(s);
}
oss << std::endl << "Failure kernel: ";
if(object->_failure)
{
SVM_String f = ::svm_kernel_print(svm,object->_failure);
oss << RAW_STRING(f);
}
oss << std::endl;
return NEW_STRING(oss.str());
%}
INSTRUCTION sequencer.attach BLN [ LIB SYM ]
%{
SVM_Value_Boolean b = ::svm_parameter_value_get(svm,argv[0]);
SVM_Value c = ::svm_parameter_value_get(svm,argv[1]);
SVM_Kernel k = nullptr;
if(::svm_value_type_is_library(svm,c))
{
SVM_Code code = ::svm_value_library_get_code(svm,c);
k = ::svm_kernel_new_code(svm,FALSE,FALSE,FALSE,nullptr,code);
for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
{
::svm_processor_interruptionhandler_set_local_local(svm,k,*irq,0,FALSE);
}
}
else
{
k = ::svm_kernel_new_symbol(svm,FALSE,FALSE,FALSE,nullptr,c);
for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
{
::svm_processor_interruptionhandler_set_local_global(svm,k,*irq,c,FALSE);
}
}
SVM_Parameter *params = ::svm_parameter_array_new(svm,1);
params[0] = ::svm_parameter_value_new(svm,b);
if(not ::svm_process_kernel_attach(svm,CURRENT(process),k,1,params))
{
ERROR_INTERNAL(FAILURE,"Unable to attach kernel");
}
%}
INSTRUCTION sequencer.ignore BLN
%{
SVM_Boolean b = ARGV_VALUE(0,boolean);
SVM_Value_PluginEntryPoint sn = ::svm_process_get_sequencer(svm,CURRENT(process));
if(not sn)
{
ERROR_INTERNAL(FAILURE,"Default sequencer attached to current process. sequencer.instance required.");
}
SVM_String snp = ::svm_value_pluginentrypoint_get_plugin(svm,sn);
SVM_String sne = ::svm_value_pluginentrypoint_get_entry(svm,sn);
if(not ((RAW_STRING(snp)=="sequencer") and (RAW_STRING(sne)=="instance")))
{
ERROR_INTERNAL(FAILURE,"Invalid plugin sequencer attacher to current process. sequencer.instance required.");
}
auto s = reinterpret_cast<sequencer_instance*>(::svm_process_sequencer_get_internal(svm,CURRENT(process)));
std::lock_guard<std::mutex> protection(s->_mutex);
if(b)
{
VARIABLE_LOCAL(s->_failure); s->_failure = nullptr;
else
{
VARIABLE_LOCAL(s->_success); s->_success = nullptr;
}
%}
Vous pouvez remarquer :
svm_process_get_sequencer
, soit bien de type sequencer.instance
,svm_process_sequencer_get_internal
. Cette structure est de type C/C++ sequencer_entry
,std::mutex
), et d'ajouter des barrières de synchronisation entre les fonctions du séquenceur et l'instruction.Après regénération et recompilation de l'extension, ajoutez un appel à cette nouvelle instruction avec TRUE
en paramètre dans l'application précédente, et constatez le changement de comportement :
#!/usr/bin/env svm
LOG SERVER "localhost" "8081" QUIET
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginsequencer/libsvmsequencer.so"
OPTION -i FLAG irq
PROCESS "test"
CODE "main" INLINE
:memory LIB*2/l
:library "success" ":com.message \"success\"\n" -> (l/0)
:library "failure" ":com.message \"failure\"\n" -> (l/1)
:sequencer.attach TRUE @(l/0)
:sequencer.attach FALSE @(l/1)
:debug BREAK
:com.message "main"
:sequencer.ignore TRUE
:shutdown :unless @&irq TRUE
:run.interrupt FAILURE
END
MEMORY irq
SEQUENCER sequencer.instance
END
svm_process_sequencer_get_internal
, souvent appelée après s'être assuré de la nature du séquenceur avec la fonction de l'interface programmatique svm_process_get_sequencer
.Vous venez de voir comment créer et manipuler les séquenceurs depuis une extension.
Dans la majorité des processus, le séquenceur par défaut (ne supportant qu'un noyau) est suffisant. Cependant, changer de séquenceur est parfois requis pour le fonctionnement de certaines instructions.
Quand tous les séquenceurs déjà existants se révèlent insuffisants, il est possible d'en créer un grâce aux extensions.