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 créer et manipuler les ordonnanceurs de la machine virtuelle.

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

Mise en place

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

PLUGIN scheduler

DEFINE

Ordonnanceur simple

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.

Structure

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;
%}

Création et destruction

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 :

  1. les ordonnanceurs sont tous lancés (et leur structure construite) lors du démarrage de la machine virtuelle,
  2. les ordonnanceurs sont arrêtes (et leur structure détruite) lorsque la machine virtuelle a terminé l'exécution du code de l'application.

Ordonnancement

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 :

Attachement et détachement de 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.

Affichage

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.

Ordonnanceur avec notification

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.

Modification de la politique d'ordonnancement

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());
%}

Ajout notification

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 :

  1. liste de processus : pour suivre en direct l'état d'exécution des processus,
  2. ordonnanceur scheduler.instance : pour suivre la vue de l'ordonnanceur, et de comment il organise les processus dans sa liste de processus,
  3. événements : pour observer les notifications et les événements qui en résultent au fur et à mesure de l'exécution.

Ordonnanceur avec horloge

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.

Modification notification

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.

Ajout de l'horloge

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 :

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 :

Opérations

Récupération

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.

Affichage

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);
%}

Modification externe

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.

Conclusion

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.