Changes between Version 45 and Version 46 of AS6-TME-B5
- Timestamp:
- Feb 22, 2022, 7:17:01 PM (3 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
AS6-TME-B5
v45 v46 9 9 La majorité des réponses aux questions sont dans le cours, c'est voulu. Les questions suivent à peu près l'ordre du cours, elles sont simples, mais vous avez besoin de comprendre le cours pour y répondre :-) 10 10 Quand une question vous demande si quelque chose est vrai ou faux, ne répondez pas juste "oui" ou "non », mais justifiez vos réponses avec une petite phrase. Le but de ces questions est d'évaluer vos connaissances, donc plus vous êtes précis, mieux c'est. Vous avez un corrigé que vous devez consulter pour vous autocorriger, mais pour qu'il soit utile, lisez-le après avoir cherché vous-même les réponses. 11 Dans certains cas, ce ne sera pas simple, mais tentez quand même une réponse, même si vous savez que c'est faux car ce sera plus simple de comprendre la réponse.11 Dans certains cas, ce ne sera pas simple, mais tentez quand même une réponse, même si vous savez que c'est faux, car ce sera plus simple de comprendre la réponse. 12 12 13 13 == … … 119 119 ''' 120 120 * C'est le **tick**. Un tick d'horloge est la durée entre deux IRQ du timer. 121 * Dans l'état actuel du code, on fait une commutation de thread à chaque tick. Dans un système plus évolué, on a la notion de **quantum** qui correspond pas un nombre fini de tick, par exemple 1 quantum = 10 ticks. Ce quantum peut varier au cours du temps pour donner plus de temps à certains threads (au démarrage par exemple ou pour des tâches que l'on veut favoriser)121 * Dans l'état actuel du code, on fait une commutation de thread à chaque tick. Dans un système plus évolué, on a la notion de **quantum** qui correspond à un nombre fini de ticks, par exemple 1 quantum = 10 ticks. Ce quantum peut varier au cours du temps pour donner plus de temps à certains threads (au démarrage par exemple ou pour des tâches que l'on veut favoriser) 122 122 ''' 123 123 }}} … … 175 175 * C'est la fonction `thread_load()` qui se charge du chargement de la restauration du contexte du thread entrant (nouvellement élu). 176 176 * Quand on sort de la fonction `thread_load()`, il y a en effet 2 cas: 177 * Le thread entrant n'a jamais été élu. Dans ce cas, le `jr $31` va nous faire entrer dans la fonction `thread_bootstrap()` dont le but est lancer le thread en allant chercher les informations dans la structure `thread_s` du thread nouvellement élu, à savoir177 * Le thread entrant n'a jamais été élu. Dans ce cas, le `jr $31` va nous faire entrer dans la fonction `thread_bootstrap()` dont le but est de lancer le thread en allant chercher les informations dans la structure `thread_s` du thread nouvellement élu, à savoir 178 178 * la fonction de démarrage `_start()` ou `thread_start()` 179 179 * la fonction principale du thread (uniquement pour les threads standards, c'est inutile pour le thread `main()`, on sait que c'est `main()`) … … 183 183 * Pour qu'on ne rentre pas dans une boucle sans fin, la valeur de retour de `thread_save()` après une restauration de contexte est `0` (alors que c'est `1` après une sauvegarde). On teste donc cette valeur de retour de `thread_save()` pour savoir ce qu'on doit faire ensuite. 184 184 * On sort ensuite de `sched_switch()` et on revient dans `thread_yield()` (actuellement c'est le seul cas, mais nous verrons d'autres fonctions appelant `sched_switch())`. 185 * Après, on revient dans un syscall ou dans l'ISR du timer, suivant l'événement qui avait abouti à la perte du processeur par le thread cour rant.185 * Après, on revient dans un syscall ou dans l'ISR du timer, suivant l'événement qui avait abouti à la perte du processeur par le thread courant. 186 186 187 187 ''' … … 213 213 * une dans l'espace utilisateur pour la pile et peut-être d'autres informations (que nous verrons plus tard) 214 214 * une dans l'espace noyau, pour le contexte du thread et ses propriétés. 215 * Lors de la création d'un thread, il faudrait allouer deux structures dynamiquement, mais c'est impossible dans l'état actuel du code car nous n'avons pas d'allocateur de mémoire dynamique.215 * Lors de la création d'un thread, il faudrait allouer deux structures dynamiquement, mais c'est impossible dans l'état actuel du code, car nous n'avons pas d'allocateur de mémoire dynamique. 216 216 * Nous avons donc fait un choix simplificateur et une seule structure entièrement dans l'espace utilisateur. C'est un choix temporaire. 217 217 ''' … … 294 294 ''' 295 295 * Quand un thread rend le processeur, il le reprendra plus tard et reviendra précisément dans la fonction où il l'avait perdu (sauf si c'est une sortie définitive avec `thread_exit()` bien sûr). 296 * Quand on entre dans une fonction C, on sait que l'on peut modifier les registres temporaires car ils ne contiennent rien pour la fonction appelante. S'ils contiennent quelque-chose d'importantla fonction appelante doit sauver leur valeur avant d'entrer dans la fonction appelée.296 * Quand on entre dans une fonction C, on sait que l'on peut modifier les registres temporaires, car ils ne contiennent rien pour la fonction appelante. S'ils contiennent quelque chose d'important, la fonction appelante doit sauver leur valeur avant d'entrer dans la fonction appelée. 297 297 * Par contre, la fonction appelante suppose que les registres persistants conservent leur valeur, c.-à-d. qu'ils ne sont pas modifiés par la fonction appelée. 298 * C'est vrai pour toutes les fonctions, c'est donc vrai aussi pour la fonction `thread_save()`. Elle peut modifier les registres temporaires mais pas les registres persistants.298 * C'est vrai pour toutes les fonctions, c'est donc vrai aussi pour la fonction `thread_save()`. Elle peut modifier les registres temporaires, mais pas les registres persistants. 299 299 * C'est donc seul les registres persistants qu'elle sauve et qui seront restaurés par la fonction `thread_load()` qui sortira de `thread_save()` sans modification des registres persistants. 300 300 ''' … … 342 342 ''' 343 343 * Quend on sort de `thread_load()`, on entre dans la fonction `_start()` (après un passage par `thread_bootstrap()` et `thread_launch()`). Or on ne sort jamais de `_start()`, on sort de l'application avec `exit()`. 344 * On n'exécute rien après `thread_load()` mais si ça devait se produire alors c'est un kernel panic !345 ''' 346 }}} 347 1. Dans quelle pile s'exécute la fonction `kinit()` ? dans quelle section est-elle ? Pourquoi elle n'estque temporaire ?344 * On n'exécute rien après `thread_load()`, mais si ça devait se produire alors c'est un kernel panic ! 345 ''' 346 }}} 347 1. Dans quelle pile s'exécute la fonction `kinit()` ? Dans quelle section est-elle ? Pourquoi n'est-elle que temporaire ? 348 348 {{{#!protected ------------------------------------------------------------------ 349 349 ''' 350 350 * `kinit()` utilise une pile temporaire en haut du segment `.kdata`. Dès qu'on entre dans une application, on utilise la pile de l'application et on ne revient plus jamais sur la pile de `kinit()`. 351 * Dans le cas général, il y a toujours une application et un thread en cours, et le processeur utilise la pile cour rante.352 * Dans la version actuelle du code, il n'y a qu'une pile par thread ,utilisée à la fois par les fonctions utilisateur et par le kernel lors des syscall ou des ISR, mais bientôt, nous aurons 2 piles par thread, une pour le code utilisateur et une pour le code noyau.351 * Dans le cas général, il y a toujours une application et un thread en cours, et le processeur utilise la pile courante. 352 * Dans la version actuelle du code, il n'y a qu'une pile par thread qui est utilisée à la fois par les fonctions utilisateur et par le kernel lors des syscall ou des ISR, mais bientôt, nous aurons 2 piles par thread, une pour le code utilisateur et une pour le code noyau. 353 353 ''' 354 354 }}} … … 356 356 {{{#!protected ------------------------------------------------------------------ 357 357 ''' 358 * Non, ça ne sert à rien, le contexte restauré ne contient rien dans ces registres, ** mais** quand appelle `thread_load()`, on ne veut pas savoir si c'est pour la première fois pour ce thread ou si c'est une vraie restauration. C'est le `jr $31` qui retournera dans `sched_switch()` ou ira dans `thread_launch()`. Alors, on accepte de restaurer des registres inutilement, c'est juste la première fois.358 * Non, ça ne sert à rien, le contexte restauré ne contient rien dans ces registres, **,mais** quand appelle `thread_load()`, on ne veut pas savoir si c'est pour la première fois pour ce thread ou si c'est une vraie restauration. C'est le `jr $31` qui retournera dans `sched_switch()` ou ira dans `thread_launch()`. Alors, on accepte de restaurer des registres inutilement, c'est juste la première fois. 359 359 ''' 360 360 }}} … … 370 370 * Deux possibilités : 371 371 1. Soit le noyau abandonne le service et rend une erreur au thread pour l'informer que le service ne peut pas être rendu, et donc que le thread doit re-tenter sa chance plus tard ou faire autre chose. Pour l'analogie, vous dites à votre client de partir et de, s'il veut, revenir plus tard ou pas. 372 2. Soit le noyau demande un changement de thread avec `thread_yield()` pour faire quelque -chose d'utile pour un autre thread. Pour l'analogie, vous diteà votre client d'attendre et vous allez faire autre chose. Le client attend et tente de rentrer dès qu'un autre client sort du restaurant. Notez que dans cette analogie, il n'y a pas de file d'attente, le dernier client arrivé sera peut-être le premier servi.372 2. Soit le noyau demande un changement de thread avec `thread_yield()` pour faire quelque chose d'utile pour un autre thread. Pour l'analogie, vous dites à votre client d'attendre et vous allez faire autre chose. Le client attend et tente de rentrer dès qu'un autre client sort du restaurant. Notez que dans cette analogie, il n'y a pas de file d'attente, le dernier client arrivé sera peut-être le premier servi. 373 373 * Dans la version actuelle du code, le thread qui attend la ressource reste dans l'état READY et donc il sera élu par l'ordonnanceur quand son tour viendra pour tester à nouveau la ressource et la prendre si elle est disponible, sinon il subit à nouveau un `thread_yield()`. 374 374 * Ce comportement sera modifié en introduisant un état `WAIT` pour les threads afin de l'ordonnanceur ne donne pas le processeur à un thread dont la ressource attendue n'est pas disponible. … … 382 382 383 383 384 Pour la partie pratique , je ne vous faitpas modifier, ou pire écrire, la gestion des threads, mais je vous invite à lire le code. Vous allez changer la manière de lire les caractères du TTY pour la rendre plus efficace. Tous les changements seront faits dans le fichier `kernel/harch.c`.384 Pour la partie pratique de la séance, je ne vous fais pas modifier, ou pire écrire, la gestion des threads, mais je vous invite à lire le code. Vous allez changer la manière de lire les caractères du TTY pour la rendre plus efficace. Tous les changements seront faits dans le fichier `kernel/harch.c`. 385 385 386 386 == Etat du code par une lecture directe 387 387 388 Le code proposé fonctionne mais il a un problème que nous allons résoudre. Pour illustrer ce problème, nous allons partir d'un exemple.388 Le code proposé fonctionne, mais il a un problème que nous allons résoudre. Pour illustrer ce problème, nous allons partir d'un exemple. 389 389 390 390 **uapp/main.c** … … 543 543 * **`T0`** appelle tty_read() qui cède le processeur à **`T1`** en l'absence de frappes. 544 544 * Le thread **`T0`** demande des lectures à chaque qu'il a le processeur, **`T1`** prend le temps qui lui est donné jusqu'à l'IRQ du TIMER. 545 * Si l'utilisateur frappe beaucoup de touches pendant que **`T0`** n'a pas le processeur. Le caractères lus doivent être stockés quelque-part dans le contrôleur de TTY pour ne pas les perdre. Mais si cette mémoire est trop petite, on risque de perdre des caractères.545 * Si l'utilisateur frappe beaucoup de touches pendant que **`T0`** n'a pas le processeur. Les caractères lus doivent être stockés quelque part dans le contrôleur de TTY pour ne pas les perdre. Mais si cette mémoire est trop petite, on risque de perdre des caractères. 546 546 547 547 [[Image(htdocs:img/IRQTTY_1.png,nolink,width=600)]] 548 548 549 L'idée va être d'utiliser l'IRQ du TTY pour réagir à chaque frappe du clavier pendant l'exécution de **`T1`** pour lire le clavier et stocker les caractères dans une file d'attente. Sur le schéma ci-dessous est représentée l'exécution de l'isr du TTY qui vole des cycle à **`T1`** pour lire le caractère reçu par le contrôleur de TTY.549 L'idée va être d'utiliser l'IRQ du TTY pour réagir à chaque frappe du clavier pendant l'exécution de **`T1`** pour lire le clavier et stocker les caractères dans une file d'attente. Sur le schéma ci-dessous est représentée l'exécution de l'isr du TTY qui vole des cycles à **`T1`** pour lire le caractère reçu par le contrôleur de TTY. 550 550 551 551 [[Image(htdocs:img/IRQTTY_2.png,nolink,width=600)]] … … 553 553 === Mise en place d'une FIFO entre l'isr du TTY et la fonction `tty_read()` 554 554 555 Le caractère lu est mis dans une structure FIFO (First In First Out). Le schéma ci-dessous illustre le fonctionnement de la FIFO. Une fifo simple a un écrivain et un lecteur. L'écrivain écrit des données avec une commande `push()` tant que la FIFO n'est pas pleine. Si elle elle pleine, il y a deux comportements possibles : l'écrivain attend de la place ou l'écrivain jette la donnée, ça dépendce qu'on veut. Ici, on jettera, parce qu'on n'a pas le moyen de ralentir le flux de données (les frappes du clavier). Le lecteur lit les données avec `pull()` tant que la FIFO n'est pas vide.555 Le caractère lu est mis dans une structure FIFO (First In First Out). Le schéma ci-dessous illustre le fonctionnement de la FIFO. Une fifo simple a un écrivain et un lecteur. L'écrivain écrit des données avec une commande `push()` tant que la FIFO n'est pas pleine. Si, il y a deux comportements possibles : l'écrivain attend de la place ou alors l'écrivain jette la donnée, ça dépend de ce qu'on veut. Ici, on jettera, parce qu'on n'a pas le moyen de ralentir le flux de données (les frappes du clavier). Le lecteur lit les données avec `pull()` tant que la FIFO n'est pas vide. 556 556 557 557 [[Image(htdocs:img/FIFO_TTY.png,nolink,width=400)]] … … 602 602 603 603 Les schémas ci-dessous le comportement de la FIFO. 604 1. A l'initialisation comme la structure est dans les variables globales, les pointeur `pt_read` et `pt_write` sont à 0. La fifo est vide puisque `pt_read == pt_write`.605 1. On écrit `A` et on incrémente `pt_write`, `pt_read` ne bouge pas puisque l'on de lit pas.604 1. A l'initialisation comme la structure est dans les variables globales, les pointeurs `pt_read` et `pt_write` sont à 0. La fifo est vide puisque `pt_read == pt_write`. 605 1. On écrit `A` et on incrémente `pt_write`, `pt_read` ne bouge pas puisque l'on ne lit pas. 606 606 1. On écrit `B` et `C`. 607 607 1. On lit `A` et on incrémente `pt_read`, on peut lire parce que `pt_read != pt_write` et donc la FIFO n'est pas vide 608 608 1. On écrit `D` et `E`. Lors de l'incrément de `pt_write` on revient à 0 à cause du modulo `size` 609 1. On écrit `F` et ce sera fini parce la fifo est pleine `(pt_write + 1)%size == pt_read`, si on veut écrire à nouveau, il faut lire.609 1. On écrit `F` et ce sera fini parce que la fifo est pleine `(pt_write + 1)%size == pt_read`, si on veut écrire à nouveau, il faut lire. 610 610 611 611 [[Image(htdocs:img/FIFO.png,nolink,width=600)]] 612 613 {{{#!protected614 {{{#!c615 static int tty_fifo_push (struct tty_fifo_s *fifo, int c)616 {617 int pt_write_next = (fifo->pt_write + 1) % sizeof(fifo->data);618 if (pt_write_next != fifo->pt_read) {619 fifo->data [fifo->pt_write] = c;620 fifo->pt_write = pt_write_next;621 return 1;622 }623 return 0;624 }625 }}}626 }}}627 612 628 613 === Utilisation de la FIFO … … 637 622 638 623 {{{#!protected 639 Pour la correction, je vous met le code sans le ranger dans des fonctions, histoirede vous aider, mais pas trop...624 Pour la correction, je vous mets le code sans le ranger dans des fonctions, afin de vous aider, mais pas trop... 640 625 641 626 {{{#!c