Introduction

Ce didacticiel est destiné aux utilisateurs sachant écrire un peu de code de la Simple Virtual Machine.

Dans ce didacticiel, vous allez découvrir comment se conçoit la programmation concurrente au sein de la machine virtuelle.

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

Mise en place

Pour commencer, créez le canevas de l'application dans le fichier exécutable ordonnanceur.svm en utilisant ce code :

#!/usr/bin/env svm
DESCRIPTION
Scheduler exploration
END
LOG
DEBUG "Scheduler"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK "application"
	END
END

Plusieurs processus

Dans la machine virtuelle, si les noyaux et le séquenceur auquel ils sont attachés sont placés dans un concept tel que le processus, c'est pour une seule raison : la machine peut faire exécuter plusieurs processus en même temps.

Ainsi, plusieurs instructions (une par processus) peuvent être exécutées en même temps au sein de la machine virtuelle, qui peut alors exploiter les capacités de parallelisation des architectures physiques modernes.

Ajout d'un processus

Modifiez le code pour ajouter un processus à l'application :

#!/usr/bin/env svm
DESCRIPTION
Scheduler exploration
END
LOG
DEBUG "Scheduler"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK "application"
	END
END
PROCESS "second"
	CODE "main" INLINE
		:debug BREAK "second"
	END
END

Puis lancez l'application sans arguments :

./ordonnanceurs.svm
### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
Error: PROCESS second not attached to SCHEDULER

Et cette erreur a un petit goût de déja vu : nous avons eu la même lorsque pour la première fois nous avons tenté d'ajouter un noyau !

En effet, les processus ne s'exécutent pas n'importe comment sur la machine virtuelle. Ils doivent être attachés à un ordonnanceur qui va appliquer une politique d'exécution sur les processus qui lui sont attachés.

Ici, les deux processus sont destinés à être attachés à l'ordonnanceur par défaut de la machine virtuelle, sauf que celui-ci (à l'instar du séquenceur par défaut) n'accepte qu'un seul processus à la fois, d'où le message d'erreur révélé sur le terminal.

Changement d'ordonnanceur

Modifiez le code pour changer l'ordonnanceur du second processus :

#!/usr/bin/env svm
DESCRIPTION
Scheduler exploration
END
LOG
DEBUG "Scheduler"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK "application"
	END
END
PROCESS "second"
	CODE "main" INLINE
		:debug BREAK "second"
	END
	SCHEDULER run.parallel
END

Puis relancez l'application. Cette fois, le message d'erreur n'apparaît plus, car le second ordonnanceur accepte le second processus.

Notez au passage que la machine accepte totalement avoir des processus attachés à des ordonnanceurs différents : cela permet d'adapter l'ordonnancement aux différents processus d'une application complexe.

Lancez l'application en mode débugueur, et ouvrez la liste des ordonnanceurs en cliquant sur "Schedulers" dans le menu principal. Puis ouvrez tous les ordonnanceurs :

Scheduler
Main menu
Breakpoints
Machine
Schedulers
Processes
Kernels
Events
Plugins
Windows list
Scheduler list
SCHEDULER
SCHEDULER run.parallel
SCHEDULER run.rrpreempt
SCHEDULER
SCHEDULER:
PROCESS application: D
SCHEDULER run.parallel
SCHEDULER run.parallel:
PROCESS second: D
SCHEDULER run.rrpreempt
SCHEDULER run.rrpreempt:
Timer 50ms

Cette vue montre qu'il y a trois ordonnanceurs disponibles, le premier étant l'ordonnanceur par défaut de la machine virtuelle. De plus, deux ordonnanceurs ont ici chacun un processus à ordonnancer, même si l'ordonnanceur run.parallel peut accepter plusieurs processus.

Ordonnanceur

Focalisons nous sur le concept d'ordonnanceur.

Fonctionnement

En soit, un ordonnanceur incarne une politique d'ordonnancement des processus qui lui sont attachés.

Pour y parvenir, un ordonnanceur dispose de deux moyens :

  1. il est notifié lorsqu'un processus attaché change d'état d'exécution,
  2. il peut enclencher une attente d'une durée en millisecondes et être notifié lorsque cette attente expire avant qu'un processus change d'état.

Un ordonnanceur incarne une politique d'exécution des processus qui lui sont attachés.

Dans les deux cas, un ordonnanceur peut agir sur les processus qui lui sont attachés : il peut relancer leur exécution, les suspendre ou les arrêter.

Exemples

Modifiez le code pour obtenir :

