Je lis l'article suivant de Robert Love
http://www.linuxjournal.com/article/6916
ça dit
"... Discutons du fait que les files d'attente de travail sont exécutées dans un contexte de processus. Cela contraste avec les autres mécanismes situés dans la moitié inférieure, qui s'exécutent tous dans un contexte d'interruption. Le code qui s'exécute dans un contexte d'interruption ne peut ni dormir ni se bloquer, le contexte n'a pas de processus de sauvegarde avec lequel replanifier. Par conséquent, comme les gestionnaires d'interruptions ne sont pas associés à un processus, le planificateur n'a rien à mettre en veille ni, plus important encore, à ce qu'il se réveille ... "
Je ne comprends pas. Autant que je sache, le planificateur du noyau est O (1), qui est implémenté via le bitmap. Alors, qu'est-ce qui empêche le scehduler de mettre en veille le contexte d'interruption et de prendre le prochain processus programmable et de le transmettre au contrôle?
Je pense que c'est une idée de design.
Bien sûr, vous pouvez concevoir un système dans lequel vous pouvez dormir en interruption, mais à part rendre le système difficile à comprendre et compliqué (de nombreuses situations à prendre en compte), cela n’aide en rien. Ainsi, du point de vue de la conception, déclarer le gestionnaire d'interruptions qui ne peut pas dormir est très clair et facile à mettre en œuvre.
De Robert Love (un hacker du noyau): http://permalink.gmane.org/gmane.linux.kernel.kernelnewbies/1791
Vous ne pouvez pas dormir dans un gestionnaire d'interruption, car les interruptions n'ont pas De contexte de processus de sauvegarde, et il n'y a donc rien à replanifier Dans. En d'autres termes, les gestionnaires d'interruptions ne sont pas associés à une tâche, Il n'y a donc rien à "mettre en veille" et (plus important encore) "rien à Réveiller". Ils doivent fonctionner atomiquement.
Ce n'est pas différent des autres systèmes d'exploitation. Dans la plupart des systèmes d'exploitation, les interruptions Ne sont pas liées. Les moitiés inférieures sont souvent, cependant.
Le gestionnaire d'erreurs de page peut dormir en raison de son invocation uniquementle code en cours d'exécution dans un contexte de processus. La mémoire Du noyau n'étant pas paginable, seuls les accès à la mémoire de l'espace utilisateur peuvent entraîner une erreurpage. Ainsi, seuls quelques endroits précis (tels que les appels à Copy_ {to, from} _user ()) peuvent être à l'origine d'une erreur de page dans le noyau. Ces lieux Doivent tous être créés avec un code qui peut dormir (c’est-à-dire le contexte du processus, Aucun verrou, etc.).
Alors, qu'est-ce qui empêche le scehduler de mettre en veille le contexte d'interruption et de prendre le prochain processus programmable et de le transmettre au contrôle?
Le problème est que le contexte d'interruption n'est pas un processus et ne peut donc pas être mis en veille.
Lorsqu'une interruption se produit, le processeur enregistre les registres sur la pile et passe au début du sous-programme de service d'interruption. Cela signifie que lorsque le gestionnaire d'interruption est en cours d'exécution, il s'exécute dans le contexte du processus qui s'exécutait lorsque l'interruption s'est produite. L'interruption s'exécute sur la pile de ce processus et, une fois le gestionnaire d'interruption terminé, ce processus reprend son exécution.
Si vous essayez de dormir ou de bloquer à l'intérieur d'un gestionnaire d'interruptions, vous arrêterez non seulement le gestionnaire d'interruptions, mais également le processus qu'il a interrompu. Cela pourrait être dangereux, car le gestionnaire d'interruptions n'a aucun moyen de savoir ce que le processus interrompu faisait, ni même s'il est prudent de suspendre ce processus.
Un scénario simple dans lequel les choses pourraient mal tourner serait un blocage entre le gestionnaire d'interruptions et le processus interrompu.
À ce stade, vous êtes dans une impasse. Process1 ne peut pas reprendre l'exécution tant que l'ISR n'est pas terminé avec sa pile. Mais l'ISR est bloqué dans l'attente de Process1 pour libérer LockA .
Parce que l'infrastructure de commutation de threads est inutilisable à ce stade. Lors du traitement d’une interruption, seuls les éléments de priorité plus élevée peuvent exécuter - Voir le Manuel du développeur Intel Software sur les interruptions, les tâches et la priorité du processeur . Si vous autorisez un autre thread à s'exécuter (ce que vous indiquez dans votre question, ce serait facile à faire), vous ne pourrez le laisser faire quoi que ce soit - si cela causait une erreur de page, vous deviez utiliser des services. dans le noyau qui sont inutilisables pendant le traitement de l’interruption (voir pourquoi ci-dessous).
En règle générale, votre seul objectif dans une routine d'interruption est d'amener le périphérique à cesser d'interrompre et de mettre en file d'attente quelque chose à un niveau d'interruption inférieur (sous Unix, il s'agit généralement d'un niveau sans interruption, mais pour Windows, il s'agit d'un niveau de dispatch, apc ou passif). faire le gros du travail lorsque vous avez accès à plus de fonctionnalités du noyau/os. Voir - Implémentation d'un gestionnaire .
C'est une propriété de la façon dont les O/S doivent fonctionner, pas quelque chose d'inhérent à Linux. Une routine d'interruption peut être exécutée à tout moment, de sorte que l'état de ce que vous avez interrompu est incohérent. Si vous avez interrompu le code de planification des threads, son état est incohérent, vous ne pouvez donc pas être sûr de pouvoir "dormir" et changer de thread. Même si vous protégez le code de commutation de threads contre toute interruption, la commutation de threads est une fonctionnalité de haut niveau du système d'exploitation et, si vous protégez tout ce sur quoi elle repose, une interruption devient davantage une suggestion que l'impératif impliqué par son nom.
Alors, qu'est-ce qui empêche le scehduler de mettre en veille le contexte d'interruption et de prendre le prochain processus programmable et de le transmettre au contrôle?
La planification se produit lors d'interruptions de la minuterie. La règle de base est qu'une seule interruption peut être ouverte à la fois. Par conséquent, si vous vous mettez en veille dans l'interruption "Données provenant du périphérique X", l'interruption de la minuterie ne peut pas s'exécuter pour la planifier.
Les interruptions se produisent également plusieurs fois et se chevauchent. Si vous mettez en veille l'interruption "got data" et récupérez plus de données, que se passe-t-il? C'est assez déroutant (et fragile) que la règle générale soit: ne pas dormir pendant les interruptions. Vous allez le faire mal.
Même si vous pouviez endormir une ISR, vous ne voudriez pas le faire. Vous voulez que vos ISR soient aussi rapides que possible afin de réduire le risque d'omission d'interruptions ultérieures.
Par nature, la question est de savoir si, dans le gestionnaire d’interruptions, vous pouvez obtenir un "current" valide (adresse du processus en cours task_structure), si oui, il est possible de modifier le contenu là-bas en conséquence pour le mettre en état "veille", qui peut être de retour par le planificateur plus tard si l’état est changé d’une manière ou d’une autre La réponse peut dépendre du matériel.
Mais dans ARM, il est impossible, car «current» n'a pas d'importance à traiter en mode d'interruption. Voir le code ci-dessous:
#linux/Arch/arm/include/asm/thread_info.h
94 static inline struct thread_info *current_thread_info(void)
95 {
96 register unsigned long sp asm ("sp");
97 return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
98 }
sp en mode UTILISATEUR et en mode SVC sont "identiques" ("identiques" ne signifient pas ici qu'elles sont égales; au lieu de cela, le sp du mode utilisateur pointe sur la pile d'espace utilisateur, tandis que le sp r13_svc du mode svc pointe sur la pile du noyau, où le processus utilisateur task_structure a été mis à jour lors du changement de tâche précédent. Lorsqu'un appel système se produit, le processus entre de nouveau dans l'espace du noyau, lorsque sp (sp_svc) n'est toujours pas modifié, ces 2 sp sont associés les uns aux autres, en ce sens, ils sont 'mêmes '), Ainsi, en mode SVC, le code du noyau peut obtenir le' courant 'valide. Mais sous d'autres modes privilégiés, par exemple le mode d'interruption, sp est 'différent', pointez sur l'adresse dédiée définie dans cpu_init (). Le «courant» calculé sous ce mode ne sera pas pertinent pour le processus interrompu, son accès entraînera des comportements inattendus. C'est pourquoi il est toujours dit que l'appel système peut se mettre en veille mais que le gestionnaire d'interruption ne le peut pas. L'appel système fonctionne sur le contexte du processus mais n'interrompt pas.
Le noyau Linux dispose de deux méthodes pour allouer une pile d’interruptions. L'un se trouve sur la pile de noyau du processus interrompu, l'autre est une pile d'interruptions dédiée par CPU. Si le contexte d'interruption est sauvegardé sur la pile d'interruptions dédiée par CPU, le contexte d'interruption n'est en réalité associé à aucun processus. La macro "en cours" produira un pointeur non valide sur le processus en cours, car la macro "en cours" avec une architecture donnée est calculée avec le pointeur de pile. Le pointeur de pile dans le contexte d'interruption peut pointer sur la pile d'interruptions dédiée, et non sur la pile de noyaux de certains processus.
Les gestionnaires d'interruption de niveau supérieur masquent les opérations de toutes les interruptions de priorité inférieure, y compris celles de l'interruption de la minuterie système. Par conséquent, le gestionnaire d’interruptions doit éviter de s’impliquer dans une activité susceptible de le faire dormir. Si le gestionnaire dort, le système peut se bloquer car le minuteur est masqué et incapable de planifier le thread en veille. Est-ce que ça a du sens?
Si une routine d’interruption de niveau supérieur en arrive au point où elle doit s’exécuter après un certain temps, elle doit alors placer une requête dans la file d’attente pour demander qu’une autre routine d’interruption soit exécutée (à priorité plus faible niveau) quelque temps plus tard.
Lorsque cette routine d'interruption est exécutée, elle remonte ensuite le niveau de priorité au niveau de la routine d'interruption d'origine et continue l'exécution. Cela a le même effet qu'un sommeil.
Interdire le blocage d'un gestionnaire d'interruptions est un choix de conception. Lorsque des données se trouvent sur le périphérique, le gestionnaire d'interruptions intercepte le processus en cours, prépare le transfert des données et active l'interruption. avant que le gestionnaire active l'interruption en cours, le périphérique doit être suspendu. Nous voulons garder nos E/S occupées et notre système réactif, nous ferions mieux de ne pas bloquer le gestionnaire d'interruptions.
Je ne pense pas que les "états instables" soient une raison essentielle. Les processus, qu'ils soient en mode utilisateur ou en mode noyau, doivent être conscients qu'ils peuvent être interrompus par des interruptions. Si le gestionnaire d'interruptions et le processus en cours accèdent à une structure de données en mode noyau et que la condition de concurrence existe, le processus en cours doit désactiver les interruptions locales. .
Je ne pense pas non plus que si le gestionnaire d'interruptions était bloqué, il ne pourrait pas être réveillé. Lorsque nous disons "bloquer", cela signifie en gros que le processus bloqué attend un événement/une ressource, il est donc lié à une file d'attente pour cet événement/cette ressource. À chaque fois que la ressource est libérée, le processus de libération est chargé de réactiver le ou les processus en attente.
Cependant, l’ennui, c’est que le processus bloqué ne peut rien faire pendant la période de blocage; il n'a rien fait de mal pour cette punition, qui est injuste. Et personne ne peut sûrement prédire le temps de blocage, de sorte que le processus innocent doit attendre une raison obscure et une durée illimitée.
Ce n’est qu’un choix de conception/mise en œuvre sous Linux. L'avantage de cette conception est simple, mais il peut ne pas être bon pour les exigences du système d'exploitation en temps réel.
D'autres systèmes d'exploitation ont d'autres conceptions/implémentations.
Par exemple, dans Solaris, les interruptions peuvent avoir des priorités différentes, ce qui permet à la plupart des périphériques d'appeler des interruptions dans des threads d'interruption. Les threads d'interruption permettent de dormir car chaque thread d'interruption possède une pile distincte dans le contexte du thread . La conception des threads d'interruption convient aux threads en temps réel qui doivent avoir des priorités plus élevées que les interruptions.