279 | | **Questions sur les Makefiles** |
| 279 | |
| 280 | |
| 281 | = 3. Programmation en C |
| 282 | |
| 283 | |
| 284 | |
| 285 | |
| 286 | Vous savez déjà programmer en C, mais vous allez voir ici des syntaxes ou des cas d'usage que vous ne connaissez peut-être pas encore. |
| 287 | 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. |
| 288 | |
| 289 | **Questions |
| 290 | |
| 291 | 1. Quels sont les usages du mot clé `static` en C ? (c'est une directive que l'on donne au compilateur C) |
| 292 | {{{#!protected ------------------------------------------------------------------------------------ |
| 293 | '' |
| 294 | * Le cours 9 n'en parle pas, mais dans le code vous trouverez cette directive un peu partout. Il y a deux usages. |
| 295 | 1. Déclarer `static` une variable globale ou une fonction en faisant précéder leur définition du mot clé `static` permets 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. |
| 296 | 1. Déclarer `static` une variable locale (dans une fonction donc) 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 d'exécution de la fonction (c'est-à-dire qu'elle n'est pas dans la pile parce que le contexte d'exécution 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. |
| 297 | '' |
| 298 | }}} |
| 299 | 1. Pourquoi déclarer des fonctions ou des variables `extern` ? |
| 300 | {{{#!protected ------------------------------------------------------------------------------------ |
| 301 | '' |
| 302 | * Ça non plus ce n'est pas dit dans le cours mais c'est sensé être connu. 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. |
| 303 | * Les déclarations `extern` permettent d'informer que le compilateur qu'une variable ou qu'une fonction existe et est définie dans un autre fichier. 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 leur type doivent être connus avant leur utilisation. |
| 304 | * 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. |
| 305 | '' |
| 306 | }}} |
| 307 | 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. |
| 308 | {{{#!protected ------------------------------------------------------------------------------------ |
| 309 | '' |
| 310 | * 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. |
| 311 | {{{#!c |
| 312 | struct test_s { |
| 313 | int a; |
| 314 | int b; |
| 315 | }; |
| 316 | struct test_s tab[2]; |
| 317 | }}} |
| 318 | '' |
| 319 | }}} |
| 320 | 1. Supposons que la structure `tty_s` et le tableau de registres de `TTY` soient définis comme suit. Écrivez la fonction C `int getchar0(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) |
| 321 | {{{#!c |
| 322 | struct tty_s { |
| 323 | int write; // tty's output |
| 324 | int status; // tty's status something to read if not null) |
| 325 | int read; // tty's input |
| 326 | int unused; // unused |
| 327 | }; |
| 328 | extern volatile struct tty_s __tty_regs_map[NTTYS]; |
| 329 | // extern : parce que ce tableau n'est pas dans ce fichier |
| 330 | // volatile : parce que le contenu du tableau peut changer tout seul, gcc doit le lire à chaque fois |
| 331 | // cela implique que gcc ne peut pas faire d'optimisation avec les registres du MIPS |
| 332 | // (cf. note en fin de page) |
| 333 | }}} |
| 334 | {{{#!protected ------------------------------------------------------------------------------------ |
| 335 | '' |
| 336 | * 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. |
| 337 | {{{#!c |
| 338 | int getchar0(void) |
| 339 | { |
| 340 | while (__tty_regs_map[0].status == 0); |
| 341 | return __tty_regs_map[0].read; |
| 342 | } |
| 343 | }}} |
| 344 | '' |
| 345 | }}} |
| 346 | 1. Écrivez la fonction C `int puts0(char *s)` qui écrit tous les caractères de la chaîne `s` sur le terminal TTY0. La fonction doit rendre le nombre de caractères écrits. On suppose que les registres des TTYs sont définis comme dans la question précédente. |
| 347 | {{{#!c |
| 348 | int puts0(char *s) |
| 349 | { |
| 350 | int res = 0; |
| 351 | while (*s) { // s est une variable locale que l'on peut modifier |
| 352 | __tty_regs_map[0].write = *s; // *s désigne le caractère pointé par s |
| 353 | s = s + 1; // on incrémente s, donc on pointe le caractère suivant |
| 354 | res = res + 1; // et on incrémente le nombre de caractères écrits |
| 355 | } |
| 356 | return res; // finalement, on rend le nombre de caractères écrits |
| 357 | } |
| 358 | }}} |
| 359 | '' |
| 360 | }}} |
| 361 | |
| 362 | |
| 363 | |
| 364 | == 4. Usage de Make |
| 365 | |
| 366 | |
358 | | |
359 | | |
360 | | |
361 | | |
362 | | = 4. Programmation en C |
363 | | |
364 | | |
365 | | |
366 | | |
367 | | 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. |
368 | | 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. |
369 | | |
370 | | **Questions |
371 | | |
372 | | 1. Quels sont les usages du mot clé `static` en C ? (c'est une directive que l'on donne au compilateur C) |
373 | | {{{#!protected ------------------------------------------------------------------------------------ |
374 | | '' |
375 | | * Le cours 9 n'en parle pas, mais dans le code vous trouverez cette directive un peu partout. Il y a deux usages. |
376 | | 1. Déclarer `static` une variable globale ou une fonction en faisant précéder leur définition du mot clé `static` permets 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. |
377 | | 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. |
378 | | '' |
379 | | }}} |
380 | | 1. Pourquoi déclarer des fonctions ou des variables `extern` ? |
381 | | {{{#!protected ------------------------------------------------------------------------------------ |
382 | | '' |
383 | | * Ç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. |
384 | | * 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 leur type doivent être connus avant leur utilisation. |
385 | | * 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. |
386 | | '' |
387 | | }}} |
388 | | 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. |
389 | | {{{#!protected ------------------------------------------------------------------------------------ |
390 | | '' |
391 | | * 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. |
392 | | {{{#!c |
393 | | struct test_s { |
394 | | int a; |
395 | | int b; |
396 | | }; |
397 | | struct test_s tab[2]; |
398 | | }}} |
399 | | '' |
400 | | }}} |
401 | | 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) |
402 | | {{{#!c |
403 | | struct tty_s { |
404 | | int write; // tty's output |
405 | | int status; // tty's status something to read if not null) |
406 | | int read; // tty's input |
407 | | int unused; // unused |
408 | | }; |
409 | | extern volatile struct tty_s __tty_regs_map[NTTYS]; |
410 | | }}} |
411 | | {{{#!protected ------------------------------------------------------------------------------------ |
412 | | '' |
413 | | * 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. |
414 | | {{{#!c |
415 | | int getchar(void) |
416 | | { |
417 | | while (__tty_regs_map[0].status == 0); |
418 | | return __tty_regs_map[0].read; |
419 | | } |
420 | | }}} |
421 | | '' |
422 | | }}} |
423 | | 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 ... ? |
424 | | {{{#!protected ------------------------------------------------------------------------------------ |
425 | | '' |
426 | | * 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. |
427 | | * `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). |
428 | | * 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. |
429 | | '' |
430 | | }}} |
| 448 | ----- |
| 449 | > ''Note sur le mot clé `volatile` |
| 450 | > * ''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 contrôleur 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.''\\ \\ |
| 451 | > * ''`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). '' |
| 452 | > * ''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.'' |