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

- La machine virtuelle peut exécuter plusieurs processus en même temps.
- Les processus doivent tous être attachés à un ordonnanceur pour être exécutés.
- La machine peut avoir plusieurs ordonnanceurs disponibles et utilisable en même temps.

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

- il active qu'un seul processus parmi tous ceux attachés pendant une courte période,
- lorsque la période expire, il va suspendre le processus,
- une fois le processus suspendu, il va en exécuter un autre.

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.