4MMPS Ecran

De Ensiwiki
Aller à : navigation, rechercher
AttentionCette page est maintenue uniquement par les enseignants. Afin de ne pas perturber le déroulement des cours, elle n'a pas vocation à être modifiée par les élèves. Mais si vous avez des modifications à proposer, merci d'en discuter ou d'envoyer un e-mail aux auteurs de la page (cf. historique)

Mycomputer.png  Deuxième Année  CDROM.png  Informatique 

Fleche gauche.png
Fleche haut.png
Fleche droite.png

Le but de cette séance est de programmer la gestion de l'écran dans un noyau de système d'exploitation. On va se limiter à un mode d'affichage très simple géré par toutes les cartes graphiques depuis le début des années 80 (norme CGA).

Spécification de l'affichage à l'écran

L'écran que nous considérons est le mode texte de base des cartes vidéo des PC dans lequel le noyau démarre. L'affichage fait 80 colonnes sur 25 lignes (les lignes et les colonnes sont en pratique numérotées à partir de 0). L'affichage s'effectue en écrivant directement dans la mémoire vidéo pour y placer les caractères et leur couleur. Certaines opérations simples d'entrées-sorties sont nécessaires pour déplacer le curseur clignotant qui indique la position actuelle d'affichage.

Principe

L'écran est couplé à une zone de la mémoire vidéo commençant à une adresse dépendant du mode utilisé (ici l'adresse de début est 0xB8000) : tout ce qui est écrit dans cette zone mémoire est donc immédiatement affiché à l'écran. Dans le mode vidéo utilisé, l'écran peut être vu comme un tableau de 80x25 = 2000 cases. Chaque case représente un caractère affiché à l'écran, et est composée de 2 octets (uint8_t en C) :

  • le premier octet contient le code ASCII du caractère ;
  • le deuxième octet contient le format du caractère, c'est à dire la couleur du texte, la couleur du fond et un bit indiquant si le texte doit clignoter :
Structure de l'octet de format
bit 7 6 5 4 3 2 1 0
clignote couleur du fond couleur du texte

Attention : le clignotement n'est pas géré correctement par l'environnement d'exécution, vous pouvez forcer le bit 7 toujours à 0.

Les couleurs disponibles sont listées dans le tableau ci-dessous :

Les 16 couleurs de la palette CGA
valeur couleur valeur couleur valeur couleur valeur couleur
0 noir
4 rouge
8 gris foncé
12 rouge clair
1 bleu
5 magenta
9 bleu clair
13 magenta clair
2 vert
6 marron
10 vert clair
14 jaune
3 cyan
7 gris
11 cyan clair
15 blanc

Les 16 couleurs sont possibles pour le texte, par contre seules les 8 premières peuvent être sélectionnées pour le fond.

Pour afficher un caractère à la ligne lig et à la colonne col de l'écran, on doit donc écrire dans le mot de 2 octets (uint16_t en C) dont l'adresse en mémoire peut être calculé à partir de la formule suivante : 0xB8000 + 2 * (lig * 80 + col).

Une fois cette adresse calculée, on peut écrire le caractère c (de couleur ct, sur un fond cf et avec un bit de clignotement cl) en utilisant les opérateurs sur les bits du langage C : le premier octet est égal à la valeur du caractère c et le 2ème octet est calculé comme suit : (cl << 7) | (cf << 4) | (ct).

Gestion du curseur

Lorsqu'on écrit dans un terminal en mode texte, on voit s'afficher un curseur clignotant qui indique la prochaine case dans laquelle on va écrire. Dans le mode vidéo que l'on utilise, ce curseur est géré directement par la carte vidéo : il suffit de lui indiquer à quelles coordonnées elle doit l'afficher.

On communique pour cela via des ports d'entrée-sorties : il s'agit de canaux de communication reliant les périphériques et dont les adresses sont fixées. Il existe deux types de ports :

  • les ports de commandes qui servent à indiquer au périphérique l'opération que l'on souhaite effectuer ;
  • les ports de données qui permettent de communiquer effectivement avec le périphérique, en lisant ou en envoyant des données.

Dans l'architecture x86, les ports sont couplés à une plage d'adresses de 64 KiO : l'adresse d'un port est donc une valeur sur 16 bits (uint16_t en C). Cependant, on ne peut pas accéder à cette zone mémoire directement via des pointeurs : on doit utiliser des instructions particulières.

Il existe des instructions assembleur dédiées pour la communication via les ports : sur l'architecture x86, il s'agit de l'instruction in (pour lire une donnée en provenance d'un port et la stocker dans un registre du processeur) et de l'instruction out (pour envoyer une donnée à un port). Pour faciliter l'utilisation de ces instructions, on fournit des fonctions C réalisant les mêmes opérations :

  • uint8_t inb(uint16_t port) renvoie l'octet lu sur le port dont l'adresse est passée en paramètre
  • void outb(uint8_t value, uint16_t port) envoie l'octet value sur le port d'adresse port
  • ainsi que l'équivalent de ces fonctions pour des valeurs sur 16 bits (inw, outw) et sur 32 bits (inl, outl)).

