# 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 :

- elle doit incarner strictement sa politique d'ordonnancement, à savoir elle doit s'assurer que les caractéristiques de cette politique sont constamment respectés (par exemple : si un seul processus doit s'exécuter, elle doit garantir qu'il n'y aura jamais deux processus en exécution à aucun moment, sous peine de dérive) ;
- elle est appelée à chaque changement d'état d'un processus géré par l'ordonnanceur. Il est fortement conseillé de ne pas récupérer l'état du processus directement sur le processus, mais d'utiliser l'état qui a occasionné l'appel de la fonction d'ordonnancement :
  1. en effet, entre le moment où le processus avertit son ordonnanceur de son changement d'état, il peut encore changer d'état, ce qui peut entraîner des confusions et une certaine complexité dans la politique d'ordonnancement,
  2. les états qui sont envoyés à l'ordonnanceur obeissent à des séquences qui sont toujours identiques : R -> S, S -> R, R -> D -> R, R -> W -> S, R -> L -> S, R -> Z, R -> I, permettant de rationaliser l'évolution des états des processus ;
- elle définit deux variables magiques :
  1. `process` qui indique quel processus a changé d'état,
  2. `state` qui indique quel est le nouvel état de ce processus ;
- il est également conseillé de ne pas écrire une fonction bloquante, la réactivité de l'ordonnanceur aux changements d'état des processus en dépend directement.

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

- Un ordonnanceur est un orchestrateur de processus, qui incarne une politique d'ordonnancement précise.
- L'écriture d'un ordonnanceur est plutôt simple en soit, mais demande toutefois une grande rigueur : les erreurs peuvent être difficiles à appréhender.
- La fonction d'ordonnancement sert à réagir aux changements d'état des processus, et c'est la fonction principale d'un ordonnanceur.
- Cette fonction d'ordonnanceur est a priori la seule légitime pour appeler la fonction d'interface programmatique `svm_process_run`, qui (re)lance l'exécution d'un processus.
- Des fonctions annexes d'attachement, de détachement et d'affichage sont requises pour le bon fonctionnement d'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.

- Il est possible d'envoyer des notifications à un ordonnanceur pour contrôler sa politique d'ordonnancement : soit pour lui communiquer une configuration à chaud, soit pour lui transmettre un événement qui doit altérer son ordonnancement.
- Pour que l'ordonnanceur réagisse aux notifications, il faut définir la fonction `notification` sur l'ordonnanceur, dont la variable magique `parameter` contient une valeur entière servant de paramètre à la notification.
- Une notification peut être envoyée à l'ordonnanceur via la fonction `svm_scheduler_notify` en y passant le paramètre entier de la notification.

# 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 :

- `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 :

- Au début, les processus sont ordonnancés comme avec l'application précédente.
- Entre les messages `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.
- Enfin, après le message `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.

- La fonction de notification peut aussi être invoquée lorsqu'un délai expire.
- Ce délai est défini par les valeurs de retour des fonctions d'ordonnancement et de notification :
  - 0 : délai désactivé (l'ordonnanceur attendra indéfiniment un événement),
  - autre valeur : délai activé, la valeur étant le délai en millisecondes.
- La variable magique `type` permet de déterminer pour quelle raison la fonction de notification a été invoquée.

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

- La fonction d'interface programmatique `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.
- La fonction d'interface programmatique `svm_scheduler_print` permet d'obtenir une version textuelle de l'état d'un ordonnanceur à partir de son instance.

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

- Il est possible de récupérer la structure interne d'un ordonnanceur avec la fonction `svm_scheduler_get_internal`.
- Il est impératif d'utiliser de la synchronisation pour lire ou écrire dans la structure d'un ordonnanceur en dehors des fonctions de celui-ci.

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