lundi 28 mai 2018

Chapitre 31 : création d'une première fenêtre avec X11


Commençons par exécuter à nouveau le programme du chapitre précédent pour entendre le doux son de Windows. Mais hélas, nous recevons notre premier message d’erreur :
PuTTY X11 proxy: unable to connect to forwarded X server: Network error: Connection refused
Serveur X non trouvé.
Soit nous n’avons pas utilisé la connexion créee au chapitre précédent (Par exemple dans mon Putty, j’ai plusieurs connexions) ou le serveur XMing sur le PC doit être relancé. En effet vous avez pu éteindre le PC et oubliez au redémarrage de relancer le serveur XMing. D’ailleurs le mieux c’est de le lancer systématiquement en début de session en plaçant un raccourci dans le menu démarrer.
Cette vérification faite, nous pouvons travailler sur notre deuxième programme où nous allons essayer d’afficher une fenêtre en utilisant la fonction XCreateSimpleWindow.
Pour cela, nous devons effectuer les étapes suivantes :
                Ouvrir une connexion au serveur d’affichage par XOpenDisplay
                Récupérer des informations dans la structure d’affichage (Display)
                Créer la fenêtre par XCreateSimpleWindow en lui passant 9 paramètres.
                Afficher la fenêtre par XMapWindow
                Mettre en place un gestionnaire d’évènements (et oui il faut bien arriver à fermer la fenêtre avec la souris) fonction XNextEvent
                Fermer et détruire la fenêtre par  XDestroyWindow
                Fermer la connexion au serveur par XCloseDisplay.
Etape 1 : Après l’appel de la fonction XOpenDisplay  nous récupérons un pointeur vers la structure Display dont la description en langage C est la suivante (toutes les structures sont décrites dans l’entité xlib.h) :

_XDisplay
{
                XExtData *ext_data;     /* hook for extension to hang data */
                struct _XPrivate *private1;
                int fd;                                   /* Network socket. */
                int private2;
                int proto_major_version;/* major version of server's X protocol */
                int proto_minor_version;/* minor version of servers X protocol */
                char *vendor;                   /* vendor of the server hardware */
        XID private3;
                XID private4;
                XID private5;
                int private6;
                XID (*resource_alloc)(  /* allocator function */
                               struct _XDisplay*
                );
                int byte_order;                                /* screen byte order, LSBFirst, MSBFirst */
                int bitmap_unit;              /* padding and data requirements */
                int bitmap_pad;                              /* padding requirements on bitmaps */
                int bitmap_bit_order;   /* LeastSignificant or MostSignificant */
                int nformats;                     /* number of pixmap formats in list */
                ScreenFormat *pixmap_format;             /* pixmap format list */
                int private8;
                int release;                        /* release of the server */
                struct _XPrivate *private9, *private10;
                int qlen;                              /* Length of input event queue */
                unsigned long last_request_read; /* seq number of last event read */
                unsigned long request;                /* sequence number of last request. */
                XPointer private11;
                XPointer private12;
                XPointer private13;
                XPointer private14;
                unsigned max_request_size; /* maximum number 32 bit words in request*/
                struct _XrmHashBucketRec *db;
                int (*private15)(
                               struct _XDisplay*
                               );
                char *display_name;     /* "host:display" string used on this connect*/
                int default_screen;        /* default screen for operations */
                int nscreens;                     /* number of screens on this server*/
                Screen *screens;            /* pointer to list of screens */
                unsigned long motion_buffer;  /* size of motion buffer */
                unsigned long private16;
                int min_keycode;            /* minimum defined keycode */
                int max_keycode;           /* maximum defined keycode */
                XPointer private17;
                XPointer private18;
                int private19;
                char *xdefaults;              /* contents of defaults from server */
                /* there is more to this structure, but it is private to Xlib */
}

Wauoh !!  complexe non !! Pour l’instant, nous allons nous contenter de repérer les infos intéressantes et de les récupérer par un déplacement à  ajouter au pointeur retourné par xopenDisplay. Par la suite nous utiliserons les descriptions de structures que j’ai traduites en formalisme assembleur (merci qui ?).
Ici nous avons besoin de récupérer un pointeur vers l’écran (screen) de l’affichage. Cette information se trouve au déplacement 140. Ce pointeur pointe sur une structure de type screen dont voici la description en C :

