Introduction

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

Dans ce didacticiel, vous allez explorer la mémoire 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 memoire.svm en utilisant ce code :

#!/usr/bin/env svm
DESCRIPTION
Memory exploration
END
LOG
DEBUG "Memory"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
	END
END

Eléments mémoire

Modifiez le code pour ajouter de la mémoire :

#!/usr/bin/env svm
DESCRIPTION
Memory exploration
END
LOG
DEBUG "Memory"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/i, STR*3/s, PTR/p
		:debug BREAK
	END
END

Puis lancez l'application en mode débugueur. Sur le point d'arrêt, la mémoire ressemble à :

Memory
Memory - K main - P application
AddressTypeValue
&0INT
&1STR
&2STR
&3STR
&4PTR
AliasPointer
i&0*1
p&4*1
s&1*3
Address:
Display
Aliases
P
Focus
Back
Clear

Le premier tableau donne une idée de la structure générale de la mémoire : elle est principalement composée d'éléments indépendants placés l'un après l'autre dans une séquence.

Position

La première colonne indique la position d'un élément dans la séquence : cette position numérique est une adresse mémoire, qui identifie de manière univoque un seul élément mémoire.

Les adresses mémoire présentes ici ont un lien étroit avec celles écrites dans le code machine : celles du code permettent de calculer les adresses utilisées dans les opérations mémoire, elles mêmes mise en correspondance avec celles existant dans la mémoire au moment de l'opération mémoire.

Modifiez le code pour ajouter deux écritures en mémoire :

#!/usr/bin/env svm
DESCRIPTION
Memory exploration
END
LOG
DEBUG "Memory"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/i, STR*3/s, PTR/p
		"123" -> &1
		"abc" -> (s/1)
		:debug BREAK
	END
END

Lancez l'application dans le débugueur, et observez où les valeurs ont été écrites en mémoire :

  1. la première est écrite explicitement dans l'adresse &1,
  2. la seconde est écrite dans une adresse qui se trouve être le résultat d'un calcul d'adresse. Ici, il s'agit de l'adresse &2.

Type

Modifiez le code à nouveau :

#!/usr/bin/env svm
DESCRIPTION
Memory exploration
END
LOG
DEBUG "Memory"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/i, STR*3/s, PTR/p
		"123" -> &1
		"abc" -> (s/1)
		"xyz" -> &0
		:debug BREAK
	END
END

Puis lancez l'application sans le débugueur :

./memoire.svm
### Simple Virtual Machine 12345 : PROCESS application | 2023-01-01 00:00:00 GMT #####################################################################
Kernel interrupted: @(main, line 4) Interruption MEMORY not handled: Write to defined address &0 with a value of type STR instead of INT.

Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(main, line 4) Interruption MEMORY not handled: Write to defined address &0 with a value of type STR instead of INT.

Processor:
 State:
   Next instruction: <main:1/4> (Current one: <main:1/3>)
   Current memory  : &0*0
   Allocated memory: &0*5
   Aliases         : i p s
   Flags           :
   Cascaded flags  :
   Local interruption handlers:
   Cascaded local interruption handlers:
 Saved states:
 Global interruption handlers:

Code at <main:1/3>:
     :memory INT/i , STR*3/s , PTR/p    # <0> @(main, line 1)
     "123" -> &1        # <1> @(main, line 2)
     "abc" -> (s/1)     # <2> @(main, line 3)
====== HERE ======
     "xyz" -> &0        # <3> @(main, line 4)
==================
     :debug BREAK       # <4> @(main, line 5)
 Labels:
 Symbols:


Memory:
  &0: INT
  &1: STR "123"
  &2: STR "abc"
  &3: STR
  &4: PTR
 Aliases:
  i -> &0*1
  p -> &4*1
  s -> &1*3
 Free space:



### Simple Virtual Machine 12345 : SYSTEM | 2023-01-01 00:00:00 GMT ##################################################################################
Process application interrupted: @(main, line 4) Interruption MEMORY not handled: Write to defined address &0 with a value of type STR instead of INT.


Un élément mémoire ne peut pas accueillir n'importe quelle valeur. En effet, chaque élément mémoire contient un type servant à limiter le stockage de valeurs ayant le même type. Cela permet de structurer le stockage des valeurs en mémoire et de rendre explicites les erreurs de lecture ou d'écriture en mémoire, comme dans l'exemple ci-dessus où une chaine de caractère tente d'être écrite à une adresse n'acceptant que des entiers.

Valeur

Un élément mémoire peut contenir une valeur dont le type correspond à celui de l'élément. Dans l'exemple précédent, les adresses impliquées dans une écriture mémoire réussie contiennent une valeur, là où les autres n'en contiennent pas.

La valeur ne peut être lue depuis un élément mémoire (et donc depuis son adresse) que si une valeur y a été écrite au préalable : un élément mémoire supporte le fait de ne pas avoir de valeur.

Dans ce cas, l'élément contient une valeur dite nulle (ce qui est particulièrement vrai pour les pointeurs : un pointeur nul est un pointeur sans valeur).

Synchronisation

Dans certaines circonstances, les accès mémoire en lecture ou en écriture ne peuvent pas être faits au même moment sur une adresse donnée. Dans tels cas, un verrou peut être activé dans un élément mémoire pour s'assurer que des lectures et écritures sur l'adresse correspondante seront faites en séquence.

Etats d'une adresse mémoire

Dans l'exemple précédent, observez les adresses :

  1. &5 : cette adresse n'est pas dans la liste des adresses connues de la mémoire, et pour cause, aucun élément mémoire lui est associé. Dans ce cas, les opérations de lecture et d'écriture vers cette adresse vont échouer. L'adresse est dite non définie.
  2. &0 : cette adresse est dans la liste des adresses connues de la mémoire, mais aucune valeur n'a été écrite à cette adresse. Une opération de lecture a cette adresse va échouer, mais une écriture reste possible. L'adresse est dite définie, et parfois non initialisée.
  3. &1 : cette adresse est dans la liste des adresses connues de la mémoire et contient une valeur. Les opérations de lecture et d'écriture vont dès lors réussir. L'adresse est dite initialisée.

Après une allocation mémoire, les adresses nouvellement créées sont toujours définies mais non initialisées. Cela permet d'éviter l'écueil des valeurs lues avant toute écriture, généralement catastrophique sur les machines réelles.

Alias

D'une exécution de l'application à l'autre, les données en mémoire peuvent ne pas être à la même adresse car l'allocation mémoire choisit automatiquement où les adresses vont être définies. Cependant, certaines données doivent rester facilement accessible sans un calcul d'adresse complexe à maintenir : la mémoire peut attribuer un nom à une zone mémoire, et ce nom est appellé alias.

La mémoire contient l'association entre chaque alias et la zone mémoire désignée par un pointeur mémoire de façon à ce qu'un alias ne peut référencer qu'un seul pointeur à la fois.

Au tout début de ce didacticiel, la fenêtre mémoire du débugueur montre cette table d'association entre les alias et les pointeurs correspondants.

Conclusion

Vous venez de voir comment fonctionne la mémoire de la machine virtuelle.

Il est important de bien maîtriser comment les valeurs utilisées intensivement par les instructions sont stockées, pour anticiper les interruptions qui peuvent être émises lors d'opérations mémoire invalides.

Cette maîtrise permet également d'organiser correctement les valeurs utilisées par l'application en ensembles cohérents, en utilisant toutes les vérifications faites par la mémoire pour détecter au plus tôt une erreur de code.