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 les ordonnanceurs de la machine virtuelle.
Le temps de lecture de ce didacticiel est estimé à 35 minutes.
Pour commencer, créez le canevas de l'extension dans le fichier ordonnanceurs.svm_plugin en utilisant ce code :
PLUGIN scheduler
DEFINE
Un ordonnanceur a une mission très simple dans la machine virtuelle : il orchestre l'exécution des processus qu'il a à sa charge selon une politique d'ordonnancement liée à son implémentation.
Comme pour les séquenceurs, le premier élément d'un ordonnanceur est sa déclaration suivie d'une structure pouvant contenir les données liées à son fonctionnement. Ici, la struct C/C++ générée a pour type le nom formé de la chaine fixe "scheduler_" suivie du nom de l'entrée de l'ordonnanceur (ce qui donne ici le type scheduler_instance).
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<SVM_Process> _processes;
%}
Ensuite, deux fonctions pour créer et détruire la structure de l'ordonnanceur doivent être définies.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<SVM_Process> _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p);
}
%}
Sur ces deux fonctions, les options default
permettent de générer le code de base de construction et de destruction de la structure. De plus, dans la fonction de destruction, cela définit la variable magique object
utilisable pour accéder directement à la structure de l'ordonnanceur.
Chacune de ces fonctions est appelée une seule fois par exécution de la machine virtuelle :
La fonction suivante est le cœur de l'ordonnanceur : il s'agit de la fonction qui incarne la politique d'ordonnancement.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<SVM_Process> _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p);
}
%}
schedule object:
%{
if(state==SUSPENDED)
{
::svm_process_run__raw(svm,process,0);
}
return 0;
%}
Cette fonction, ici très simple (elle réalise l'ordonnancement le plus facile, celui qui exécute tous les processus en même temps), est pourtant plutôt délicate à écrire :
process
qui indique quel processus a changé d'état,state
qui indique quel est le nouvel état de ce processus ;Pour pouvoir gérer des processus, ceux-ci doivent être attachés à l'ordonnanceur. Réciproquement, lorsqu'un processus ne doit plus être ordonnancé, ou avant d'être attaché à un autre ordonnanceur, il doit être détaché.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<SVM_Process> _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p);
}
%}
schedule object:
%{
if(state==SUSPENDED)
{
::svm_process_run__raw(svm,process,0);
}
return 0;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(process);
return TRUE;
%}
detach object:
%{
auto it = std::find(object->_processes.begin(),object->_processes.end(),process);
if(it==object->_processes.end()) return FALSE;
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
%}
Ces fonctions récupèrent le processus et le placent ou l'enlève de l'ordonnanceur. Dans les deux cas, elles retournent TRUE
lorsque l'opération réussit, et FALSE
sinon.
Notez que ces fonctions peuvent recevoir un paramètre entier dans la variable magique parameter
. Par défaut, cette valeur vaut 0, mais une autre valeur peut être utilisée pour modifier la manière d'attacher ou de détacher un processus.
La dernière fonction obligatoire sert à l'investigation, et donne sous forme textuelle une représentation de l'état de l'ordonnanceur, ainsi que les processus gérés.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<SVM_Process> _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p);
}
%}
schedule object:
%{
if(state==SUSPENDED)
{
::svm_process_run__raw(svm,process,0);
}
return 0;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(process);
return TRUE;
%}
detach object:
%{
auto it = std::find(object->_processes.begin(),object->_processes.end(),process);
if(it==object->_processes.end()) return FALSE;
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
Une fois cette fonction ajoutée, l'ordonnanceur est complet.
Générez l'extension et compilez la.
Ecrivez une petite application déclarant deux ou trois processus attachés en utilisant la directive SCHEDULER scheduler.instance
, et exécutez la : tous les processus vont s'exécuter en même temps, sans relation de dépendance.
Il est même recommandé de lancer cette application dans le débugueur, et de regarder les interactions entre l'ordonnanceur et les processus, dans la fenêtre d'événements. Cette fenêtre est un outil puissant pour mettre au point un ordonnanceur.
svm_process_run
, qui (re)lance l'exécution d'un processus.La réactivité aux changements d'état des processus n'est pas le seul outil à la portée des ordonnanceurs, et nous allons voir ici un premier outil.
Pour illustrer correctement le concept de notifications, il nous faut adopter une autre politique d'ordonnancement : un seul processus en exécution, les autres en attente.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return 0;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
Mais ainsi, l'ordonnanceur n'est guère utilisable : un seul processus va s'exécuter, jusqu'à ce qu'il se termine ou se suspende de lui même. Dans certaines circonstance, c'est souhaitable, mais ce n'est pas notre but ici : nous voulons changer le processus "de temps en temps".
Une première manière de procéder consiste à définir une notification qui va suspendre le processus courant, et de fournir une instruction envoyant cette notification.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return 0;
%}
notification object:
%{
if(parameter==0)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
{
::svm_process_suspend(svm,it->first);
break;
}
}
}
return 0;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
INSTRUCTION scheduler.next
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,0);
%}
Vous pouvez noter que dans la fonction de notification de l'ordonnanceur, la variable magique parameter
contient la valeur donnée en troisième argument de la fonction svm_scheduler_notify__raw
.
Générez puis compilez l'extension. Puis créez une application avec ce code :
#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginscheduler/libsvmscheduler.so"
ARGUMENT INT repetition
ARGUMENT INT interval
ARGUMENT INT loop
PROCESS "controler"
CODE "main" INLINE
:shutdown :when @&repetition IN &0*1
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD @&interval
:scheduler.next
:shift &i
:goto loop :when @&i IN &0*@&repetition
END
MEMORY repetition interval
END
PROCESS "1"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
PROCESS "2"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
PROCESS "3"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
Lancez l'application avec des valeurs d'arguments variées, et observez le résultat :
repetition | interval | loop | Résultat obtenu sur les processus 1, 2 et 3 |
---|---|---|---|
0 | 5 | 30 | Chaque processus s'exécute entièrement avant de donner la main au suivant. |
20 | 5 | 30 | Chaque processus exécute 4 ou 5 boucles avant de passer la main au processus suivant. |
9 | 3 | 30 | En moyenne, les processus commencent par exécuter une ou deux boucles en se passant la main à tour de rôle, puis au bout d'un moment chaque processus s'exécute jusqu'à sa terminaison avant de passer la main au suivant. |
5 | 20 | 30 | Chaque processus va exécuter une bonne partie de ses boucles avant de passer la main au suivant. Puis, sur la deuxième rotation, tous les processus vont s'arrer avant que la notification suivante arrive. |
Il est fortement recommandé de suivre le déroulement de l'exécution de cette application dans le débugueur, en se focalisant sur les fenêtres :
notification
sur l'ordonnanceur, dont la variable magique parameter
contient une valeur entière servant de paramètre à la notification.svm_scheduler_notify
en y passant le paramètre entier de la notification.En plus des notifications, les ordonnanceurs ont accès à une horloge intégrée. Pour être plus précis, il s'agit pas d'une horloge monotonique (régulière), mais plutôt d'une échéance programmable qui se déclenche lorsqu'aucune notification ou changement d'état n'est intervenu dans le temps imparti.
Cela permet à un ordonnanceur de rester réactif en cas de changement d'état tout en ayant la possibilité d'agir si rien ne s'est produit dans un temps imparti.
Nous allons ici ajouter la capacité à notre dernier ordonnanceur de réagir à une horloge, tout en laissant la possibilité de gérer le changement de processus via une notification.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
scheduler_instance()
:_timer(0) {}
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
unsigned long int _timer;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return 0;
%}
notification object:
%{
switch(parameter)
{
case 0:
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
{
::svm_process_suspend(svm,it->first);
break;
}
}
break;
case 1:
object->_timer = 3000;
break;
case 2:
object->_timer = 0;
break;
}
return 0;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
INSTRUCTION scheduler.next
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,0);
%}
INSTRUCTION scheduler.timer BLN
%{
SVM_Boolean t = ARGV_VALUE(0,boolean);
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,(t==TRUE)?1:2);
%}
Concrètement, une valeur entière a été ajoutée à la structure de l'ordonnanceur, et le nécessaire pour configurer cette valeur au travers de notifications.
Il s'agit maintenant d'activer l'horloge, et de réagir à l'expiration du délai.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
scheduler_instance()
:_timer(0) {}
void suspend(const void *svm)
{
for(auto it=_processes.begin() ; it!=_processes.end() ; ++it)
{
if(it->second==RUNNING)
{
::svm_process_suspend(svm,it->first);
break;
}
}
}
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
unsigned long int _timer;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return object->_timer;
%}
notification object:
%{
if(type==TIMER)
{
object->suspend(svm);
return object->_timer;
}
switch(parameter)
{
case 0:
object->suspend(svm);
break;
case 1:
object->_timer = 3000;
break;
case 2:
object->_timer = 0;
break;
}
return object->_timer;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
INSTRUCTION scheduler.next
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,0);
%}
INSTRUCTION scheduler.timer BLN
%{
SVM_Boolean t = ARGV_VALUE(0,boolean);
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,(t==TRUE)?1:2);
%}
Maintenant, la valeur de retour des fonctions d'ordonnancement et de notification n'est plus 0, mais correspond au temps d'attente avant déclenchement de l'horloge : ce temps d'attente est exprimé en millisecondes (ici, 3000), ou désactive l'horloge quand le temps d'attente est mis à 0.
Vous pouvez également remarquer la présence de la variable magique type
dans la fonction de notification, qui prend les valeurs :
NOTIFICATION
: lorsque la fonction est appelée sur une notification. Dans ce cas, la variable magique parameter
contient l'argument de la notification.TIMER
: lorsque la fonction est appelée sur une expiration du délai d'horloge. Dans ce cas, la variable magique parameter
contient le délai d'attente qui vient d'expirer.Générez puis compilez l'extension. Puis modifiez l'application avec ce code :
#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginscheduler/libsvmscheduler.so"
ARGUMENT INT repetition
ARGUMENT INT interval
ARGUMENT INT loop
ARGUMENT INT timer
PROCESS "controler"
CODE "main" INLINE
:shutdown :when @&repetition IN &0*1
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD @&interval
:scheduler.next
:shift &i
:goto loop :when @&i IN &0*@&repetition
:scheduler.timer TRUE
:run.trace "Timer ON"
:run.sleep HARD @&timer
:run.trace "Timer OFF"
:scheduler.timer FALSE
END
MEMORY repetition interval timer
END
PROCESS "1"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
PROCESS "2"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
PROCESS "3"
CODE "main" INLINE
:memory INT/i
0 -> &i
:label loop
:run.sleep HARD 1
:run.trace @&i
:shift &i
:goto loop :when @&i IN &0*@&loop
END
MEMORY loop
SCHEDULER scheduler.instance
END
Vous pouvez lancer l'application ainsi :
./scheduler.svm 5 5 30 20
Vous constaterez que :
Timer ON
et Timer OFF
, le processus de contrôle ne fait qu'attendre. Cependant, les processus sont bien ordonnancés tour à tour, grâce à l'horloge : il n'est plus nécessaire de provoquer un changement de processus, l'ordonnanceur le fait désormais tout seul régulièrement.Timer OFF
, l'ordonnancement devient très simple : l'horloge étant désactivée et l'ordonnanceur ne recevant plus de notifications pour changer de processus, chaque processus termine son exécution avant de passer la main au suivant.type
permet de déterminer pour quelle raison la fonction de notification a été invoquée.La première opération disponible est la possibilité de récupérer un ordonnanceur à partir de son nom. La fonction de l'interface programmatique svm_scheduler_get
permet cette opération.
Il n'est pas nécessaire de modifier le code de l'extension, car cette fonction est déjà utilisée dans les exemples précédents.
La seconde opération permet de récupérer une version textuelle de l'état de l'ordonnanceur.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
%}
DEFINE
SCHEDULER scheduler.instance
%{
scheduler_instance()
:_timer(0) {}
void suspend(const void *svm)
{
for(auto it=_processes.begin() ; it!=_processes.end() ; ++it)
{
if(it->second==RUNNING)
{
::svm_process_suspend(svm,it->first);
break;
}
}
}
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
unsigned long int _timer;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return object->_timer;
%}
notification object:
%{
if(type==TIMER)
{
object->suspend(svm);
return object->_timer;
}
switch(parameter)
{
case 0:
object->suspend(svm);
break;
case 1:
object->_timer = 3000;
break;
case 2:
object->_timer = 0;
break;
}
return object->_timer;
%}
attach object:
%{
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
INSTRUCTION scheduler.next
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,0);
%}
INSTRUCTION scheduler.timer BLN
%{
SVM_Boolean t = ARGV_VALUE(0,boolean);
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,(t==TRUE)?1:2);
%}
INSTRUCTION scheduler.print -> STR
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
SVM_String ss = ::svm_scheduler_print(svm,s);
return NEW_VALUE(string,ss);
%}
svm_scheduler_get
permet de récupérer une instance d'ordonnanceur à partir de son nom sous forme de point d'entrée d'extension.svm_scheduler_print
permet d'obtenir une version textuelle de l'état d'un ordonnanceur à partir de son instance.Tout comme pour les séquenceurs, il est possible de modifier les ordonnanceurs depuis des instructions ou des types. Il est en revanche obligatoire d'ajouter de la synchronisation autour des modifications de la structure d'un ordonnanceur, car celle-ci est gérée de manière totalement asynchrone par rapport aux processus.
Modifiez le code de l'extension :
PLUGIN scheduler
includes:
%{
#include <list>
#include <algorithm>
#include <mutex>
%}
DEFINE
SCHEDULER scheduler.instance
%{
scheduler_instance()
:_timer(0) {}
void suspend(const void *svm)
{
for(auto it=_processes.begin() ; it!=_processes.end() ; ++it)
{
if(it->second==RUNNING)
{
::svm_process_suspend(svm,it->first);
break;
}
}
}
std::list<std::pair<SVM_Process,SVM_Process_State> > _processes;
unsigned long int _timer;
mutable std::mutex _mutex;
%}
create default: %{}
delete default:
%{
for(auto& p:object->_processes)
{
VARIABLE_LOCAL(p.first);
}
%}
schedule object:
%{
std::lock_guard<std::mutex> lock(object->_mutex);
SVM_Process_State ancien_etat;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
ancien_etat = it->second;
it->second = state;
if((ancien_etat==RUNNING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_back(p);
}
if((ancien_etat==WAITING) and (state==SUSPENDED))
{
auto p = *it;
object->_processes.erase(it);
object->_processes.push_front(p);
}
break;
}
}
bool en_cours = false;
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==RUNNING)
en_cours = true;
}
if(not en_cours)
{
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->second==SUSPENDED)
{
::svm_process_run__raw(svm,it->first,0);
break;
}
}
}
return object->_timer;
%}
notification object:
%{
std::lock_guard<std::mutex> lock(object->_mutex);
if(type==TIMER)
{
object->suspend(svm);
return object->_timer;
}
switch(parameter)
{
case 0:
object->suspend(svm);
break;
case 1:
object->_timer = 3000;
break;
case 2:
object->_timer = 0;
break;
}
return object->_timer;
%}
attach object:
%{
std::lock_guard<std::mutex> lock(object->_mutex);
VARIABLE_GLOBAL(process);
object->_processes.push_back(std::make_pair(process,SUSPENDED));
return TRUE;
%}
detach object:
%{
std::lock_guard<std::mutex> lock(object->_mutex);
for(auto it=object->_processes.begin() ; it!=object->_processes.end() ; ++it)
{
if(it->first==process)
{
VARIABLE_LOCAL(process);
object->_processes.erase(it);
return TRUE;
}
}
return FALSE;
%}
print object:
%{
std::lock_guard<std::mutex> lock(object->_mutex);
std::ostringstream oss;
for(const auto& p: object->_processes)
{
SVM_String s = ::svm_process_print(svm,p.first);
oss << RAW_STRING(s) << std::endl;
}
return NEW_STRING(oss.str());
%}
INSTRUCTION scheduler.next
%{
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,0);
%}
INSTRUCTION scheduler.timer BLN
%{
SVM_Boolean t = ARGV_VALUE(0,boolean);
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
::svm_scheduler_notify__raw(svm,s,(t==TRUE)?1:2);
%}
INSTRUCTION scheduler.custom_timer INT
%{
auto t = ARGV_VALUE(0,integer);
if(t<=0)
{
ERROR_INTERNAL(FAILURE,"Invalid timer");
}
SVM_Scheduler s = ::svm_scheduler_get(svm,CONST_PEP(scheduler,instance));
auto rs = reinterpret_cast<scheduler_instance*>(::svm_scheduler_get_internal(svm,s));
std::lock_guard<std::mutex> lock(rs->_mutex);
rs->_timer = t;
%}
Modifiez l'application pour changer l'intervale de l'horloge à 10000ms.
Notez enfin qu'il est toujours préférable d'utiliser les notifications pour configurer un ordonnanceur, car cela peut éviter des points de contention préjudiciables au fonctionnement de l'ordonnanceur, ainsi qu'une complexité supplémentaire à la maintenance de l'ordonnanceur.
svm_scheduler_get_internal
.Vous venez de voir comment créer et manipuler un ordonnanceur depuis une extension.
La création d'un nouvel ordonnanceur devrait être plutôt rare dans la vie d'un utilisateur. Cependant, bien comprendre comment s'écrit un ordonnanceur peut fortement aider à comprendre leur utilisation dans une application.
Cela permet également de personnaliser une application en y ajoutant un ordonnanceur complètement nouveau, pour atteindre des objectifs de performance tout en préservant les ressources processeur du système hôte.