typedef struct {
                XExtData *ext_data;     /* hook for extension to hang data */            0
                struct _XDisplay *display;/* back pointer to display structure */    4
                Window root;                   /* Root window id. */                            8
                int width, height;            /* width and height of screen */                 12  et 16 ?
                int mwidth, mheight;    /* width and height of  in millimeters */    20  et 24
                int ndepths;                      /* number of depths possible */                  28
                Depth *depths;                               /* list of allowable depths on the screen */     32
                int root_depth;                               /* bits per pixel */                             36
                Visual *root_visual;       /* root visual */                            40
                GC default_gc;                 /* GC for the root root visual */                44
                Colormap cmap;                              /* default color map */                          48
                unsigned long white_pixel;                                           52
                unsigned long black_pixel;         /* White and Black pixel values */       56
                int max_maps, min_maps;         /* max and min color maps */
                int backing_store;           /* Never, WhenMapped, Always */
                Bool save_unders;
                long root_input_mask; /* initial root input mask */
} Screen;

Nous trouvons des informations intéressantes comme l’identification de la fenêtre mère (position8), la taille de l’écran(position 12 et 16), le nombre de bits de codage d’un pixel (position 36 les codes RGB du pixel blanc et du pixel noir (position 52 et 56).  Nous mettons ces infos dans les registres r1 r3,r4 et r5 ( affichage du contenu par la macro d’affichage des registres pour vérification).
Maintenant, nous pouvons créer la fenêtre avec la fonction XCreateSimpleWindow dont la signature en C est la suivante :

Window XCreateSimpleWindow(display, parent, x, y, width, height, border_width,
border, background)
Display *display;
Window parent;
int x, y;
unsigned int width, height;
unsigned int border_width;
unsigned long border;
unsigned long background;
display Specifies the connection to the X server.
parent Specifies the parent window.
xy
Specify the x and y coordinates, which are the top-left outside corner of the new
window’s borders and are relative to the inside of the parent window’s borders.
width
height Specify the width and height, which are the created window’s inside dimensions
and do not include the created window’s borders. The dimensions must be
nonzero, or a BadValue error results.
border_width Specifies the width of the created window’s border in pixels.
border Specifies the border pixel value of the window.
background Specifies the background pixel value of the window.

Donc nous mettons dans r0, le pointeur du Display, dans r1 l’identification de la racine récupérée dans la structure écran précédente, zéro dans r2 et r3 pour la position X et Y de la fenêtre et  stop !! nous ne pouvons pas alimenter les autres registres pour passer les paramètres. Il nous faut respecter la normalisation des appels de fonctions standards (voir le chapitre ) ou seuls les 4 premiers registres sont utilisés pour passer ces paramètres, les autres doivent être passés par la pile. Et il nous faut aussi respecter que la pile soit toujours alignée sur une frontière de double mot et donc empiler un nombre de registre pair. Ici, il y a 9 paramètres à passer dont 4 par les registres et il en reste 5 donc il faut faire un push de plus pour avoir un nombre pair. C’est pourquoi nous commençons par mettre zero dans le registre r8 et nous effectuons un push de celui-ci. Puis nous passons les autres paramètres et dans l’ordre inverse demandé dans la signature de la fonction. Nous passons la valeur du pixel blanc comme valeur du pixel du fond d’écran, le pixel noir pour la bordure puis la valeur 5 pour la taille de la bordure (par l’intermèdiaire du registre r8) puis la hauteur et la largeur de la fenêtre. Ouf !!
Après l’appel de la fonction, nous réalignons la pile par l’instruction add sp,#24 pour compenser les 6 push de 4 octets et nous testons le code retour de la fonction. Ceci sera à faire après chaque appel de fonction X11 pour vérifier si tout c’est bien passé et éviter des recherches laborieuses.
Si tout est OK, nous récupérons un pointeur vers une structure de type fenêtre (window), pointeur que nous allons utiliser pour afficher notre fenêtre par XMapWindow.
Puis nous avons une boucle qui va gérer les évènements reçus dans l’affichage. Ici nous nous contentons d’appeler la fonction XNextEvent en lui passant le pointeur du Display et un pointeur vers une zone de mémoire qui stockera l’évènement reçu. Remarque : si cette fonction n’est pas appelée, la fenêtre ne sera pas affichée. Eventuellement elle peut être affichée en appelant la fonction Xflush et sans la boucle.
Comme on ne gère aucun évènement, le programme ne fait rien de plus.
Au lancement du programme, une fenêtre doit apparaitre sur votre PC avec le titre Xming et les menus système habituels. Vous pouvez déplacer la fenêtre, la réduire, la redimensionner comme toute fenêtre. Vous la fermer en cliquant sur la X et vous avez le magnifique message suivant :
XIO:  fatal IO error 11 (Resource temporarily unavailable) on X server "localhost:10.0"
      after 8 requests (6 known processed) with 0 events remaining.
Nous verrons au chapitre suivant comment éliminer cette erreur.
Enfin pour terminer le chapitre, rappelons quelques principes de X11 :
Toute fenêtre est contenue dans une fenêtre mère (ou racine)
Toute fenêtre fille est contenue dans une fenêtre mère ou est tronquée
Une fenêtre parent a toujours un titre.
Les boutons, menus, boites de dialogues  sont tous des fenêtres.
Les positions et tailles des fenêtres sont mesurées en pixels.
Chaque fenêtre à son propre système de coordonnées.
Une autre question se pose : cette fenêtre s’affiche-t-elle  aussi sur l’écran du Raspberry ?  Et bien, il faut brancher l’écran, le clavier , relancer le Raspberry, et à partir de l’écran graphique rechercher dans l’arborescence de fichier le programme et l’éxecuter. Mais on peut aussi voir une image de l’écran avec la solution proposée pour les tests du Framebuffer avec le serveur x11vnc sur le raspberry et le client tightvncViewer sur Windows et ça fonctionne.

Exercice :  modifier la position et la taille de la fenêtre
                Modifier la couleur du fond de la fenêtre.
                La taille de la bordure ne semble pas fonctionner (n’est pas large de 5 pixels dans notre exemple !!), rechercher pourquoi ?
                Dans la structure Display, il y a un pointeur vers une chaine de caractères qui donne le nom du vendeur, afficher cette chaine. Vous pouvez aussi afficher les versions du système X11.

vendredi 25 mai 2018

Chapitre 30 : Utilisation du système graphique X11


Dans les chapitres 13 à 17, nous avons vu comment utiliser le framebuffer pour afficher des dessins sur l’écrans du raspberry. Mais continuer à utiliser cette solution est terriblement couteuse en temps de développement car il faut tout gérer (dessin, police, couleurs etc.)
Pour pouvoir aller plus loin, il va falloir utiliser des librairies graphiques qui vont faire une grand part de notre travail. Mais ici au lieu d’utiliser des libraires comme openvg ou QT  , nous allons nous plonger dans la librairie de X11 car X11 est le système graphique de base de tous les Unix et donc de Linux et donc de raspbian. Si vous avez déjà entendu parler de X11, vous pensez que ce système graphique est très lourd à mettre en place et à programmer car il permet de créer des fenêtres, des boutons, des menus des dessins et il possède des dizaines de fonctions !!. C’est ce que nous allons voir dans les chapitres suivants de ce blog.
Tout d’abord nous allons mettre en place les outils nécessaires au développement avec X11 sur le raspberry.
Donc chargement de la librairie nécessaire :  sudo apt-get install libx11-dev
Puis adaptation du script de compilation pour appeler la librairie et dont voici un exemple :

#compilation assembleur avec librairie X11
#echo $0,$1
echo "Compilation de "$1".s"
as -o $1".o"   $1".s" -a >listingX11.txt
gcc  -o $1 $1".o" ~/vincent/asm/routinesARM.o -e main -lX11 -L/usr/lpp/X11/lib
echo "Fin de compilation."

(remarque : routinesARM.o contient les objets de mes propres routines vues dans les chapitres précédents).
En lisant la documentation sur X11, j’apprends que ce système est à base de client-serveur et qu’il possible d’afficher les écrans sur n’importe quel autre ordinateur disposant d’un serveur X11. Cela me convient car je pourrais travailler de mon ordinateur sous windows, de compiler et lancer les programmes sur le raspberry avec une connexion ssh  par putty et de voir les résultats sur l’écran du PC. Pour cela, j’installe le serveur gratuit XMing disponible à l’adresse https://sourceforge.net/projects/xming/
L’installation se passe sans problème. Si le serveur ne se lance pas, il faut l’activer en lançant le programme Xlaunch.exe se trouvant dans le répertoire d’installation sur votre PC (chosir les options fenêtes multiples puis laisser toutes les autres par défaut).
Maintenant il faut créer une connexion ssh par putty spéciale en configurant l’option enable X11 forwarding se trouvant dans le menu connexion sous-menu SSH :


Bien entendu, il faut renseigner l’adresse IP de votre raspberry et le port et sauvegarder la configuration sous un nom adéquat ( par exemple raspX11). Maintenant il ne vous reste plus qu’à ouvrir une session ssh avec cette configuration de saisir l’identifiant de connexion et le mot de passe.
Pour vérifier si cela fonctionne, vous lancer l’application graphique fournie en standard sur le raspberry : leafpad et normalement vous devez voir apparaitre sur votre ordinateur Window un petit éditeur de texte. Super non ?
Si vous aller dans le menu ouvrir, vous voyez que l’arborescence qui s’affiche est bien celle de votre raspberry et pas celle de votre ordinateur windows, preuve que l’application leafpad s’exécute sur le raspberry et que c’est seulement l’affichage (et le clavier et la souris) qui est géré par le serveur XMing sur votre PC.
Il faut aussi rechercher la documentation sur Internet concernant X11 (et il en a beaucoup !!). Un excellent document en anglais écrit pour le langage C par Ross Maloney nous servira de trame pour partir à la découverte des nombreuses fonctions des libraires X11. A télécharger sur le site : http://yenolam.com/writings/xlibbook-0.5.pdf
Et une référence sur les fonctions X11 : https://www.x.org/docs/X11/xlib.pdf
https://fr.wikipedia.org/wiki/X_Window_System
Toutes les fonctions sont aussi décrites sur le site en anglais de Christophe Tronche : https://tronche.com/gui/x/xlib/
Et pour tester tout cela voici un premier petit programme qui va faire appel à la fonction XOpenDisplay pour établir une connexion avec le serveur d’affichage. Si la connexion est ok, nous récupérons un pointeur vers une structure appelée Display (Affichage) et qui contient les informations sur la connexion (plus loin dans le programme, nous afficherons le contenu de cette structure pour voir ce qu’elle contient). Ensuite nous nous contentons d’appel la fonction XBell en lui passant dans le registre r0 le pointeur du Display et dans r1 la valeur 100 (volume maximum). Cette fonction fait biper le haut-parleur de votre PC grâce au serveur XMing.  Puis nous fermons le Display par la fonction XCloseDisplay pour libérer les ressources utilisées et nous terminons le programme.
Vous remarquerez que les noms de fonctions commencent tous par X et que nous passons le pointeur du Display dans le registre r0 et tous les programmes commenceront par cet appel de connexion. Ce premier programme doit fonctionner sans problème !! Ah oui, n’oubliez pas d’activer votre haut-parleur sur votre PC pour entendre le son.

lundi 21 mai 2018

Chapitre 29 : chaines de caractères


Dans cet article, nous allons voir plusieurs routines très utiles pour traiter les chaines de caractères (string). Une chaine de caractères est toujours stockée en mémoire et est accessible par son adresse de début contenu dans un pointeur ou un registre. Il existe plusieurs manières de  gérer une chaine soit par exemple comme le C en terminant celle çi par un zéro binaire soit en mettant dans le premier octet (ou le demi mot ou le mot) sa longueur.
Ici nous utiliserons la définition comme le C cad avec un zéro final. Nous avons vu qu’une chaine se définissait en mémoire par la directive .asciz (ou aussi .string)qui mettra le zéro binaire automatiquement à la fin de la chaine. Pour des suites de caractères sans 0 final, il faut utiliser la directive .ascii.
Ces routines peuvent être utiliser tel quel mais elles serviront surtout de base pour écrire des fonctions plus intégrables ou répondant à d’autres besoins spécifiques. Par exemple le calcul de la longueur peut être fait directement dans le code soit avoir à appeler la routine ( pour alléger le nombre d’instructions et réduire le temps d’exécution).
La première fonction permet de vérifier et de corriger une chaine de caractère si celle ci n’est pas correcte. Elle force un zéro binaire à la rencontre du premier caractère non alphanumérique au sens strict. En effet, sur mon système les programmes sont encodés avec l’option uft8 et donc les caractères accentués ne sont pas considérés comme des caractères ascii mais sont codées sur 2 caractères (ou plus pour des alphabets exotiques). Donc suivant les cas, il faudra adapter cette fonction pour gérer vos besoins. Elle peut être utile pour la lecture des lignes d’un fichier car chaque ligne ne se termine pas par un 0 binaire mais par les caractères 0x0D0A.
La deuxième fonction permet de calculer la longueur d’une chaine. Il s’agit d’une boucle simple qui compte le nombre de caractères jusqu’à trouver le zéro final.
Ensuite nous trouvons les fonctions de copie d’une chaine entière et de copie de n caractères d’une chaine
Puis 2 fonctions de concaténation de chaines,  la première oblige la fourniture par le code appelant de la zone de réception . La seconde utilise le tas (heap) Linux pour réserver la zone de réception et retourne son adresse au code appelant.
De même 2 fonctions de comparaison de chaine, la première qui tient compte de la casse (majucules-minuscules) et l’autre qui n’en tient pas compte.
Puis 2 fonctions de recherche, une pour chercher un seul caractère et l’autre pour chercher une sous-chaine à l’intérieur d’une chaine. Ces fonctions s’arrêtent au premier caractère ou sous chaine trouvé et sont donc à adapter pour rechercher d’autres occurrences.
Enfin une fonction d’insertion d’une sous chaine dans une autre puis une fonction de tri. Pour cela toutes les chaines qui ont servies dans les tests précédents sont identifiées par un pointeur stocké dans une table. Cette table permet l’affichage des chaines puis leur tri et enfin un affichage après le tri pour vérification. Dans notre exemple nous appelons la fonction de comparaisonsanscasse mais vous pouvez la remplacer par l’autre fonction de comparaison. Le tri utilisé est un tri shell dans lequel seuls les pointeurs des chaines sont déplacés. Ainsi les chaines quelle que soit leur longueur ne sont pas déplacées ce qui permet au tri d’être efficace. Attention, ce tri utilise une gestion des incréments simplifiée ce qui peut entrainer pour des grosses quantités de chaine une dégénérescence du tri (pour plus de détails, voir la théorie et les multiples analyses du tri shell).
Pour terminer, une fonction permet d’éclater une chaine en plusieurs sous chaines en fonction d’un séparateur. La fonction retourne l’adresse d’une table qui contient dans le premier poste, le nombre de zones extraites puis ensuite dans chaque poste l’adresse de la sous-chaine. Dans ce programme, nous découpons la chaine avec un séparateur espace. Nous affichons le résultat dans une boucle qui balaye la table fournie en retour de la fonction.
La table des zones est aussi stockée sur le tas grâce à l’appel system linux Brk.

Exercice :   écrire une fonction qui inverse une chaine
                   améliorer la recherche d'un caractère pour trouver le 2ième, 3iéme etc.
 

lundi 7 mai 2018

Chapitre 28 : retour sur les algorithmes de division.


Le processeur arm de mon raspberry n’admet pas la division entière comme instruction de base et il faut donc la programmer. Le chapitre 15 du site Thinckingeek propose plusieurs algorithmes et il s’en trouve d’autres sur Internet et dans les livres sur les algorithmes. Dans ce chapitre je vais m’y intéresser en mesurant le temps nécessaire pour effectuer des milliers de divisions avec chacun d’entre eux.
Dans ce programme, nous décrivons dans la .data, le message nécessaire à l’affichage du temps et dans le .bss 2 tables de 10000 postes de 4 octets : la première contiendra le dividende sous forme de nombres aléatoires et la deuxième le diviseur aussi sous forme de nombres aléatoires. Lors de la création de cette dernière nous nous arrangerons pour que le diviseur soit toujours dans une tranche de nombres mille fois inférieur au dividende. Ces tables permettront d’effectuer des divisions de nombres aléatoires identiques pour tous les algorithmes testés.
Dans le code, nous commençons par générer les 10000 dividendes et les 10000 diviseurs aléatoires que nous stockons dans les 2 tables. Ensuite nous créons 2 sous routines pour enregistrer l’heure de départ d’un test (debutChrono) et l’heure de fin du test (stopChrono) pour calculer par différence le temps écoulé. Pour cela nous nous servons de l’appel system Linux gettimeofday (code 0x4E) pour enregistrer l’heure. La sous routine stopChrono affichera le temps en secondes et microsecondes.
Ensuite nous créons toutes les sous routines de division que nous voulons tester : celles du chapitre 15, une qui fait appel à la division en virgule flottante double précision du processeur, une tirée d’un livre sur l’assembleur ARM et 2 programmée par moi et issus de différents idées d’algorithmes trouvées sur Internet. Chaque routine commence par un test du diviseur pour vérifier qu’il soit différent de zéro. Dans ce cas, la routine retourne la valeur -1 dans le registre r0 car il est peu probable en division non signée d’avoir ce résultat. Mais il serait peut être préferable de positionner le carry à 1 dans ce cas (et penser à le mettre à 0 si la division est OK) ce qui coute quelques instructions de plus.
Pour chacune d’entres elles, nous effectuons les mêmes divisions de nombres aléatoires et nous affichons le temps mis.
Le premier algorithme est identique à la première division du chapitre 15 de http://thinkingeek.com/arm-assembler-raspberry-pi/. Après plusieurs séries de tests, il s’avère le plus lent de tous avec en moyenne 4 ms pour 10000 divisions.
Le second fait appel aux instructions en virgule flottante puisque la division est disponible !! Celle solution s’avère efficace puisque l’on tombe à 2ms. Remarque : j’avais fait des tests sur de grands dividendes et j’avais trouvé des écarts sur des résultats que je n’arrivais pas à expliquer. Et enfin j’ai trouvé une erreur dans l’instruction vcvt.f64.u32 d1, s1  car j’avais mis vcvt.f64.s32 d1, s1 car au début je pensais que le s de S32 voulait dire single pour simple précision alors qu’il veut dire signed. Donc il faut faire très attention lors du codage de ces instructions  et sans cesse tester et vérifier !!!
Le troisième reprend tel quel l’algorithme better_unsigned_division de thinkingeek Le résultat est le meilleur
 de tous car on descend à 1,4 ms. Reste à comprendre comment il fonctionne !!!
Le suivant donne aussi de bons résultats avec 1,8ms et les 2 autres sont moins efficaces mais restent dans la moyenne. 
Ces 2 derniers essaient de diminuer le nombre de boucles internes en évitant les zéros inutiles en début des nombres
 grâce à l’instruction clz. Dans ces programmes j’utilise l’instruction rsb bien utile . En effet avant pour enlever
 la valeur d’un registre d’une constante, je mettais la constante dans un premier registre puis je l’enlevais 
par l’instruction sub. Mais c’est plus simple avec rsb r1,r1,#32 soit r1 =  32 –r1.
 
Nous voyons donc ici différents manières de programmer la même routine et les incidences sur le temps d’exécution. 
Ces algorithmes sont à étudier de très près car ils montrent des utilisations diverses des instructions assembleurs.
Exercice :  essayer d’améliorer ces routines (piste : diminuer le nombre de boucles, ou diminuer le nombre
 d’instructions des boucles) 
                Vérifier que ces algorithmes sont exacts pour une large valeur de dividende et diviseur en stockant les résultats de chacun puis en les comparant pour détecter un éventuel écart.
                Adapter le programme pour effectuer des divisions signées.