168 | | 1. À quoi sert la directive `.globl label` ? |
169 | | {{{#!protected ------------------------------------------------------------------------------------ |
170 | | '' |
171 | | Cours 9 / slide 18\\\\ |
172 | | Ce qu'il faut comprendre, c'est que les comportements du `C` et de l'assembleur sont inversés vis-à-vis des labels. Dans un fichier `.c`, quand on définit un label (une fonction ou variable), ce label est par défaut `extern`, c'est-à-dire qu'il est utilisable dans un autre fichier `.c`. Si on veut que le label ne soit utilisable que dans le fichier dans lequel il est défini, il faut utiliser le mot clé `static` lors de sa déclaration. En assembleur, c'est l'inverse, les labels sont par défaut `static`, c'est-à-dire utilisable uniquement dans le fichier où ils sont définis. Si on veut qu'ils soient utilisables dans les autres fichiers, il faut le dire avec la directive `.globl`. |
| 167 | 1. À quoi sert la directive `.globl label` ? (C9 S18) |
| 168 | {{{#!protected ------------------------------------------------------------------------------------ |
| 169 | '' |
| 170 | * Ce qu'il faut comprendre, c'est que les comportements du `C` et de l'assembleur sont inversés vis-à-vis des labels. Dans un fichier `.c`, quand on définit un label (une fonction ou variable), ce label est par défaut `extern`, c'est-à-dire qu'il est utilisable dans un autre fichier `.c`. Si on veut que le label ne soit utilisable que dans le fichier dans lequel il est défini, il faut utiliser le mot clé `static` lors de sa déclaration. En assembleur, c'est l'inverse, les labels sont par défaut `static`, c'est-à-dire utilisable uniquement dans le fichier où ils sont définis. Si on veut qu'ils soient utilisables dans les autres fichiers, il faut le dire avec la directive `.globl`. |
211 | | = 3. Programmation en C |
212 | | |
213 | | |
214 | | |
215 | | |
216 | | Vous savez déjà programmer en C, mais vous allez voir des syntaxes ou des cas d'usage que vous ne connaissez peut-être pas encore. |
217 | | Les questions qui sont posées ici n'ont pas toutes été vues en cours, mais vous connaissez peut-être les réponses, sinon ce sera l'occasion d'apprendre. |
218 | | |
219 | | **Questions |
220 | | |
221 | | 1. Quels sont les usages du mot clé `static` en C ? (c'est une directive que l'on donne au compilateur C) |
222 | | {{{#!protected ------------------------------------------------------------------------------------ |
223 | | '' |
224 | | Le cours 9 n'en parle pas, mais dans le code vous trouverez cette directive un peu partout. Il y a deux usages, on a déjà parlé du premier, mais pas encore du second. |
225 | | 1. Déclarer `static` une variable globale ou une fonction en faisant précéder leur définition du mot clé `static` permet de limiter la visibilité de cette variable ou de cette fonction au seul fichier de déclaration. Notez que par défaut les variables et les fonctions du C ne sont pas `static`, il faut le demander explicitement. C'est exactement l'inverse en assembleur où tout label est implicitement `static` ; il faut demander avec la directive `.globl` de le rendre visible. |
226 | | 1. Déclarer `static` une variable locale permet de la rendre persistante, c'est-à-dire qu'elle conserve sa valeur entre deux appels. Cette variable locale n'est pas dans le contexte de la fonction (c'est-à-dire qu'elle n'est pas dans la pile parce que le contexte est libéré en sortie de fonction). Une variable locale `static` est en fait allouée comme une variable globale mais son usage est limité à la seule fonction où elle est définie. |
227 | | '' |
228 | | }}} |
229 | | 1. Pourquoi déclarer des fonctions ou des variables `extern` ? |
230 | | {{{#!protected ------------------------------------------------------------------------------------ |
231 | | '' |
232 | | Ça n'ont plus ce n'est pas dit dans le cours mais c'est sensé être connu, sinon c'est qu'il y a des choses à apprendre. Notez que la directive externe est implicite en C et qu'on peut donc ne pas l'écrire. On la met pour la lisibilité du code. |
233 | | * Les déclarations `extern` permettent d'informer que le compilateur qu'une variable ou qu'une fonction existe et est définie ailleurs. Le compilateur connaît ainsi le type de la variable ou du prototype des fonctions, il sait donc comment les utiliser. En C, par défaut, les variables et les fonctions doivent être déclarées / leur existence et type doit être connus avant leur utilisation. |
234 | | * Il n'y a pas de déclaration `extern` en assembleur parce que ce n'est pas un langage typé. Pour l'assembleur, un label c'est juste une adresse donc un nombre. |
235 | | '' |
236 | | }}} |
237 | | 1. Comment déclarer un tableau de structures en variable globale ? La structure est nommée `test_s`, elle a deux champs `int` nommés `a` et `b`. Le tableau est nommé `tab` et a 2 cases. |
238 | | {{{#!protected ------------------------------------------------------------------------------------ |
239 | | '' |
240 | | Là encore, ce sont des connaissances censées être connues, mais c'est important parce qu'on a besoin de le comprendre pour la déclaration des registres du TTY. |
241 | | {{{#!c |
242 | | struct test_s { |
243 | | int a; |
244 | | int b; |
245 | | }; |
246 | | struct test_s tab[2]; |
247 | | }}} |
248 | | '' |
249 | | }}} |
250 | | 1. Supposons que la structure `tty_s` et le tableau de registres de `TTY` soient définis comme suit. Écrivez une fonction C `int getchar(void)` bloquante qui attend un caractère tapé au clavier sur le `TTY0`. Nous vous rappelons qu'il faut attendre que le registre `TTY_STATUS` soit différent de 0 avant de lire `TTY_READ`. `NTTYS` est un `#define` définit dans le Makefile de compilation avec le nombre de terminaux du SoC (en utilisant l'option `-D` de gcc). |
251 | | {{{#!c |
252 | | struct tty_s { |
253 | | int write; // tty's output |
254 | | int status; // tty's status something to read if not null) |
255 | | int read; // tty's input |
256 | | int unused; // unused |
257 | | }; |
258 | | extern volatile struct tty_s __tty_regs_map[NTTYS]; |
259 | | }}} |
260 | | {{{#!protected ------------------------------------------------------------------------------------ |
261 | | '' |
262 | | Cours 9 / slide 10\\\\ |
263 | | En principe, cela ne devrait pas poser de difficulté, mais cela nécessite d'avoir compris comment fonctionne le TTY et de savoir écrire une fonction en C. Pour aider, au cas où, on peut donner la description de ce qui est attendu:\\Tant que le registre `status` est à 0 alors attendre, puis lire le registre `read`.\\Notez que cela nécessite de savoir accéder aux champs d'une structure. |
264 | | {{{#!c |
265 | | int getchar(void) |
266 | | { |
267 | | while (__tty_regs_map[0].status == 0); |
268 | | return __tty_regs_map[0].read; |
269 | | } |
270 | | }}} |
271 | | '' |
272 | | }}} |
273 | | 1. Savez-vous à quoi sert le mot clé `volatile` ? Nous n'en avons pas parlé en cours, mais c'est nécessaire pour les adresses des registres de périphérique, une idée ... ? |
274 | | {{{#!protected ------------------------------------------------------------------------------------ |
275 | | '' |
276 | | Ce n'est pas dit dans le cours, mais c'est un concept important. Quand le programme doit aller chercher une donnée dans la mémoire puis faire plusieurs calculs dessus, le compilateur optimise en réservant un registre du processeur pour cette variable afin de ne pas être obligé d'aller lire la mémoire à chaque fois. Mais, il y a des cas où ce comportement n'est pas souhaitable (il est même interdit). C'est le cas pour les données qui se trouvent dans les registres de périphériques. Ces données peuvent être changées par le périphérique sans que le processeur le sache, de sorte qu'une valeur lue par le processeur à l'instant `t` n'est plus la même (dans le registre du périphérique) à l'instant `t+1`. Le compilateur ne doit pas optimiser, il doit aller chercher la donnée en mémoire à chaque fois que le programme le demande. |
277 | | * `volatile` permet de dire à `gcc` que la variable en mémoire peut changer à tout moment, elle est volatile. Ainsi quand le programme demande de lire une variable `volatile` le compilateur doit toujours aller la lire en mémoire. Il ne doit jamais chercher à optimiser en utilisant un registre afin de réduire le nombre de lecture mémoire (load). De même, quand le programme écrit dans une variable `volatile`, cela doit toujours provoquer une écriture dans la mémoire (store). |
278 | | * Ainsi, les registres de périphériques doivent toujours être impérativement lus ou écrits à chaque fois que le programme le demande, parce que c'est justement ces lectures et ces écritures qui commandent le périphérique. |
279 | | '' |
280 | | }}} |
281 | | |
282 | | |
283 | | |
284 | | |
285 | | = 4. Compilation |
| 209 | = 3. Compilation |
404 | | 4. Au début du fichier se trouve la déclaration des variables du Makefile, quelle est la différence entre `=`, `?=` et `+=` ? |
405 | | {{{#!protected ------------------------------------------------------------------------------------ |
406 | | '' |
407 | | Ce n'est pas expliqué dans le cours, mais c'est utilisé dans les `Makefile`s. La syntaxe des `Makefile`s peut-être très complexe (c'est un vieux langage), ici nous ne verrons qu'une partie. La question sur la déclaration des variables optionnelles est intéressante si on fait le parallèle avec ce qu'il faut faire pour avoir le même comportement avec le préprocesseur et ses `#ifndef` (on a une autre méthode encore pour le `shell`.\\A notez que le Makefile `voit` les variables du `shell` comme s'il les avait définies lui même. |
| 329 | 4. Où est utilisé `CFLAGS` ? Que fait `-DNTTYS=$(NTTY)` et pourquoi est-ce utile ici ? (C9 annexe S8) |
| 330 | {{{#!protected ------------------------------------------------------------------------------------ |
| 331 | '' |
| 332 | * Le compilateur C peut avoir beaucoup de paramètres. Définir une variable `CFLAGS` permet de les déclarer une fois au début et d'utiliser cette variable plusieurs fois dans le `Makefile`. Si on veut changer un argument, il suffit de le faire une seule fois. Ce genre de choses est nécessaire si on veut faire des `Makefile`s facile à lire et à faire évoluer. |
| 333 | * La variable `CFLAGS` est utilisée par `gcc`, il y a ici toutes les options indispensables pour compiler mais il en existe beaucoup, ce qui fait des tonnes de combinaison d'options ! |
| 334 | * `-DNTTYS=$(NTTY)` permet de définir et donner une valeur à une macro (ici définition `NTTYS` avec la valeur `$(NNTY)` comme le fait un `#define` dans un fichier C. Cette commande éviter donc d'ouvrir les codes pour les changer. |
| 335 | '' |
| 336 | }}} |
| 337 | 1. Si on exécute `make` sans cible, que se passe-t-il ? (C9 annexe S6) |
| 338 | {{{#!protected ------------------------------------------------------------------------------------ |
| 339 | '' |
| 340 | * Mettre une règle `help` comme règle par défaut permet de documenter l'usage du `Makefile`, ce qui est plutôt une bonne pratique quand il y a beaucoup de cible et de paramètres. C'est d'autant plus vrai qu'on utilise les Makefiles comme des ensembles de `shell script`. |
| 341 | * C'est la première cible qui est choisie, donc ici c'est équivalent à `make help`. Cela affiche l'usage pour connaître les cibles disponibles. |
| 342 | '' |
| 343 | }}} |
| 344 | Réponses non présentes dans les slides, mais utiles à savoir. |
| 345 | 6. à quoi servent `@` et `-` au début de certaines commandes ? |
| 346 | {{{#!protected ------------------------------------------------------------------------------------ |
| 347 | '' |
| 348 | * `@` permet de ne pas afficher la commande avant son exécution. On peut rendre ce comportement systématique en ajoutant la règle `.SILENT:` n'importe où dans le fichier. |
| 349 | * `-` permet de ne pas stopper l'exécution des commandes même si elles rendent une erreur, c'est-à-dire une valeur de sortie différente de 0. |
| 350 | '' |
| 351 | }}} |
| 352 | 7. Au début du fichier se trouve la déclaration des variables du Makefile, quelle est la différence entre `=`, `?=` et `+=` ? |
| 353 | {{{#!protected ------------------------------------------------------------------------------------ |
| 354 | '' |
413 | | 1. Où est utilisé `CFLAGS` ? Que fait `-DNTTYS=$(NTTY)` et pourquoi est-ce utile ici ? |
414 | | {{{#!protected ------------------------------------------------------------------------------------ |
415 | | '' |
416 | | Cours 9 / Slide 8 annexe\\\\ |
417 | | Le compilateur C peut avoir beaucoup de paramètres. Définir une variable `CFLAGS` permet de les déclarer une fois au début et d'utiliser cette variable plusieurs fois dans le `Makefile`. Si on veut changer un argument, il suffit de le faire une seule fois. Ce genre de choses est nécessaire si on veut faire des `Makefile`s facile à lire et à faire évoluer. |
418 | | * La variable `CFLAGS` est utilisée par `gcc`, il y a ici toutes les options indispensables pour compiler mais il en existe beaucoup, ce qui fait des tonnes de combinaison d'options ! |
419 | | * `-DNTTYS=$(NTTY)` permet de définir et donner une valeur à une macro (ici définition `NTTYS` avec la valeur `$(NNTY)` comme le fait un `#define` dans un fichier C. Cette commande éviter donc d'ouvrir les codes pour les changer. |
420 | | '' |
421 | | }}} |
422 | | 1. Si on exécute `make` sans cible, que se passe-t-il ? |
423 | | {{{#!protected ------------------------------------------------------------------------------------ |
424 | | '' |
425 | | Cours 9 / slides 6 annexe\\\\ |
426 | | Mettre une règle `help` comme règle par défaut permet de documenter l'usage du `Makefile`, ce qui est plutôt une bonne pratique quand il y a beaucoup de cible et de paramètres. C'est d'autant plus vrai qu'on utilise les Makefiles comme des ensembles de `shell script`. |
427 | | * C'est la première cible qui est choisie, donc ici c'est équivalent à `make help`. Cela affiche l'usage pour connaître les cibles disponibles. |
428 | | '' |
429 | | }}} |
430 | | 1. à quoi servent `@` et `-` au début de certaines commandes ? |
431 | | {{{#!protected ------------------------------------------------------------------------------------ |
432 | | '' |
433 | | Ce n'est pas dit en cours, mais comme c'est utilisé, il n'est pas inutile de le savoir. |
434 | | * `@` permet de ne pas afficher la commande avant son exécution. On peut rendre ce comportement systématique en ajoutant la règle `.SILENT:` n'importe où dans le fichier. |
435 | | * `-` permet de ne pas stopper l'exécution des commandes même si elles rendent une erreur, c'est-à-dire une valeur de sortie différente de 0. |
436 | | '' |
437 | | }}} |
| 360 | |
| 361 | |
| 362 | |
| 363 | |
| 364 | |
| 365 | = 4. Programmation en C |
| 366 | |
| 367 | |
| 368 | |
| 369 | |
| 370 | Vous savez déjà programmer en C, mais vous allez voir des syntaxes ou des cas d'usage que vous ne connaissez peut-être pas encore. |
| 371 | Les questions qui sont posées ici n'ont pas toutes été vues en cours, mais vous connaissez peut-être les réponses, sinon ce sera l'occasion d'apprendre. |
| 372 | |
| 373 | **Questions |
| 374 | |
| 375 | 1. Quels sont les usages du mot clé `static` en C ? (c'est une directive que l'on donne au compilateur C) |
| 376 | {{{#!protected ------------------------------------------------------------------------------------ |
| 377 | '' |
| 378 | * Le cours 9 n'en parle pas, mais dans le code vous trouverez cette directive un peu partout. Il y a deux usages. |
| 379 | 1. Déclarer `static` une variable globale ou une fonction en faisant précéder leur définition du mot clé `static` permet de limiter la visibilité de cette variable ou de cette fonction au seul fichier de déclaration. Notez que par défaut les variables et les fonctions du C ne sont pas `static`, il faut le demander explicitement. C'est exactement l'inverse en assembleur où tout label est implicitement `static` ; il faut demander avec la directive `.globl` de le rendre visible. |
| 380 | 1. Déclarer `static` une variable locale permet de la rendre persistante, c'est-à-dire qu'elle conserve sa valeur entre deux appels. Cette variable locale n'est pas dans le contexte de la fonction (c'est-à-dire qu'elle n'est pas dans la pile parce que le contexte est libéré en sortie de fonction). Une variable locale `static` est en fait allouée comme une variable globale mais son usage est limité à la seule fonction où elle est définie. |
| 381 | '' |
| 382 | }}} |
| 383 | 1. Pourquoi déclarer des fonctions ou des variables `extern` ? |
| 384 | {{{#!protected ------------------------------------------------------------------------------------ |
| 385 | '' |
| 386 | * Ça n'ont plus ce n'est pas dit dans le cours mais c'est sensé être connu, sinon c'est qu'il y a des choses à apprendre. Notez que la directive externe est implicite en C et qu'on peut donc ne pas l'écrire. On la met pour la lisibilité du code. |
| 387 | * Les déclarations `extern` permettent d'informer que le compilateur qu'une variable ou qu'une fonction existe et est définie ailleurs. Le compilateur connaît ainsi le type de la variable ou du prototype des fonctions, il sait donc comment les utiliser. En C, par défaut, les variables et les fonctions doivent être déclarées / leur existence et type doit être connus avant leur utilisation. |
| 388 | * Il n'y a pas de déclaration `extern` en assembleur parce que ce n'est pas un langage typé. Pour l'assembleur, un label c'est juste une adresse donc un nombre. |
| 389 | '' |
| 390 | }}} |
| 391 | 1. Comment déclarer un tableau de structures en variable globale ? La structure est nommée `test_s`, elle a deux champs `int` nommés `a` et `b`. Le tableau est nommé `tab` et a 2 cases. |
| 392 | {{{#!protected ------------------------------------------------------------------------------------ |
| 393 | '' |
| 394 | * Là encore, ce sont des connaissances censées être connues, mais c'est important parce qu'on a besoin de le comprendre pour la déclaration des registres du TTY. |
| 395 | {{{#!c |
| 396 | struct test_s { |
| 397 | int a; |
| 398 | int b; |
| 399 | }; |
| 400 | struct test_s tab[2]; |
| 401 | }}} |
| 402 | '' |
| 403 | }}} |
| 404 | 1. Supposons que la structure `tty_s` et le tableau de registres de `TTY` soient définis comme suit. Écrivez une fonction C `int getchar(void)` bloquante qui attend un caractère tapé au clavier sur le `TTY0`. Nous vous rappelons qu'il faut attendre que le registre `TTY_STATUS` soit différent de 0 avant de lire `TTY_READ`. `NTTYS` est un `#define` définit dans le Makefile de compilation avec le nombre de terminaux du SoC (en utilisant l'option `-D` de gcc). (C9 S10) |
| 405 | {{{#!c |
| 406 | struct tty_s { |
| 407 | int write; // tty's output |
| 408 | int status; // tty's status something to read if not null) |
| 409 | int read; // tty's input |
| 410 | int unused; // unused |
| 411 | }; |
| 412 | extern volatile struct tty_s __tty_regs_map[NTTYS]; |
| 413 | }}} |
| 414 | {{{#!protected ------------------------------------------------------------------------------------ |
| 415 | '' |
| 416 | * En principe, cela ne devrait pas poser de difficulté, mais cela nécessite d'avoir compris comment fonctionne le TTY et de savoir écrire une fonction en C. Pour aider, au cas où, on peut donner la description de ce qui est attendu:\\Tant que le registre `status` est à 0 alors attendre, puis lire le registre `read`.\\Notez que cela nécessite de savoir accéder aux champs d'une structure. |
| 417 | {{{#!c |
| 418 | int getchar(void) |
| 419 | { |
| 420 | while (__tty_regs_map[0].status == 0); |
| 421 | return __tty_regs_map[0].read; |
| 422 | } |
| 423 | }}} |
| 424 | '' |
| 425 | }}} |
| 426 | 1. Savez-vous à quoi sert le mot clé `volatile` ? Nous n'en avons pas parlé en cours, mais c'est nécessaire pour les adresses des registres de périphérique, une idée ... ? |
| 427 | {{{#!protected ------------------------------------------------------------------------------------ |
| 428 | '' |
| 429 | * Ce n'est pas dit dans le cours, mais c'est un concept important. Quand le programme doit aller chercher une donnée dans la mémoire puis faire plusieurs calculs dessus, le compilateur optimise en réservant un registre du processeur pour cette variable afin de ne pas être obligé d'aller lire la mémoire à chaque fois. Mais, il y a des cas où ce comportement n'est pas souhaitable (il est même interdit). C'est le cas pour les données qui se trouvent dans les registres de périphériques. Ces données peuvent être changées par le périphérique sans que le processeur le sache, de sorte qu'une valeur lue par le processeur à l'instant `t` n'est plus la même (dans le registre du périphérique) à l'instant `t+1`. Le compilateur ne doit pas optimiser, il doit aller chercher la donnée en mémoire à chaque fois que le programme le demande. |
| 430 | * `volatile` permet de dire à `gcc` que la variable en mémoire peut changer à tout moment, elle est volatile. Ainsi quand le programme demande de lire une variable `volatile` le compilateur doit toujours aller la lire en mémoire. Il ne doit jamais chercher à optimiser en utilisant un registre afin de réduire le nombre de lecture mémoire (load). De même, quand le programme écrit dans une variable `volatile`, cela doit toujours provoquer une écriture dans la mémoire (store). |
| 431 | * Ainsi, les registres de périphériques doivent toujours être impérativement lus ou écrits à chaque fois que le programme le demande, parce que c'est justement ces lectures et ces écritures qui commandent le périphérique. |
| 432 | '' |
| 433 | }}} |