Changes between Version 21 and Version 22 of AS6-TME-B5


Ignore:
Timestamp:
Feb 21, 2022, 10:44:55 AM (3 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B5

    v21 v22  
    1414
    1515La majorité des réponses aux questions sont dans le cours ou dans le rappel du cours donné au début de cette page, 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 :-)
    16 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 auto-corriger, mais pour qu'il soit utile, lisez-le après avoir chercher vous-même les réponses.
     16Quand 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.
    1717
    1818
     
    2929{{{#!protected ------------------------------------------------------------------
    3030'''
    31  * Non, la majorité du code s'exécute en mode utilisateur (user) mais lorsqu'il fait un appel système, il entre dans le noyau pour exécuter les fonctions rendant le service avec le droit du root, et c'est toujours le processus utilisateur qui s'exécute mais avec les droits du root.
     31 * Non, la majorité du code s'exécute en mode utilisateur (user), mais lorsqu'il fait un appel système, il entre dans le noyau pour exécuter les fonctions rendant le service avec le droit du root, et c'est toujours le processus utilisateur qui s'exécute, mais avec les droits du root.
    3232'''
    3333}}}
     
    3535{{{#!protected ------------------------------------------------------------------
    3636'''
    37  * Non pour les interruptions, les demandes d'interruption (IRQ pour Interrupt ReQuest) sont faites par les périphériques grâce à des signaux électriques binaires (2 états). Ces demandes ne peuvent pas être demander directement par le code de l'utilisateur. Toutefois, une IRQ est la conséquence d'une commande ou d'une configuration faite par le programme, alors on pourrait dire que les IRQ sont provoquées par les programmes.
     37 * Non pour les interruptions, les demandes d'interruption (IRQ pour Interrupt ReQuest) sont faites par les périphériques grâce à des signaux électriques binaires (2 états). Ces demandes ne peuvent pas être faites directement par le code de l'utilisateur. Toutefois, une IRQ est la conséquence d'une commande ou d'une configuration faite par le programme, alors on pourrait dire que les IRQ sont provoquées par les programmes.
    3838 * Oui pour les exceptions, une exception est toujours la conséquence de l'exécution d'une instruction que le processeur ne peut pas ou ne sait pas faire.
    3939'''
     
    4545'''
    4646}}}
    47 1. Dans un fichier exécutable, avant qu'il ne soit chargé en mémoire, on trouve le code du programme et les données globales. Est-ce qu'il y a aussi les piles d'exécution des threads ? justifiez votre réponse.
     471. Dans un fichier exécutable, avant qu'il ne soit chargé en mémoire, on trouve le code du programme et les données globales. Est-ce qu'il y a aussi les piles d'exécution des threads ? Justifiez votre réponse.
    4848{{{#!protected ------------------------------------------------------------------
    4949'''
     
    5454{{{#!protected ------------------------------------------------------------------
    5555'''
    56  * On en a au moins 1 dont la fonction principale est `main()`. Le nombre maximum est défini dans le système d'exploitation pour notre cas, mais plus généralement, il est dépend de la quantité de mémoire disponible car chaque thread utilise une pile qui peut être grande.
     56 * On en a au moins 1 dont la fonction principale est `main()`. Le nombre maximum est défini dans le système d'exploitation pour notre cas, mais plus généralement, il est dépend de la quantité de mémoire disponible, car chaque thread utilise une pile qui peut être grande.
    5757'''
    5858}}}
     
    100100{{{#!protected ------------------------------------------------------------------
    101101'''
    102  * C'est un ''thread switching overhead cost'', ce qui signifie ''frais de commutation''. C'est le temps que le noyau met pour sélectionner un nouveau thread (avec l'ordonnanceur), sauver le contexte du thread entrant et charger le contexte du thread entrant. Nous n'avons pas plusieurs processus dans notre application, cet ''overhead'' est donc assez court car tout les threads partage le même espace d'adressage, mais quand il y a plusieurs processus, le coût de changement de thread de 2 processus distincts est beaucoup plus cher, car il faut vider (''flush'') les caches, nous en parlerons au prochain cours.
     102 * C'est un ''thread switching overhead cost'', ce qui signifie ''frais de commutation''. C'est le temps que le noyau met pour sélectionner un nouveau thread (avec l'ordonnanceur), sauver le contexte du thread entrant et charger le contexte du thread entrant. Nous n'avons pas plusieurs processus dans notre application, cet ''overhead'' est donc assez court, car tous les threads partagent le même espace d'adressage, mais quand il y a plusieurs processus, le coût de changement de thread de 2 processus distincts est beaucoup plus cher, car il faut vider (''flush'') les caches, nous en parlerons au prochain cours.
    103103'''
    104104}}}
     
    106106{{{#!protected ------------------------------------------------------------------
    107107'''
    108  * C'est une politique ''round robin'' ou ''robin des bois'', ou à tour de rôle équitablement. Attention à ne pas la confondre avec la politique ''fifo'', dans cette dernière ce que l'ordonnanceur, c'est le prochain thread entrant sera celui qui est sorti depuis le plus longtemps, ou dit autrement, quand un processeur sort (perd le processeur), il sera le dernier à le regagner. Vous allez dire que c'est du ''round robin'', mais non, dans la politique ''fifo'', ce sont les threads eux-mêmes qui décident quand ils rendent le processeur, par un appel explicite à `thread_yield()` ou lorsqu'il demande une ressource indisponible. Il n'y a pas de recherche d'équité alors qu'avec la politique ''round robin'', le noyau utilise un timer pour que chaque thread dispose du même temps d'exécution en imposant des `thread_yield()`.
     108 * C'est une politique ''round robin'' ou ''robin des bois'', ou à tour de rôle équitablement. Attention à ne pas la confondre avec la politique ''fifo'', dans cette dernière ce que l'ordonnanceur, c'est le prochain thread entrant sera celui qui est sorti depuis le plus longtemps, ou dit autrement, quand un processeur sort (c.-à-d. perd le processeur), il sera le dernier à le regagner. Vous allez dire que c'est du ''round robin'', mais non, dans la politique ''fifo'', ce sont les threads eux-mêmes qui décident quand ils rendent le processeur, par un appel explicite à `thread_yield()` ou lorsqu'il demande une ressource indisponible. Il n'y a pas de recherche d'équité alors qu'avec la politique ''round robin'', le noyau utilise un timer pour que chaque thread dispose du même temps d'exécution en imposant des `thread_yield()`.
    109109'''
    110110}}}
     
    112112{{{#!protected ------------------------------------------------------------------
    113113'''
    114  * Ça dépend un peu de la fréquence du processeur. Il faut que la commutation soit assez rapide pour donner l'illusion du parallélisme (l'impression que tout les threads s'exécutent en même temps), mais pas trop à cause de l'overhead de changement de thread. La réponse est entre 10 et 100Hz. Plus la fréquence du processeur est grande plus la fréquence de commutation peut être rapide. À 1Ghz, un processeur exécutent 10 millions de cycles en 10ms (100Hz), si l'overhead de changement de thread est de 1000 cycles (un ordre de grandeur), l'overhead prend 0.01% du temps d'exécution du processeur, c'est négligeable.
     114 * Ça dépend un peu de la fréquence du processeur. Il faut que la commutation soit assez rapide pour donner l'illusion du parallélisme (l'impression que tous les threads s'exécutent en même temps), mais pas trop à cause de l'overhead de changement de thread. La réponse est entre 10 et 100Hz. Plus la fréquence du processeur est élevée, plus la fréquence de commutation peut être rapide. À 1GHz, un processeur exécute 10 millions de cycles en 10ms (100Hz), si l'overhead de changement de thread est de 1000 cycles (un ordre de grandeur), l'overhead prend 0.01% du temps d'exécution du processeur, c'est négligeable.
    115115'''
    116116}}}
     
    133133{{{#!protected ------------------------------------------------------------------
    134134'''
    135  * C'est l'IRQ du timer pour respecter la politique ''round robin'', mais pas seulement, on retire le processeur aux threads bloqués, parce qu'ils ont demandé une ressource au noyau mais que cette ressource n'est pas disponible. Le thread peut aussi demander à rendre le processeur.
     135 * C'est l'IRQ du timer pour respecter la politique ''round robin'', mais pas seulement, on retire le processeur aux threads bloqués, parce qu'ils ont demandé une ressource au noyau, mais que cette ressource n'est pas disponible. Le thread peut aussi demander à rendre le processeur.
    136136'''
    137137}}}
     
    148148'''
    149149}}}
    150 1. Où est sauvé le contexte d'un thread ? Que pouvez dire de la fonction de sauvegarde ?
    151 {{{#!protected ------------------------------------------------------------------
    152 '''
    153  *
    154 '''
    155 }}}
    156 1. Chaque thread dispose de sa pile propre, est-ce que l'on doit aussi sauver la pile lors des changement de thread ?
     1501. Où est sauvé le contexte d'un thread ? Que pouvez-vous dire de la fonction de sauvegarde ?
     151{{{#!protected ------------------------------------------------------------------
     152'''
     153 *
     154'''
     155}}}
     1561. Chaque thread dispose de sa pile propre, doit-on aussi sauver la pile lors des changements de thread ?
    157157{{{#!protected ------------------------------------------------------------------
    158158'''
     
    177177'''
    178178}}}
    179 1. La structure `thread_s` rassemble les propriété du thread, sa pile et le tableau de sauvegarde de son contexte. Cette structure est, dans l'état actuel du code` entièrement dans dans le segment des données globales de l'application. Pouvez-vous justifiez cette situation et en discuter ?
    180 {{{#!protected ------------------------------------------------------------------
    181 '''
    182  *
    183 '''
    184 }}}
    185 1. Le tableau de sauvegarde du contexte d'un thread est initialisé avec des valeurs qui seront chargées dans les registres du processeur au premier chargement du threads. Tous les registres n'ont pas besoin d'être initialisé avec une valeur. Seuls les registres `$c0_sr` (`$12` du coprocesseur système) , `$sp` (`$29` des GPR) et `$ra` (`$31` des GPR) ont besoins d'avoir une valeur choisie. Pourquoi ?
     1791. La structure `thread_s` rassemble les propriétés du thread, sa pile et le tableau de sauvegarde de son contexte. Cette structure est, dans l'état actuel du code` entièrement dans dans le segment des données globales de l'application. Pouvez-vous justifier cette situation et en discuter ?
     180{{{#!protected ------------------------------------------------------------------
     181'''
     182 *
     183'''
     184}}}
     1851. Le tableau de sauvegarde du contexte d'un thread est initialisé avec des valeurs qui seront chargées dans les registres du processeur au premier chargement du thread. Tous les registres n'ont pas besoin d'être initialisés avec une valeur. Seuls les registres `$c0_sr` (`$12` du coprocesseur système) , `$sp` (`$29` des GPR) et `$ra` (`$31` des GPR) ont besoin d'avoir une valeur choisie. Pourquoi ?
    186186{{{#!protected ------------------------------------------------------------------
    187187'''
     
    195195'''
    196196}}}
    197 1. La fonction `sched_switch()` appelle d'abord l'électeur de thread qui choisit le thread entrant (qui gagne le processeur), puis `sched_switch()` sauve le contexte du thread sortant (qui perd le processeur) et charge le contexte du thread entrant, enfin `sched_switch()` change l'état du thread entrant à `RUNNING`. `sched_switch()` est appelée par `thread_yield()`. Pouvez expliquer pourquoi avoir créé `sched_switch()` ? Ce n'est pas évident au premier abord, mais il y a une raison.
     1971. La fonction `sched_switch()` appelle d'abord l'électeur de thread qui choisit le thread entrant (qui gagne le processeur), puis `sched_switch()` sauve le contexte du thread sortant (qui perd le processeur) et charge le contexte du thread entrant, enfin `sched_switch()` change l'état du thread entrant à `RUNNING`. `sched_switch()` est appelée par `thread_yield()`. Pouvez-vous expliquer pourquoi avoir créé `sched_switch()` ? Ce n'est pas évident au premier abord, mais il y a une raison.
    198198{{{#!c
    199199void sched_switch (void) { //
     
    204204      thread_current_idx = th_next;                        // mise à jour de thread_current_idx
    205205      thread_load (thread_tab[th_next]->context);          // chargement de contexte & sortie par jr $31
    206     }                                                      // donc de thread_save() mais qui rend 0
     206    }                                                      // donc de thread_save(), mais qui rend 0
    207207  }
    208208  thread_tab[thread_current_idx]->state= TH_STATE_RUNNING; // the thread choisi est dans l'état
     
    218218'''
    219219}}}
    220 1. Quand un thread est élu pour la première fois, à la sortie de `thread_load()`, on appelle la fonction `thread_bootstrat()`. Retrouver dans les transparents du cours les étapes qui vont mener à l'exécution de la fonction principale du thread élu, et expliquez-les.
     2201. Quand un thread est élu pour la première fois, à la sortie de `thread_load()`, on appelle la fonction `thread_bootstrat()`. Retrouvez dans les transparents du cours les étapes qui vont mener à l'exécution de la fonction principale du thread élu, et expliquez-les.
    221221{{{#!protected ------------------------------------------------------------------
    222222'''
     
    230230'''
    231231}}}
    232 1. Quand un thread **TS** perd le processeur pour une raison X à la date `T`, il entre dans le noyau par kentry, puis il y a une séquence d'appel de fonctions jusqu'à la fonction `thread_load()` du thread entrant **TE**. Lorsqu'on sort de ce `thread_load()`, on est dans le nouveau thread **TE**. Plus tard, le thread **TS** sera élu à son tour et gagnera à nouveau le processeur en sortant lui aussi d'un `thread_load()`. En conséquence, on sortira de la séquence des appels qu'il y avait eu à la date `T`.\\Expliquez, en vous appuyant sur la description du comportement précédent, pourquoi on ne sauve pas les registres temporaires dans le contexte des threads.
    233 {{{#!protected ------------------------------------------------------------------
    234 '''
    235  *
    236 '''
    237 }}}
    238 1. Dans le cours, nous suivons l'exécution du code au démarrage (vers le slide 37), nous pouvons voir que la fonction `kinit()` fait 3 choses importantes : (1) initialiser à `0` la section `BSS` (contenant les variables globales non explicitement initialisée dans le programme), (2) demander à l'architecture de s'initialiser et (3) lancer la première (et ici seule) application.
     2321. Quand un thread **TS** perd le processeur pour une raison X à la date `T`, il entre dans le noyau par kentry, puis il y a une séquence d'appel de fonction jusqu'à la fonction `thread_load()` du thread entrant **TE**. Lorsqu'on sort de ce `thread_load()`, on est dans le nouveau thread **TE**. Plus tard, le thread **TS** sera élu à son tour et gagnera à nouveau le processeur en sortant lui aussi d'un `thread_load()`. En conséquence, on sortira de la séquence des appels qu'il y avait eu à la date `T`.\\Expliquez, en vous appuyant sur la description du comportement précédent, pourquoi on ne sauve pas les registres temporaires dans le contexte des threads.
     233{{{#!protected ------------------------------------------------------------------
     234'''
     235 *
     236'''
     237}}}
     2381. Dans le cours, nous suivons l'exécution du code au démarrage (vers le slide 37), nous pouvons voir que la fonction `kinit()` fait 3 choses importantes : (1) initialiser à `0` la section `BSS` (contenant les variables globales non explicitement initialisées dans le programme), (2) demander à l'architecture de s'initialiser et (3) lancer la première (et ici seule) application.
    239239{{{#!c
    240240void kinit (void)
     
    257257}
    258258}}}
    259  a. Où sont définis les symboles `__bss_origin`, `__bss_end`, `__main_thread`, `_start` et quels sont leur type ?
    260 {{{#!protected ------------------------------------------------------------------
    261 '''
    262  *
    263 '''
    264 }}}
    265  a. Dites ce que sont les arguments `2` et `3` de `thread_kernel()` et pourquoi, ici, on les mets à `0`.
     259 a. Où sont définis les symboles `__bss_origin`, `__bss_end`, `__main_thread`, `_start` et quel est leur type ?
     260{{{#!protected ------------------------------------------------------------------
     261'''
     262 *
     263'''
     264}}}
     265 a. Dites ce que sont les arguments `2` et `3` de `thread_kernel()` et pourquoi, ici, on les met à `0`.
    266266{{{#!protected ------------------------------------------------------------------
    267267'''
     
    281281'''
    282282}}}
    283  a. Pour le chargement de thread `main()` avec `thread_load (_main_thread.context)`, on initialise les registres `$16` à `$23`, `$30`, `$c0_EPC`, est-utile ? Si oui pourquoi ? Si non, pourquoi faire ces initialisations ?
     283 a. Pour le chargement de thread `main()` avec `thread_load (_main_thread.context)`, on initialise les registres `$16` à `$23`, `$30`, `$c0_EPC`, est-utile ? Si oui pourquoi ? Sinon, pourquoi faire ces initialisations ?
    284284{{{#!protected ------------------------------------------------------------------
    285285'''