Téléchargé 16 fois
Vote des utilisateurs
1
1
Détails
Licence : Non renseignée
Mise en ligne le 9 septembre 2016
Langue : Français
Référencé dans
Navigation
TimerOS: un système d'exploitation qui affiche l'horloge :)
TimerOS: un système d'exploitation qui affiche l'horloge :)
Et voilà un petit système d'exploitation (4 KiB :) écrit en assembleur et en langage C. Quelques lignes de code ont été copiées du code source du noyau Linux-0.01:) avec quelques modifications!
Le système va juste démarrer votre PC, faire quelques initialisations nécessaires pour passer en mode protégé et exécuter le noyau. Celui-ci, va juste afficher l'horloge à l'écran. C'est en utilisant l'interruption de timer (timer interrupt) et la mémoire vidéo VGA.
Avec ce système, votre ordinateur deviendra une montre numérique :)
Architecture
------------------
Le système peut tourné sur toute machine compatible avec l'IBM-PC (Un HP Pavilion dv6000 dans mon cas) et occupée par un processeur de la famille INTEL x86_32 (un Pentium Dual Core dans mon cas). Vous pouvez également utiliser une machine virtuelle pour tester le système. Qemu et VirtualBox vous permettent d'en créer une.
Compilation
-----------------
Le code source contient deux Makefiles:
Installation
----------------
Vous pouvez tester le système en utilisant une machine virtuelle. Avec Qemu vous pouvez lancer le système à l'aide de la commande suivante:
qemu-system-x86_64 Image
Pour installer le système sur un flash disque procédez comme suit:
Modèle de la mémoire
---------------------------------
Le modèle de la mémoire utilisé par le système et le Flat Memoy Model. Autrement dit, le système utilise les mêmes segments (espace mémoire linéaire) pour le code(CS), les données(DS,ES,FS,GS) et la pile(SS). C'eat le même modèle de la mémoire utilisé par le noyau Linux. Ainsi, notre GDT (Global Descriptor Table) est initialisé, dans boot/boot.s et boot/head.s, comme suit:
gdt:
.quad 0x0000000000000000
.quad 0x00CF9B000000FFFF
.quad 0x00CF93000000FFFF
Ici, on a défini deux descripteurs de segments. Selon ces descripteurs, chaque segment et de taille 4 GiB (Limit = 0xFFFFF et Granularity Bit = 1) et commence à l'adresse physique 0 (Base = 0x00000000).
L'indice 0x8 de deuxième descripteur sera chargé dans le registre CS (Code Segment) en exécutant l'instruction LJMP dans le fichier boot/boot.s. Ainsi, le processeur va rechercher les instructions à exécuter dans le segment indiqué par ce descripteur. L'indice 0x10 de troisième descripteur sera chargé dans les registres DS,SS, ES, FS, GS à l'aide de l'instruction MOV dans le fichier boot/head.s. Ainsi les données seront accédées en lecture et en écriture dans le segment spécifié par ce troisième descripteur. Aussi les données de la pile seront empilées et dépilées dans le même segment de données!
Note: Le noyau n'implémente pas le modèle paginé de la mémoire. La pagination de la mémoire est désactivée. Ainsi, les adresses physiques et les adresses linéaires sont égaux.
Le mode protégé
--------------------------
Au démarrage de l'ordinateur, Tous les processeurs de la famille x86 fonctionnent en mode réel (ou mode 8086). Ainsi, le processeur ne peut exécuter que des codes 16-bit et ne peut adresser que 1MiB de la mémoire. Pour exécuter des codes 32-bit et pour accéder à plus que 1MiB de la mémoire (4GiB) et pour se profiter de toutes les fonctionnalités du processeur on doit passer en mode protégé (Protected Mode). Notre système, comme tout autre système moderne tel que Linux :), fonctionne en mode protégé. Le passage de mode réel en mode protégé est accomplis par la mise à 1 de Bit PE (Protection Enable) de registre de contrôle CR0 en exécutant les lignes de code suivantes:
mov %cr0, %eax
or $1, %ax
mov %eax, %cr0
Mais, avant de faire ça le processeur doit accomplir quelques opérations d'initialisation. Au minimum, le processeur doit désactiver les interruptions matérielles à l'aide de l'instruction CLI et initialiser un GDT. C'est exactement ce que nous avons codé dans le fichier boot/boot.s. Consultez le manuel INTEL pour avoir accès au différentes étapes qu'il faut suivre pour activer convenablement le mode protégé.
Dans ce mode, le processeur est très sensible au erreurs de votre code (les bugs du code). Par exemple, en accédant à la mémoire, si le processeur ne trouvait pas un descripteur convenable dans le GDT, il va générer un Triple Fault qui va provoquer une séquence infini de redémarrage de l'ordinateur!
Pour savourer le goût réel du mode protégé, le processeur doit exécuter un programme 32-bit. C'est on exécutant un LJMP vers un segment contenant un code 32-bit, juste après la mise à 1 de bit PE! Selon le manuel INTEL, un LJMP d'un code 16-bit à un code 32-bit nécessite l'utilisation de prefixe 0x66 comme suit:
.byte 0x66, 0xea
.long ok_pm + 0x7c00
.word 0x8
Finalement, on est en mode protégé et le processeur est entrain d'exécuter un code 32-bit. Les interruptions matérielles sont encore désactivées (ou masquées). Il faut initialiser le IDT (Interrupt Descriptor Table) avant de les activer avec l'instruction STI.
Chargement à la mémoire et prise de contrôle
-------------------------------------------------------------------
* Le programme d'amorçage (/boot/boot.s)
Le binaire de programme d'amorçage boot.s doit être stocké sur le premier secteur d'un périphérique de stockage de masse (Un flash disque dans notre cas). Il sera chargé par le programme de démarrage du BIOS à l'adresse physique 0x7C00. C'est une convention IBM qu'on doit respecter. Lorsqu'il prend le contrôle (de la part du BIOS), il va accomplir les opérations suivantes:
** Le kernel head (boot/head.s)
C'est le premier portion du code 32-bit du système qui sera exécuté. Son rôle est de réinitialiser le GDT, initialiser le IDT et d'appeler la fonction principale main du système.
Parlant un peu de IDT. Notre système n'utilisera qu'une seule interruption. C'est l'interruption de timer ayant comme ID (Identificateur) 0x20. Ainsi, on a initialisé toutes les entrées (inclus l'entrée 0x20 pour le moment) par le même Interrupt Gate Descriptor. C'est en faisant appel au sous programme setup_idt.
*** La fonction principale (init/main.c)
Cette fonction est appelé dans le programme head.s. Elle s'exécute jusqu'à ce que vous éteindre votre machine. C'est à cause de la boucle infini for(;;){}. Elle permet d'accomplir les opérations suivantes:
**** Le noyau (kernel/isr.s et kernel/timer.c)
Dans un système d'exploitation évolué, comme Linux, le noyau est la portion du code principale qui va réagir avec l'ordinateur (mémoire, périphériques ...) d'une part et qui va rendre service à l'utilisateur (Il lui permettra d'exploiter les ressources de l'ordinateur: utiliser le disque dur, la carte réseau ...) d'autre part.
Bon, notre pauvre noyau va juste afficher l'horloge sur l'écran. C'est en utilisant l'interruption de PIT (PIT: Programmable Interrupt Timer) INTEL 8253 ou 8254 (pour mettre à jour l'horloge) et la mémoire vidéo (pour l'affichage). Le PIT sera initialisé tel que il est indiqué par les lignes de commentaires dans le code source. Le fichier kernel/isr.s contient le code source de l'ISR (Interrupt Service Routine) qui sera exécuté à chaque interruption de PIT (HZ = 100 timer interrupt par seconde). Lorsqu'il sera exécuté, ce routine va incrémenter la variable jiffies et appeler a fonction do_timer(). A son tour, la fonction do_timer() va extraire les heures, les minutes et les secondes du variable jiffies et les afficher en format: hh:mm:ss.
A chaque interruption de timer le processeur va consulter l'entrée(descripteur) numéro 32 (0x20) dans le IDT. Ce descripteur contient les informations nécessaires, réquises par le processeur, pour localiser à la mémoire et exécuter le ISR convenable. Ce descripteur est de type Interrupt gate. Le macro set_intr_gate() défini dans le fichier include/system.h, permet d'initialiser ce déscripteur. Le champ Offset doit contenir l'adresse &timer_interrupt de la portion du code contenue dans le fichier kernel/isr.s. Le champ Offset doit contenir l'indice 0x8 de descripteur de segment de code dans le GDT.
Avant, d'afficher l'horloge, on doit convertir les heures, les minutes et les secondes calculés de format binaire en des codes ASCII. L'affichage aura lieu par l'envoi de ces codes ASCII à la mémoire vidéo VGA (mode 80x25 16 couleur texte) situé à l'adresse 0xb8000-0xbFFFF. Le pointeur video_ptr est utilisé pour accéder à cette zone mémoire depuis un code C :)
Notre système ne permet pas d'exploiter efficacement un ordinateur. Il permet juste de démarrer l'ordinateur, depuis un flash disque, et afficher l'horloge. Alors qu'un système d'exploitation doit, au minimum, nous permettre d'exploiter le clavier, l'écran et le disque dur! Bon, implémenter ses fonctionnalités est simple, mais il nécessite trop de temps. Si vous êtes passionnés (comme moi:) par le développement des OS vous pouvez démarrer avec le noyau Linux-0.01. Son code source est très simple par rapport aux autres versions du noyau, mais il nécessite trop de corrections!!
Le système va juste démarrer votre PC, faire quelques initialisations nécessaires pour passer en mode protégé et exécuter le noyau. Celui-ci, va juste afficher l'horloge à l'écran. C'est en utilisant l'interruption de timer (timer interrupt) et la mémoire vidéo VGA.
Avec ce système, votre ordinateur deviendra une montre numérique :)
Architecture
------------------
Le système peut tourné sur toute machine compatible avec l'IBM-PC (Un HP Pavilion dv6000 dans mon cas) et occupée par un processeur de la famille INTEL x86_32 (un Pentium Dual Core dans mon cas). Vous pouvez également utiliser une machine virtuelle pour tester le système. Qemu et VirtualBox vous permettent d'en créer une.
Compilation
-----------------
Le code source contient deux Makefiles:
- kernel/Makefile: Ce Makefile permet d'assembler (avec l'option -c!) le fichier isr.s et de compiler le fichier timer.c. Les fichiers objets générés (isr.o et timer.o) seront, ensuite, liés avec l'option -r (de l'éditeur de liens ld) pour générer le fichier kernel.o en format relocatable ELF. Note:On a utilisé l'option -r par ce que le fichier kernel.o sera ensuite relié avec les autres fichiers objet pour produire le fichier binaire system!
- Le Makefile principal:
- Assembler et lier le fichier /boot/boot.s. L'édition de liens est accomplis avec l'option --oformat binary pour générer un fichier en format raw binary. les options -Ttext et -Tdata sont utilisées pour indiquer à l'éditeur de liens que les sections de texte et de données doivent être chargées à l'adresses 0x0 et 0x7C00. Notez bien que le programme boot (le programme d'amorçage de notre système) sera chargé par le BIOS à l'adresse physique 0x7C00, et que dans le fichier boot.s les données et le code sont inclus dans la même section .text et que l'instruction lgdt doit charger une adresse linéaire (physique) dans le registre GDTR, et que, et que, et que ... :)
- Générer les fichiers objets boot/head.o, init/main.o, kernel/kernel.o.
- Lier les fichiers boot/head.o, init/main.o, kernel/kernel.o pour produire le fichier binaire system. Notre système sera chargé, par le programme d'amorçage, à l'adresse linéaire (ou physique puisqu'on ne va pas utiliser la mémoire paginée) 0x1000. Donc, on a passé l'option -Ttext 1000 à l'éditeur de liens.
- Finalement, les fichiers binaires boot et system seront réunis pour produire le fichier image Image de notre système en exécutant la commande suivant:
cat boot/boot system > Image
- Assembler et lier le fichier /boot/boot.s. L'édition de liens est accomplis avec l'option --oformat binary pour générer un fichier en format raw binary. les options -Ttext et -Tdata sont utilisées pour indiquer à l'éditeur de liens que les sections de texte et de données doivent être chargées à l'adresses 0x0 et 0x7C00. Notez bien que le programme boot (le programme d'amorçage de notre système) sera chargé par le BIOS à l'adresse physique 0x7C00, et que dans le fichier boot.s les données et le code sont inclus dans la même section .text et que l'instruction lgdt doit charger une adresse linéaire (physique) dans le registre GDTR, et que, et que, et que ... :)
Installation
----------------
Vous pouvez tester le système en utilisant une machine virtuelle. Avec Qemu vous pouvez lancer le système à l'aide de la commande suivante:
qemu-system-x86_64 Image
Pour installer le système sur un flash disque procédez comme suit:
- Connectez votre flash disque à votre PC.
- Attention: toutes les données sauvegardées sur le flash seront perdues. Donc, copiez les sur votre disque dur avant de passer à l'étape suivante!!
- Ouvrir un terminal. Puis, en tant que root, entrer la commande fdisk -l pour afficher les noms de tous les disques connectés à votre PC. Vérifiez le nom de votre flash disque. Attention: ne pas confondre entre les noms de flash disque et ceux des partitions de vos disques durs !!!
- Dans le fichier Makefile principal, dé-commenter la ligne: ## sudo dd_rescue -A Image /dev/sdx
- Remplacer /dev/sdx par le nom de votre flash disque.
- Utilisez la commande make install pour insaller le fichier binaire Image sur votre flash disque en partant du premier secteur (le secteur d'amorçage).
- Redémarrez votre PC pour s'amuser :)
Modèle de la mémoire
---------------------------------
Le modèle de la mémoire utilisé par le système et le Flat Memoy Model. Autrement dit, le système utilise les mêmes segments (espace mémoire linéaire) pour le code(CS), les données(DS,ES,FS,GS) et la pile(SS). C'eat le même modèle de la mémoire utilisé par le noyau Linux. Ainsi, notre GDT (Global Descriptor Table) est initialisé, dans boot/boot.s et boot/head.s, comme suit:
gdt:
.quad 0x0000000000000000
.quad 0x00CF9B000000FFFF
.quad 0x00CF93000000FFFF
Ici, on a défini deux descripteurs de segments. Selon ces descripteurs, chaque segment et de taille 4 GiB (Limit = 0xFFFFF et Granularity Bit = 1) et commence à l'adresse physique 0 (Base = 0x00000000).
L'indice 0x8 de deuxième descripteur sera chargé dans le registre CS (Code Segment) en exécutant l'instruction LJMP dans le fichier boot/boot.s. Ainsi, le processeur va rechercher les instructions à exécuter dans le segment indiqué par ce descripteur. L'indice 0x10 de troisième descripteur sera chargé dans les registres DS,SS, ES, FS, GS à l'aide de l'instruction MOV dans le fichier boot/head.s. Ainsi les données seront accédées en lecture et en écriture dans le segment spécifié par ce troisième descripteur. Aussi les données de la pile seront empilées et dépilées dans le même segment de données!
Note: Le noyau n'implémente pas le modèle paginé de la mémoire. La pagination de la mémoire est désactivée. Ainsi, les adresses physiques et les adresses linéaires sont égaux.
Le mode protégé
--------------------------
Au démarrage de l'ordinateur, Tous les processeurs de la famille x86 fonctionnent en mode réel (ou mode 8086). Ainsi, le processeur ne peut exécuter que des codes 16-bit et ne peut adresser que 1MiB de la mémoire. Pour exécuter des codes 32-bit et pour accéder à plus que 1MiB de la mémoire (4GiB) et pour se profiter de toutes les fonctionnalités du processeur on doit passer en mode protégé (Protected Mode). Notre système, comme tout autre système moderne tel que Linux :), fonctionne en mode protégé. Le passage de mode réel en mode protégé est accomplis par la mise à 1 de Bit PE (Protection Enable) de registre de contrôle CR0 en exécutant les lignes de code suivantes:
mov %cr0, %eax
or $1, %ax
mov %eax, %cr0
Mais, avant de faire ça le processeur doit accomplir quelques opérations d'initialisation. Au minimum, le processeur doit désactiver les interruptions matérielles à l'aide de l'instruction CLI et initialiser un GDT. C'est exactement ce que nous avons codé dans le fichier boot/boot.s. Consultez le manuel INTEL pour avoir accès au différentes étapes qu'il faut suivre pour activer convenablement le mode protégé.
Dans ce mode, le processeur est très sensible au erreurs de votre code (les bugs du code). Par exemple, en accédant à la mémoire, si le processeur ne trouvait pas un descripteur convenable dans le GDT, il va générer un Triple Fault qui va provoquer une séquence infini de redémarrage de l'ordinateur!
Pour savourer le goût réel du mode protégé, le processeur doit exécuter un programme 32-bit. C'est on exécutant un LJMP vers un segment contenant un code 32-bit, juste après la mise à 1 de bit PE! Selon le manuel INTEL, un LJMP d'un code 16-bit à un code 32-bit nécessite l'utilisation de prefixe 0x66 comme suit:
.byte 0x66, 0xea
.long ok_pm + 0x7c00
.word 0x8
Finalement, on est en mode protégé et le processeur est entrain d'exécuter un code 32-bit. Les interruptions matérielles sont encore désactivées (ou masquées). Il faut initialiser le IDT (Interrupt Descriptor Table) avant de les activer avec l'instruction STI.
Chargement à la mémoire et prise de contrôle
-------------------------------------------------------------------
* Le programme d'amorçage (/boot/boot.s)
Le binaire de programme d'amorçage boot.s doit être stocké sur le premier secteur d'un périphérique de stockage de masse (Un flash disque dans notre cas). Il sera chargé par le programme de démarrage du BIOS à l'adresse physique 0x7C00. C'est une convention IBM qu'on doit respecter. Lorsqu'il prend le contrôle (de la part du BIOS), il va accomplir les opérations suivantes:
- Mettre à jours le contenu des registres: DS, ES, SS et SP.
- Définir le mode vidéo et afficher un message à l'utilisateur en utilisant les interruptions BIOS.
- Charger le système (le binaire system) dans la mémoire en utilisant l'interruption BIOS convenable. Contrairement au noyau Linux 0.01, notre système sera chargé à l'adresse physique 0x1000 (SYSSEG = 0x100). Notez qu'avec une machine compatible avec l'IBM-PC, le premier 1KiB de la mémoire est réservé pour le IVT (Interrupt Vector Table) et les données de BIOS. On a choisi de ne pas toucher ce premier 1KiB. De plus on a un espace mémoire suffisant pour charger notre pauvre sytème;)
- Initialiser les deux 8259A PIC en envoyant des ICW au ports convenables.
- Passer en mode protégé. C'est déjà expliqué ci-dessus.
- Transférer le contrôle au noyau en exécutant un LJMP vers le code 32-bit.
** Le kernel head (boot/head.s)
C'est le premier portion du code 32-bit du système qui sera exécuté. Son rôle est de réinitialiser le GDT, initialiser le IDT et d'appeler la fonction principale main du système.
Parlant un peu de IDT. Notre système n'utilisera qu'une seule interruption. C'est l'interruption de timer ayant comme ID (Identificateur) 0x20. Ainsi, on a initialisé toutes les entrées (inclus l'entrée 0x20 pour le moment) par le même Interrupt Gate Descriptor. C'est en faisant appel au sous programme setup_idt.
*** La fonction principale (init/main.c)
Cette fonction est appelé dans le programme head.s. Elle s'exécute jusqu'à ce que vous éteindre votre machine. C'est à cause de la boucle infini for(;;){}. Elle permet d'accomplir les opérations suivantes:
- Initialiser l'horloge: C'est en appelant la fonction time_init(). Comme j'ai dit, notre système va juste afficher l'horloge à l'écran. Ainsi, on doit l'initialiser. Comment ? L''horloge (RTC: Real Time Clock) de l'ordinateur est stocké dans une mémoire CMOS interne (intégré à la carte mère) et qui possède son propre source d'alimentation. La fonction time_init() va utiliser les instructions assembleur IN et OUT de manipulation des ports d'entrées/sorties pour lire l'horloge (le temps de démarrage de l'ordinateur) et le récupérer dans la variable startup_time.
- Initialiser le timer: En appelant la fonction timer_init()
- Activer les interruptions matérielles: Après la réinitialisation de l'IDT par la fonction timer_init(), notre système est prêt à réagir à l'interruption de timer. Donc on peut les activer, en exécutant l'instruction STI.
**** Le noyau (kernel/isr.s et kernel/timer.c)
Dans un système d'exploitation évolué, comme Linux, le noyau est la portion du code principale qui va réagir avec l'ordinateur (mémoire, périphériques ...) d'une part et qui va rendre service à l'utilisateur (Il lui permettra d'exploiter les ressources de l'ordinateur: utiliser le disque dur, la carte réseau ...) d'autre part.
Bon, notre pauvre noyau va juste afficher l'horloge sur l'écran. C'est en utilisant l'interruption de PIT (PIT: Programmable Interrupt Timer) INTEL 8253 ou 8254 (pour mettre à jour l'horloge) et la mémoire vidéo (pour l'affichage). Le PIT sera initialisé tel que il est indiqué par les lignes de commentaires dans le code source. Le fichier kernel/isr.s contient le code source de l'ISR (Interrupt Service Routine) qui sera exécuté à chaque interruption de PIT (HZ = 100 timer interrupt par seconde). Lorsqu'il sera exécuté, ce routine va incrémenter la variable jiffies et appeler a fonction do_timer(). A son tour, la fonction do_timer() va extraire les heures, les minutes et les secondes du variable jiffies et les afficher en format: hh:mm:ss.
A chaque interruption de timer le processeur va consulter l'entrée(descripteur) numéro 32 (0x20) dans le IDT. Ce descripteur contient les informations nécessaires, réquises par le processeur, pour localiser à la mémoire et exécuter le ISR convenable. Ce descripteur est de type Interrupt gate. Le macro set_intr_gate() défini dans le fichier include/system.h, permet d'initialiser ce déscripteur. Le champ Offset doit contenir l'adresse &timer_interrupt de la portion du code contenue dans le fichier kernel/isr.s. Le champ Offset doit contenir l'indice 0x8 de descripteur de segment de code dans le GDT.
Avant, d'afficher l'horloge, on doit convertir les heures, les minutes et les secondes calculés de format binaire en des codes ASCII. L'affichage aura lieu par l'envoi de ces codes ASCII à la mémoire vidéo VGA (mode 80x25 16 couleur texte) situé à l'adresse 0xb8000-0xbFFFF. Le pointeur video_ptr est utilisé pour accéder à cette zone mémoire depuis un code C :)
Notre système ne permet pas d'exploiter efficacement un ordinateur. Il permet juste de démarrer l'ordinateur, depuis un flash disque, et afficher l'horloge. Alors qu'un système d'exploitation doit, au minimum, nous permettre d'exploiter le clavier, l'écran et le disque dur! Bon, implémenter ses fonctionnalités est simple, mais il nécessite trop de temps. Si vous êtes passionnés (comme moi:) par le développement des OS vous pouvez démarrer avec le noyau Linux-0.01. Son code source est très simple par rapport aux autres versions du noyau, mais il nécessite trop de corrections!!
Nos ressources disponibles
Le code source du système est écrit en:
Donc, voilà quelques tutoriels utiles:
* Documentation INTEL: INTEL x32-64 Megadoc
* Code source du noyau: Linux-0.01
- Assembleur GNU
- Assembleur en ligne
- Langage C
Donc, voilà quelques tutoriels utiles:
- Un tutoriel sur l'assembleur GNU et l'architecture x86
- Un tutoriell sur l'assembleur en ligne avec le langage C
- Des tutoriels sur le langage C
* Documentation INTEL: INTEL x32-64 Megadoc
* Code source du noyau: Linux-0.01
Bien je dirais plutôt que c'est un "utilitaire sans OS", car un OS sert justement à exploiter le système, alors que là
on exploite pas grand chose (le minimum étant clavier/écran/RAM/mémoire de masse)
Mais ça peut-être une base intéressante pour tous tous ceux qui souhaitent tenter de faire un mini OS
on exploite pas grand chose (le minimum étant clavier/écran/RAM/mémoire de masse)
Mais ça peut-être une base intéressante pour tous tous ceux qui souhaitent tenter de faire un mini OS
Justement
Enfin, quand j'ai dit un OS ... c'est juste pour le prestige
Développer un OS c'est simple, mais ça nécessite trop de temps!
Pour gagner le temps, je suis entrain de corriger et récompiler le noyau Linux-0.01
pour le faire tourner sur un moderne PC. C'est mieux ... n'est pas ?
Enfin, quand j'ai dit un OS ... c'est juste pour le prestige
Développer un OS c'est simple, mais ça nécessite trop de temps!
Pour gagner le temps, je suis entrain de corriger et récompiler le noyau Linux-0.01
pour le faire tourner sur un moderne PC. C'est mieux ... n'est pas ?
vdxfvgd
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.