Dans les cartes vidéos VGA que l'on utilise dans ce TP, le port de commande gérant la position du curseur est le 0x3D4 et le port de données associé est le 0x3D5. La position du curseur est un entier sur 16 bits calculé via la formule suivante : pos = col + lig * 80. Cette position doit être envoyée en deux temps à la carte vidéo : on envoie d'abord l'octet de poids faible puis l'octet de poids fort de la position. La succession d'opérations à effecter est donc la suivante :

  1. envoyer la commande 0x0F sur le port de commande pour indiquer à la carte que l'on va envoyer la partie basse de la position du curseur
  2. envoyer cette partie basse sur le port de données
  3. envoyer la commande 0x0E sur le port de commande pour signaler qu'on envoie maintenant la partie haute
  4. envoyer la partie haute de la position sur le port de données.

Les caractères à afficher

On considère dans ce TP les caractères de la table ASCII (man ascii), qui sont numérotés de 0 à 127 inclus. Les caractères dont le code est supérieur à 127 (accents, etc.) seront ignorés.

Les caractères de code ASCII 32 à 126 doivent être affichés en les plaçant à la position actuelle du curseur clignotant et en déplaçant ce curseur sur la position suivante : à droite, ou au début de la ligne suivante si le curseur était sur la dernière colonne.

Les caractères de 0 à 31, ainsi que le caractère 127 sont des caractères de contrôle. Le tableau ci-dessous décrit ceux devant être gérés. Tous les autres caractères de contrôle doivent être ignorés.

Caractères de contrôle à gérer
Code de contrôle Mnémonique Syntaxe en C Effet
8 BS \b Recule le curseur d'une colonne s'il n'est pas sur la première colonne
9 HT \t Avance à la prochaine tabulation (colonnes 0, 8, 16, ..., 64, 72, 79)
10 LF \n Déplace le curseur sur la ligne suivante, colonne 0
12 FF \f Efface l'écran et place le curseur sur la colonne 0 de la ligne 0
13 CR \r Déplace le curseur sur la ligne actuelle, colonne 0

Travail demandé

Le but final est d'écrire une fonction void console_putbytes(char *chaine, int32_t taille) qui affiche une chaine de caractères à la position courante du curseur. Attention, vous devez respecter le nom et la spécification de cette fonction car elle est appelée par d'autres fonctions du noyau, par exemple printf.

Pour cela, on recommande d'implanter progressivement la spécification en la découpant en fonctions.

Par exemple, vous pouvez implanter dans cet ordre :

  1. une fonction uint16_t *ptr_mem(uint32_t lig, uint32_t col) qui renvoie un pointeur sur la case mémoire correspondant aux coordonnées fournies (cette fonction peut aussi s'écrire efficacement sous la forme d'une macro)
  2. une fonction void ecrit_car(uint32_t lig, uint32_t col, char c) qui écrit le caractère c aux coordonnées spécifiées (vous pouvez aussi ajouter des paramètres pour permettre de préciser la couleur du caractère, celle du fond ou le bit de clignotement)
  3. une fonction void place_curseur(uint32_t lig, uint32_t col) qui place le curseur à la position donnée
  4. une fonction void efface_ecran(void) dont on vous laisse deviner le but
  5. une fonction void traite_car(char c) qui traite un caractère donné (c'est à dire qui l'affiche si c'est un caractère normal ou qui implante l'effet voulu si c'est un caractère de contrôle)
  6. une fonction void defilement(void)qui fait remonter d'une ligne l'affichage à l'écran (il pourra être judicieux d'utiliser memmove définie dans string.h pour cela)
  7. la fonction console_putbytes demandée, qui va sûrement utiliser les fonctions précédentes

Afin de vérifier le bon fonctionnement de vos différentes fonctions, le plus simple est de faire un affichage avec printf (définie dans stdio.h), car printf utilise console_putbytes pour l'affichage à l'écran.

Le module de gestion de l'écran garde en interne la position courante du curseur, ainsi que les différents attributs (couleur du texte, du fond, clignotement), dans des variables globales.

Le bout de bibliothèque C fourni comprend de nombreuses fonctions utiles : il faut s'en servir pour ne pas ré-inventer (et perdre du temps à mettre au point) du code redondant ! Vous trouverez la documentation des fonctions C dans les pages man habituelles : par exemple, man memmove ou man sprintf.

Pour être plus clair, on utilise dans cet énoncé les types exacts du C99 définis dans stdint.h, dont on rappelle la correspondance avec les types standards (sur une architecture 32 bits) dans le tableau ci-dessous :

Types exacts du C99
Type C99 Taille en octets Type C ANSI (sur x86)
int64_t 8 long long
uint64_t 8 unsigned long long
int32_t 4 int
uint32_t 4 unsigned
int16_t 2 short
uint16_t 2 unsigned short
int8_t 1 char
uint8_t 1 unsigned char

Les caractères seront désignés par le type standard char et on rappelle que sur x86, tous les pointeurs sont sur 32 bits (quelque-soit la taille de la donnée pointée).

Petits rappels de C

Au cas où vous auriez oublié votre C « bas-niveau », on rappelle quelques opérations binaires :

  • x décalé de n bits vers la gauche : x << n ;
  • x décalé de n bits vers la droite : x >> n (attention si le type de x est signé, il s’agit d’un décalage arithmétique avec propagation du bit de signe) ;
  • forcer les 3 bits de poids faibles de x à 0 : x &= 0xF8 ;
  • forcer le bit de poids fort de x à 1 : x |= 0x80 ;
  • calculer le not binaire de x : ~x (bien lire « tilda x »).