#!/usr/bin/env svm
DESCRIPTION
Scheduler exploration
END
LOG
DEBUG "Scheduler"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PLUGIN "svmint.so"
PLUGIN "svmstr.so"
ARGUMENT INT nb
ARGUMENT INT loops
PROCESS "application"
	CODE "main" INLINE
		:memory INT/i, (STR, INT)/parameters
		0 -> &i
	:label create
		:int.print @&i -> &parameters
		:str.join "child_" @&parameters -> &parameters
		@&loops -> (parameters/1)
		:run.parallel_call $"child" parameters @&parameters SCHED=run.parallel PARAMS=MOVE
		:shift &i
		:goto create :when @&i IN &0*@&nb
		:shutdown
	:symbol child
		:com.message @&P " started"
		:memory INT/i
		0 -> &i
	:label loop
		:shift &i
		:goto loop :when @&i IN &0*@(P/1)
		:com.message @&P " stopped"
	END
	MEMORY nb loops
END

Avant de lancer l'application, regardons l'instruction :run.parallel_call : cette instruction simule un appel de fonction dont le code s'exécute dans un autre processus. C'est pour cela que vous trouvez notamment les arguments SCHED=run.parallel pour indiquer quel ordonnanceur utiliser.

Maintenant, lancez l'application avec ses deux arguments :

  1. le premier correspond au nombre de processus créés, et il est conseillé d'en créer moins que de processeurs physiques,
  2. le second correspond au nombre de boucles (pour faire exécuter un grand nombre d'instructions inutiles) exécutées par chaque processus créé, et il est conseillé de mettre un nombre assez grand pour observer l'exécution de l'application.

Par exemple :

./ordonnanceurs.svm 5 10000000
child_0 started
child_1 started
child_2 started
child_3 started
child_4 started
child_4 stopped
child_3 stopped
child_1 stopped
child_2 stopped
child_0 stopped

En soit, le résultat n'est pas très intéressant. Cependant, relancez l'application et observez son exécution sur un moniteur système permettant de voir les threads (comme htop, par exemple). Dans le cas de l'ordonnanceur run.parallel, tous les processus utilisent 100% d'un cœur physique : cet ordonnanceur exécute tous les processus en même temps au maximum des capacités de la machine physique.

Modifiez le nom de l'ordonnanceur dans le code pour utiliser run.rrpreempt :

#!/usr/bin/env svm
DESCRIPTION
Scheduler exploration
END
LOG
DEBUG "Scheduler"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PLUGIN "svmint.so"
PLUGIN "svmstr.so"
ARGUMENT INT nb
ARGUMENT INT loops
PROCESS "application"
	CODE "main" INLINE
		:memory INT/i, (STR, INT)/parameters
		0 -> &i
	:label create
		:int.print @&i -> &parameters
		:str.join "child_" @&parameters -> &parameters
		@&loops -> (parameters/1)
		:run.parallel_call $"child" parameters @&parameters SCHED=run.rrpreempt PARAMS=MOVE
		:shift &i
		:goto create :when @&i IN &0*@&nb
		:shutdown
	:symbol child
		:com.message @&P " started"
		:memory INT/i
		0 -> &i
	:label loop
		:shift &i
		:goto loop :when @&i IN &0*@(P/1)
		:com.message @&P " stopped"
	END
	MEMORY nb loops
END

Relancez l'application avec les mêmes arguments, et observez dans le moniteur système. Cette fois, les processus sont exécutés en divisant 100% par le nombre de processus créés, et un seul processus semble être actif à chaque moment.

En effet, l'ordonnanceur run.rrpreempt (pour Round-Robin preemptive) a une politique différente d'exécution :

Cela répartit la charge des processus attachés à l'équivalent d'un seul cœur physique !

Ordonnanceurs existants

L'ordonnanceur par défaut est le seul qui est implémenté directement dans la machine virtuelle.

Les autres ordonnanceurs sont fournis par les extensions, et leur politique d'exécution devrait être généralement décrite dans l'aide associée à l'ordonnanceur.

Conclusion

Vous venez de voir que la machine virtuelle accepte plusieurs processus s'exécutant en concurrence avec des politiques d'exécution incarnée par des ordonnanceurs.

Bien choisir l'ordonnanceur de chaque processus d'une application permet tout d'abord d'obtenir un compromis entre la réactivité attendue par les utilisateurs et l'empreinte sur le système physique de l'exécution de l'application.

De plus, certains ordonnanceurs peuvent communiquer avec les processus qui leur sont attachés pour réaliser des ordonnancements bien plus complexes et plus adaptés que les ordonnanceurs génériques du système d'exploitation.