Changes between Version 20 and Version 21 of AS6-TME-B7


Ignore:
Timestamp:
Apr 12, 2022, 4:53:35 PM (2 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AS6-TME-B7

    v20 v21  
    2929'''
    3030 * Dès lors que les allocations peuvent être demandées en parallèle et qu'elles ont besoin d'accéder en écriture à des structures partagées, il faut garantir que plusieurs modifications n'arrivent pas en même temps.
    31  * Dans kO6, les appels systèmes ne sont pas interruptibles. Cela signifie que, lorsque vous demandez au système de créer un objet (un thread par exemple) et qu'il utilise ses allocateurs de mémoire (`malloc_ustack()` et `kmalloc()`), il n'y a pas de risque de compétition avec d'autres threads sur le même core, parce que si le tick courant s'achève, l'IRQ du timer sera masquée et donc ne sera traitée que lorsque les allocations seront terminées. Mais il y a un risque s'il y a deux cores, parce qu'ils seront en vrai parallélisme. Pour le moment, il n'y a qu'un core parce qu'avec deux cores, il y a le problème de la cohérence des caches non traitée dans cette version du SoC. Il n'y a donc pas de risque pour les allocations du noyau, mais nous mettrons quand même les protections pour le futur.
     31 * Dans kO6, les appels système ne sont pas interruptibles. Cela signifie que, lorsque vous demandez au système de créer un objet (un thread par exemple) et qu'il utilise ses allocateurs de mémoire (`malloc_ustack()` et `kmalloc()`), il n'y a pas de risque de compétition avec d'autres threads sur le même core, parce que si le tick courant s'achève, l'IRQ du timer sera masquée et donc ne sera traitée que lorsque les allocations seront terminées. Mais il y a un risque s'il y a deux cores, parce qu'ils seront en vrai parallélisme. Pour le moment, il n'y a qu'un core parce qu'avec deux cores, il y a le problème de la cohérence des caches non traitée dans cette version du SoC. Il n'y a donc pas de risque pour les allocations du noyau, mais nous mettrons quand même les protections pour le futur.
    3232 * Pour les allocations de l'utilisateur, il y a un vrai problème. Un malloc peut parfaitement être interrompu par une IRQ du timer provoquant un changement de thread, lequel peut aussi demander une allocation. 
    3333'''
     
    3838 * Une opération de verrouillage pour mettre à 1 le verrou s'il est à 0 : `spin_lock(lock)`
    3939 * Une opération de déverrouillage pour le mettre à 0 : `spin_unlock(lock)`
    40  * Parfois, on a des API où `spin_lock()` est nommée `lock_acquire()` et `spin_unlock()` est nommée `lock_release()`, mais c'est la même idée. `spin` fait bien pensée que c'est une attente active, c'est mieux je trouve.
     40 * Parfois, on a des API où `spin_lock()` est nommée `lock_acquire()` et `spin_unlock()` est nommée `lock_release()`, mais c'est la même idée. `spin` fait bien pensé que c'est une attente active, c'est mieux, selon moi.
    4141 * Il y a des implémentations avec des timeouts, ou avec distribution de tickets pour un traitement dans l'ordre, et d'autres.
    4242'''
     
    4545{{{#!protected ------------------------------------------------------------------
    4646'''
    47  * Une séquence atomique, dans notre contexte, est une séquence d'instructions qui ne peut être interrompue, ou si elle l'est on peut le savoir. Ça n'a pas de rapport directe avec les interruption, mais en général, les séquences atomiques ne sont pas interruptibles et sont bornées dans le temps.
     47 * Une séquence atomique, dans notre contexte, est une séquence d'instructions qui ne peut être interrompue, ou si elle l'est on peut le savoir. Ça n'a pas de rapport direct avec les interruptions, mais en général, les séquences atomiques ne sont pas interruptibles et sont bornées dans le temps.
    4848 * On utilise les couples d'instructions LL/SC parce que le MIPS ne propose pas les instructions CAS et TAS et qu'il n'y a pas de mémoire de verrou.
    4949'''
     
    5252{{{#!protected ------------------------------------------------------------------
    5353'''
    54  * C'est le premier qui fait SC qui emporte la mise, avec la séquence vue en cours pour le spin_lock. Puisque le suivant, se verra refuser son SC étant donnée qu'il y a eu une opération mémoire autre que LL sur ce verrou.
     54 * C'est le premier qui fait SC qui emporte la mise, avec la séquence vue en cours pour le spin_lock. Puisque le suivant, on se verra refuser son SC étant donné qu'il y a eu une opération mémoire autre que LL sur ce verrou.
    5555'''
    5656}}}
     
    6767 * Il y a désormais la fonction `thread_wait()` qui permet à un thread (A) de passer de lui-même de l'état RUNNING à l'état WAIT, et la fonction `thread_notify()` qui permet à un thread (B) de faire passer un autre thread (A) de l'état WAIT à l'état READY.
    6868 * En cas de compétition, entre `thread_wait()` et `thread_notify()`, si `thread_notify()` s'exécute avant `thread_wait()`, il pourrait y avoir y risque que le thread (A) se mette en attente et ne soit pas notifié (puisque c'est déjà fait. Mais on sort de cette situation parce que `thread_wait()` peut savoir que `thread_notify` est déjà passé, car son propre état n'est plus RUNNING, il est READY.
    69  * Non, ce ne sont pas des fonctions de l'API utilisateur, ces fonctions sont appelées par les service du noyau. La raison est qu'avant de passer un thread à WAIT, il doit déjà s'être enregistré dans une file d'attente d'une ressource et se sont des structures interne du noyau. C'est pareil pour notify qui est invoqué lors d'une libération de ressource du noyau.
     69 * Non, ce ne sont pas des fonctions de l'API utilisateur, ces fonctions sont appelées par les services du noyau. La raison est qu'avant de passer un thread à WAIT, il doit déjà s'être enregistré dans une file d'attente d'une ressource et ce sont des structures internes du noyau. C'est pareil pour notify qui est invoqué lors d'une libération de ressource du noyau.
    7070'''
    7171}}}
     
    7676'''
    7777}}}
    78 1. Un thread passe de l'état RUNNING à l'état ZOMBIE lorsqu'il appelle `thread_exit()`. Quand passe-t-il à l'état DEAD ? Quand est-ce qu'on efface la piles et les structures utilisées par un thread, est-ce qu'un thread peut s'effacer lui-même ?
     781. Un thread passe de l'état RUNNING à l'état ZOMBIE lorsqu'il appelle `thread_exit()`. Quand passe-t-il à l'état DEAD ? Quand est-ce qu'on efface la pile et les structures utilisées par un thread, est-ce qu'un thread peut s'effacer lui-même ?
    7979{{{#!protected ------------------------------------------------------------------
    8080''' 
    8181 * Un thread passe à DEAD lors de l'exécution d'un `thread_join()` par un autre thread.
    82  * L'effacement a lieu après le passage à l'état DEAD et un thread ne peut pas s'effacer lui-même. Pour effacer, il faut exécuter du code, or pour exécuter du code, il faut une pile, et on ne peut pas effacer la pile parce qu'elle est utilisée. C'est pour ça que c'est toujour un autre thread qui fait le ménage.
    83  * Dans le code de kO6, il n'y a pas encore de ménage, ça permet de voir les threads mort de l'affichage du contenu du scheduler. Mais, plus tard cela pourra être ajouté dans le scheduler. Quand un thread devient RUNNING, il peut regarder s'il y a eu un DEAD juste précédemment et l'effacer. On pourrait aussi, le faire au dernier moment quand le kernel n'a plus de place, alors il fait le ménage (un peu comme chez moi à certaines périodes :-)
    84 '''
    85 }}}
    86 1. Le contenu de la structure `struct thread_s` (c'est-à-dire les champs qui la compose) est définie dans le fichier `kernel/kthread.c`. En conséquence cette structure n'est utilisable que par les fonctions de ce même fichier `kernel/kthread.c`. A l'extérieur, les fonctions ne manipulent que des pointeurs sur la structure de type `thread_t` (défnis par un `typedef struct thread_s * thread_t`. Pourquoi ce choix, pourquoi n'avoir pas définie la structure `thread_s` dans le fichier `kernel/kthread.h` ?
    87 {{{#!protected ------------------------------------------------------------------
    88 '''
    89  *
     82 * L'effacement a lieu après le passage à l'état DEAD et un thread ne peut pas s'effacer lui-même. Pour effacer, il faut exécuter du code, or pour exécuter du code, il faut une pile, et on ne peut pas effacer la pile parce qu'elle est utilisée. C'est pour ça que c'est toujours un autre thread qui fait le ménage.
     83 * Dans le code de kO6, il n'y a pas encore de ménage, ça permet de voir les threads DEAD de l'affichage du contenu du scheduler. Mais, plus tard, cela devra être ajouté, par exemple, dans le scheduler. Quand un thread devient RUNNING, il peut regarder s'il y a eu un DEAD juste précédemment et l'effacer. On pourrait aussi, le faire au dernier moment quand le kernel n'a plus de place, alors il fait le ménage (un peu comme chez moi à certaines périodes :-)
     84'''
     85}}}
     861. Le contenu de la structure `struct thread_s` (c'est-à-dire les champs qui la composent) est définie dans le fichier `kernel/kthread.c`. En conséquence cette structure n'est utilisable que par les fonctions de ce même fichier `kernel/kthread.c`. A l'extérieur, les fonctions ne manipulent que des pointeurs sur la structure de type `thread_t` (définis par un `typedef struct thread_s * thread_t`. Pourquoi ce choix, pourquoi n'avoir pas défini la structure `thread_s` dans le fichier `kernel/kthread.h` ?
     87{{{#!protected ------------------------------------------------------------------
     88'''
     89 * Le fait de ne pas rendre visibles les champs de la structure thread_s en dehors du fichier thread permet d'éviter des erreurs de programmation. En effet, pour modifier cette structure partagée, il faut prendre un verrou et le fait d'avoir réduit le nombre de fonctions ayant la possibilité de le faire est plus sûr.
     90 * Dans la mesure du possible, on essaye de faire ça pour toutes les structures du kernel. Cela n'a rien d'obligatoire, c'est juste plus sûr.
    9091'''
    9192}}}
     
    9394{{{#!protected ------------------------------------------------------------------
    9495'''
    95  *
     96 * Une condition de compétition, c'est quand on a deux séquences d'instructions indépendantes et pouvant être exécutée en parallèle et que l'état du système dépend de l'ordre d'exécution des instructions des deux séquences.
     97 * Cela se produit par exemple entre `thread_wait(A)` et `tread_notify(A)`. Si les deux fonctions commencent en même temps, on veut que le résultat final soit que le thread A ne soit pas WAIT.
    9698'''
    9799}}}
     
    99101{{{#!protected ------------------------------------------------------------------
    100102'''
    101  *
     103 * Fondamentalement, les spinlocks sont des verrous à attente active alors que les mutex sont des verrous à liste d'attente.
     104 * Un Mutex utilise un spinlock pour garantir la séquence atomique d'accès à sa structure.
    102105'''
    103106}}}
     
    105108{{{#!protected ------------------------------------------------------------------
    106109'''
    107  *
    108 '''
    109 }}}
    110 1. Le système définit `errno` de la façon suivante : `#define errno *__errno_location()`, que fait la fonction `errno_location()` ? Comment cela peut régler le problème ?
    111 {{{#!protected ------------------------------------------------------------------
    112 '''
    113  *
     110 * Le problème de errno, c'est qu'une application peut avoir plusieurs threads, c'est même le cas général, et chaque thread fait des appels système. Or lorsqu'un appel système sort, il doit mettre un code d'erreur dans la variable `errno`. Il soit y en avoir autant que de thread, mais toutes avec le même nom.
     111 * `errno` est une variable globale locale au thread. Elle est dans le Thread Local Storage.
     112 * Dans le cas de kO6, il n'y aura pas d'autres variables de cette catégorie pour le moment.
     113'''
     114}}}
     1151. Le système définit `errno` de la façon suivante : `#define errno *__errno_location()`, que fait la fonction `__errno_location()` ? Comment cela peut régler le problème ?
     116{{{#!protected ------------------------------------------------------------------
     117'''
     118 * `__errno_location()` est une fonction qui rend l'adresse d'une case de mémoire. Si cette adresse est différente pour chaque thread, le problème est réglé/
    114119'''
    115120}}}