Présentation | Sous Linux, les processus qui n'attendent que le temps CPU sont appelés processus prêts. Ils sont placés dans une file d'attente d'exécution et l'indicateur d'état d'un processus prêt est TASK_RUNNING. Une fois la tranche de temps d'un processus en cours d'exécution épuisée, le planificateur du noyau Linux privera le processus du contrôle du processeur et sélectionnera un processus approprié à exécuter dans la file d'attente d'exécution. |
Bien sûr, un processus peut également libérer activement le contrôle du CPU. La fonction planning() est une fonction de planification qui peut être activement appelée par un processus pour planifier d'autres processus afin qu'ils occupent le processeur. Une fois que le processus qui abandonne activement le CPU est reprogrammé pour occuper le CPU, il commencera à s'exécuter à partir de l'emplacement où il s'est arrêté pour la dernière fois, c'est-à-dire qu'il commencera à s'exécuter à partir de la ligne de code suivante qui appelle planning().
Parfois, un processus doit attendre qu'un événement spécifique se produise, tel que l'initialisation du périphérique, la fin d'une opération d'E/S ou l'expiration du minuteur. Dans ce cas, le processus doit être supprimé de la file d'attente d'exécution et ajouté à une file d'attente. À ce moment, le processus entre en état de veille.
L'un est l'état de veille interruptible, son indicateur d'état est TASK_INTERRUPTIBLE ;
L'autre est un état de veille ininterrompue et son indicateur d'état est TASK_UNINTERRUPTIBLE. Un processus en état de veille interruptible dormira jusqu'à ce qu'une certaine condition devienne vraie. Par exemple, générer une interruption matérielle, libérer les ressources système que le processus attend ou délivrer un signal peuvent être les conditions pour réveiller le processus. L'état de sommeil ininterrompu est similaire à l'état de sommeil interruption, mais il a une exception, c'est-à-dire que le processus qui délivre le signal à cet état de sommeil ne peut pas changer son état, c'est-à-dire qu'il ne répond pas au signal de réveil. L'état de veille ininterrompue est généralement moins utilisé, mais il reste très utile dans certaines situations spécifiques. Par exemple, le processus doit attendre et ne peut être interrompu jusqu'à ce qu'un événement spécifique se produise.
Dans les systèmes d'exploitation Linux modernes, les processus entrent généralement en état de veille en appelant planning(). Le code suivant exécute
.
montre comment mettre en veille un processus en cours d'exécution.
sleep_task = actuel ;
set_current_state(TASK_INTERRUPTIBLE);
horaire();
func1();
/* Reste du code ... */
Dans la première instruction, le programme stocke un pointeur de structure de processus sleep_task, et current est une macro qui pointe vers l'exécution en cours
structure du processus. set_current_state() change l'état du processus de l'état d'exécution TASK_RUNNING à l'état de veille
TASK_INTERRUPTIBLE. Si planning() est planifié par un processus dans l'état TASK_RUNNING, alors planning() planifiera un autre processus pour occuper le CPU ; si planning() est planifié par un processus dans l'état TASK_INTERRUPTIBLE ou TASK_UNINTERRUPTIBLE, alors il y a une étape supplémentaire pour En cours d'exécution : le processus en cours d'exécution sera supprimé de la file d'attente d'exécution avant qu'un autre processus ne soit planifié, ce qui entraînera la mise en veille du processus en cours car il n'est plus dans la file d'attente d'exécution.
Nous pouvons utiliser la fonction suivante pour réveiller le processus qui vient de s'endormir.
wake_up_process(sleeping_task);
Après avoir appelé wake_up_process(), le statut du processus en veille sera défini sur TASK_RUNNING et le planificateur
Il sera ajouté à la file d'attente d'exécution. Bien entendu, ce procédé ne pourra être mis en oeuvre que la prochaine fois qu'il sera programmé par l'ordonnanceur.
Réveil invalide
Dans presque tous les cas, le processus se mettra en veille après avoir vérifié certaines conditions et constaté que les conditions ne sont pas remplies. Mais parfois
Cependant, le processus commencera à dormir une fois que la condition de jugement sera vraie. Si tel est le cas, le processus dormira indéfiniment. C'est ce qu'on appelle le problème de réveil invalide. Dans le système d'exploitation, lorsque plusieurs processus tentent d'effectuer un traitement sur des données partagées et que le résultat final dépend de l'ordre dans lequel les processus sont exécutés, une condition de concurrence critique se produit. Il s'agit d'un problème typique dans le système d'exploitation. up C'est précisément à cause des conditions de concurrence.
Imaginez qu'il y ait deux processus A et B. Le processus A traite une liste chaînée. Il doit vérifier si la liste chaînée est vide. Si elle n'est pas vide, il supprimera le lien
.
Les données du tableau subissent certaines opérations et le processus B ajoute également des nœuds à la liste chaînée. Lorsque la liste chaînée est vide, car il n'y a pas de données à exploiter, le processus A se met en veille. Lorsque le processus B ajoute un nœud à la liste chaînée, il réveille le processus A. Le code est le suivant :
Un processus :
1 spin_lock(&list_lock);
2 si(list_empty(&list_head)) {
3 spin_unlock(&list_lock);
4 set_current_state(TASK_INTERRUPTIBLE);
5 horaire();
6 spin_lock(&list_lock);
7}
8
9 /* Reste du code ... */
10 spin_unlock(&list_lock);
Processus B :
100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);
Il y aura un problème ici. Si le processus A est exécuté après la ligne 3 et avant la ligne 4, le processus B est planifié par un autre processeur
mettre en service. Au cours de cette tranche de temps, le processus B a terminé toutes ses instructions et tente donc de réveiller le processus A. À ce stade, le processus A n'est pas encore entré en veille, l'opération de réveil n'est donc pas valide. Après cela, le processus A continue de s'exécuter. Il pensera à tort que la liste chaînée est encore vide à ce moment-là, il définit donc son statut sur TASK_INTERRUPTIBLE puis appelle planning() pour se mettre en veille. Parce que le processus B a manqué l'appel de réveil, il dormira indéfiniment. C'est le problème du réveil invalide, car le processus A dort toujours même s'il y a des données dans la liste chaînée qui doivent être traitées.
Comment éviter les problèmes de réveil invalide ? Nous avons constaté que les réveils invalides se produisent principalement après avoir vérifié les conditions et que l'état du processus est mis en veille
Avant l'état, wake_up_process() du processus B offrait à l'origine la possibilité de définir l'état du processus A sur TASK_RUNNING. Malheureusement, l'état du processus A était encore TASK_RUNNING à ce moment-là, donc wake_up_process() a changé l'état du processus A de veille. état à l’état de fonctionnement. L’effort n’a pas eu l’effet escompté. Pour résoudre ce problème, un mécanisme de garantie doit être utilisé pour faire en sorte que la détermination du fait que la liste chaînée soit vide et la mise en veille de l'état du processus deviennent une étape intégrante, c'est-à-dire que la source de la condition de concurrence critique doit être éliminée, de sorte que le wake_up_process. ( ) peut jouer le rôle de réveiller un processus qui est en état de veille.
Après avoir trouvé la raison, reconcevez la structure du code du processus A pour éviter le problème de réveil invalide dans l'exemple ci-dessus.
Un processus :
1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3 si(list_empty(&list_head)) {
4 spin_unlock(&list_lock);
5 horaire();
6 spin_lock(&list_lock);
7}
8 set_current_state(TASK_RUNNING);
9
10 /* Reste du code ... */
11 spin_unlock(&list_lock);
Comme vous pouvez le voir, ce code définit l'état actuel du processus d'exécution sur TASK_INTERRUPTIBLE avant de tester les conditions, et se définit sur l'état TASK_RUNNING lorsque la liste chaînée n'est pas vide. De cette façon, si le processus B est dans le processus A, contrôlez le processus
Après avoir appelé wake_up_process() une fois que la liste chaînée est vide, l'état du processus A changera automatiquement par rapport au TASK_INTERRUPTIBLE
d'origine.
Il devient TASK_RUNNING. Après cela, même si le processus appelle à nouveau planning(), puisque son état actuel est TASK_RUNNING, il ne sera toujours pas supprimé de la file d'attente d'exécution, il n'entrera donc pas en veille par erreur, et bien sûr, le problème. de réveil invalide sera évité.
Dans le système d'exploitation Linux, la stabilité du noyau est cruciale afin d'éviter des problèmes de réveil invalide dans le noyau du système d'exploitation Linux,
.
Le noyau Linux doit utiliser des opérations similaires aux suivantes lorsqu'il doit mettre un processus en veille :
/* 'q' est la file d'attente dans laquelle nous voulons dormir */
DECLARE_WAITQUEUE(attendre,actuel);
add_wait_queue(q, &attendre);
set_current_state(TASK_INTERRUPTIBLE);
/* ou TASK_INTERRUPTIBLE */
while(!condition) /* 'condition' est la condition d'attente*/
horaire();
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);
L'opération ci-dessus permet au processus de s'ajouter en toute sécurité à une file d'attente pour la mise en veille en suivant la série d'étapes suivantes : Ajustez d'abord
Utilisez DECLARE_WAITQUEUE () pour créer un élément de file d'attente, puis appelez add_wait_queue() pour vous ajouter à la file d'attente et définissez l'état du processus sur TASK_INTERRUPTIBLE ou TASK_INTERRUPTIBLE. Ensuite, la boucle vérifie si la condition est vraie : si c'est le cas, il n'est pas nécessaire de dormir, si la condition n'est pas vraie, planning() est appelé. Lorsque les conditions vérifiées par le processus sont remplies, le processus se définit sur TASK_RUNNING et appelle remove_wait_queue() pour se retirer de la file d'attente.
Comme vous pouvez le voir ci-dessus, le responsable du code du noyau Linux définit également l'état du processus en état de veille avant que le processus ne vérifie les conditions,
Ensuite, la boucle vérifie la condition. Si la condition est remplie avant que le processus ne commence à dormir, alors la boucle se terminera et utilisera set_current_state() pour définir son état sur prêt. Cela garantit également que le processus n'aura pas tendance à entrer en veille par erreur, et bien sûr. ne provoquera pas d’erreur.
Utilisons un exemple dans le noyau Linux pour voir comment le noyau Linux évite la veille invalide. Ce code provient du noyau Linux2.6 (linux-2.6.11/kernel/sched.c : 4254) :
4253 /* Attendez kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 tandis que (!kthread_should_stop()) {
4256 horaire();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258}
4259 __set_current_state(TASK_RUNNING);
4260 retour 0 ;
Les codes ci-dessus appartiennent au thread du service de migration migration_thread. Ce fil vérifie constamment kthread_should_stop(),
.
Il ne peut pas quitter la boucle jusqu'à ce que kthread_should_stop() renvoie 1, ce qui signifie que le processus restera en veille tant que kthread_should_stop() renvoie 0. Nous pouvons voir dans le code que la vérification kthread_should_stop() est effectivement exécutée une fois que l'état du processus est défini sur TASK_INTERRUPTIBLE. Par conséquent, si un autre processus tente de le réveiller après la vérification des conditions mais avant planning(), l'opération de réveil du processus ne sera pas invalidée.
Grâce à la discussion ci-dessus, nous pouvons constater que la clé pour éviter un réveil invalide du processus sous Linux est de réveiller le processus avant qu'il ne vérifie la condition
Le statut est défini sur TASK_INTERRUPTIBLE ou TASK_UNINTERRUPTIBLE, et si les conditions cochées sont remplies, il devrait
Réinitialisez son statut à TASK_RUNNING. De cette manière, que les conditions d'attente du processus soient remplies ou non, le processus n'entrera pas en état de veille par erreur car il est retiré de la file d'attente prête, évitant ainsi le problème d'un réveil invalide.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!