Table des matières
Comment un processus enfant hérite-t-il des données du processus parent
1. Copier la séparation au moment de la création
Que fait le système d'exploitation lorsqu'un processus se termine ?
Méthodes courantes de terminaison de processus
Le code s'exécute et le résultat est correct
Le code s'exécute jusqu'à la fin avec des résultats incorrects
Comment terminer un processus avec du code
création de processus
fonction fourche()
J'ai déjà expliqué en détail l'utilisation de cette fonction dans l'article précédent, donc cet article se penche principalement sur fork() d'un point de vue plus approfondi .
La fonction fork est une fonction très importante sous Linux, qui crée un nouveau processus à partir d'un processus existant. Le nouveau processus est le processus enfant et le processus d'origine est le processus parent.
#include <unistd.h>
pid_t fork(void);
valeur de retour : renvoie 0 dans le processus enfant, renvoie l'identifiant du processus enfant dans le processus parent, renvoie -1 en cas d'erreur
Nous allons maintenant expliquer d'un point de vue plus systématique :
Veuillez décrire, que fait le système d'exploitation lorsque fork() crée un processus enfant ?
1. Tout d'abord, le point de départ de cette question est : fork() crée un processus enfant, et il y aura un processus de plus dans le système !
2. Ensuite, répondez quel est le processus :
Processus = structure de données du noyau (OS) + code de processus et données (généralement à partir du disque, c'est-à-dire le résultat du chargement du programme C/C++)
3. Après cela, les opérations suivantes seront effectuées sur le processus enfant :
a. Allouer de nouveaux blocs de mémoire et structures de données au processus enfant
b. Copier une partie du contenu de la structure de données du processus parent vers le processus enfant
c. Ajouter des processus enfants à la liste des processus système
d.fork() renvoie et démarre l'appel du planificateur.
Comment un processus enfant hérite-t-il des données du processus parent
La création d'un processus enfant et l'attribution de la structure de noyau correspondante au processus enfant doivent être uniques au processus enfant, car le processus est indépendant. En théorie, les sous-processus devraient également avoir leur propre code et leurs propres données !
Il se peut qu'en général, le processus enfant n'ait pas de processus chargé, c'est-à-dire que le processus enfant n'ait pas son propre code et ses propres données, donc le processus enfant ne peut utiliser que "le code et les données du processus parent"
C'est faux, cela ne veut-il pas dire que le processus est indépendant, comment utiliser le code et les données du processus parent ?
Sont également discutés au cas par cas :
Code : ne peut pas être réécrit, peut seulement être lu , donc père et fils partagent, pas de problème !
Données : susceptibles d'être modifiées, elles doivent donc être détachées !
Alors pour les données , comment les séparer ?
1. Copier la séparation au moment de la création
Examinons d'abord la première méthode : lors de la création d'un processus enfant, il suffit de le copier et de le séparer directement, alors quel est le problème ?
1. Lorsque nous créons un processus enfant, pouvons-nous l'exécuter immédiatement ? C'est l'un d'eux.
2. Même si cela fonctionne tout de suite, aurez-vous accès à toutes les données ? C'est le deuxième.
3. Même si vous pouvez accéder à toutes les données, votre accès à toutes les données est-il écrit ? Si ce n'est pas écrit, il n'est pas du tout nécessaire de copier.
Le problème est donc qu'il est possible de copier l'espace de données que le processus fils n'utilisera pas du tout, même s'il est utilisé, il ne peut être qu'en lecture !
Cette situation est également bien illustrée :
Deux constantes de chaîne différentes, lorsque nous imprimons les adresses, nous constatons que leurs adresses sont les mêmes !
Parce que le compilateur sait que le contenu de cette variable const modifiée ne peut pas être modifié, les variables suivantes avec le même contenu pointeront directement vers elle.
C'est juste pour dire à tout le monde : quand le compilateur compile le programme, il sait économiser de l'espace. De plus, ce type d'interface système qui utilise directement la mémoire accordera plus d'attention aux fichiers .
Par conséquent, la création d'un processus enfant n'a pas besoin de copier les données qui ne seront pas accessibles ou uniquement lues.
Mais voici la question, quel type de données vaut la peine d'être copié, et quel type de données doit être copié ?
Il doit s'agir de données pouvant être écrites par le processus parent ou le processus enfant à l'avenir .
Cependant, d'une manière générale, même le système d'exploitation ne peut pas savoir à l'avance quels espaces peuvent être écrits.
Mais même si vous le savez, copiez-le à l'avance, l'utiliserez-vous immédiatement ?
La réponse est qu'il y a tellement de données qui peuvent être écrites, certainement pas, mais l'espace vous a été donné, mais vous ne l'utilisez pas, donc cela cause un gaspillage d'espace.
OS a donc choisi une technologie : la copie sur écriture pour séparer les données des processus parents et enfants.
2. Copie sur écriture★
Ainsi, combiné avec ce qui précède, la copie sur écriture signifie que lorsque vous avez besoin de données pouvant être écrites , le système d'exploitation vous donnera l'espace correspondant.
Pourquoi le système d'exploitation adopte la technologie de copie sur écriture pour séparer les données de processus parent et enfant :
1. Lorsqu'il est utilisé, il est à nouveau alloué, ce qui est une manifestation d'une utilisation efficace de la mémoire.
2. Le système d'exploitation ne peut pas prédire quels espaces seront accessibles avant l'exécution du code.
Voici un autre problème :
Ensuite, le code avant le processus parent fork(), le processus enfant est-il partagé ?
La réponse est partagée et le processus enfant partage tout le code avant le processus parent fork().
Une image sera expliquée ci-dessous. J'ai également décrit cette image en détail dans l'exécution simultanée du concept de processus et de l'état .
Autrement dit, il y a l'adresse de la ligne de code suivante dans EIP, et ces données stockées sont appelées données de contexte.
Lorsque le processus enfant hérite du processus parent, une copie sur écriture se produit et les données de contexte du processus parent sont copiées dans le processus enfant.
Bien que les processus parent et enfant soient programmés séparément plus tard, les codes sont différents, et chacun modifiera l'EIP, mais ce n'est plus important, car le processus enfant pense déjà que la valeur initiale de son propre code EIP est le code après fork ().
Ainsi, bien que le processus enfant s'exécute après fork(), cela ne signifie pas que le processus enfant de code avant fork() ne peut pas le voir !
processus terminé
Que fait le système d'exploitation lorsqu'un processus se termine ?
Nous savons qu'un programme devient un processus lorsqu'il s'exécute, et un processus est composé de code de processus et de données + structure de données du noyau .
Par conséquent, la fin du processus consiste à libérer la structure de données du noyau et les données et codes correspondants appliqués par le processus.L'essentiel est de libérer les ressources système.
Méthodes courantes de terminaison de processus
Le code s'exécute et le résultat est correct
Cette situation est très courante.Lorsque nous écrivons des questions algorithmiques, comme Lituo, si le résultat d'une question est correct, il sera affiché comme réussi après la soumission finale.
Ou écrivez-le vous-même sous le compilateur, et la sortie finale est également conforme à nos résultats attendus, etc.
Je me demande si nous avons remarqué que la fonction main a une valeur de retour, return 0, quelle est la signification de sa valeur de retour, et pourquoi elle retourne toujours 0 ?
En fait, la valeur de retour de la fonction main n'est pas toujours 0, mais on écrit souvent 0 quand on écrit habituellement.
La valeur retournée à la fin de la fonction principale est appelée le code de sortie du processus.
Généralement, 0 représente le succès, indiquant que le résultat du processus en cours d'exécution est correct.
Le résultat de l'opération du drapeau non nul est incorrect, ce qui sera décrit en détail ultérieurement.
Par exemple, nous renvoyons un 10.
Ensuite, nous utilisons $? pour afficher le code de sortie du processus le plus récent.
On peut constater qu'à la première exécution, le code de sortie obtenu est le 10 le plus récemment renvoyé.
La raison pour laquelle il vaut 0 pour la deuxième fois est que le dernier echo $? est également un processus, il a été exécuté avec succès, il renvoie donc 0.
Quel est donc le sens du retour de la fonction main ?
Utilisé pour revenir au processus de niveau supérieur, utilisé pour juger du résultat d'exécution du processus, peut être ignoré.
Par exemple, si nous écrivons un programme d'addition et de sommation, si la réponse est correcte, il renvoie 0, et si la réponse est fausse, il renvoie 1.
À ce stade, notre somme est le processus correct, donc le résultat devrait retourner 0
Et lorsque nous écrivons mal la logique de calcul de sum et ne parvenons pas à obtenir le résultat correct, il renverra 1, indiquant que le résultat final du programme est incorrect, ce qui est également le sens de la valeur de retour de la fonction principale.
code de sortie★
Pour en revenir au code de sortie non nul que nous venons de mentionner, il existe d'innombrables valeurs non nulles, qui peuvent être utilisées pour identifier différentes causes d'erreur.
Après la fin de notre programme, il est pratique de localiser les détails de la cause de l'erreur.
Ces raisons sont également définies sous linux, nous pouvons imprimer pour voir, ici nous devons utiliser une fonction strerror()
Ce qu'il fait est de renvoyer une description de chaîne du code d'erreur.
Nous entrons le code:
Sortez ensuite le résultat :
Jusqu'à 100, nous avons constaté qu'il existe de nombreux types d'erreurs.
Bien sûr, nous pouvons utiliser nous-mêmes ces codes de sortie et leurs significations, mais si vous souhaitez le définir vous-même, vous pouvez également concevoir vous-même un ensemble de schémas de sortie.
Le code s'exécute jusqu'à la fin avec des résultats incorrects
Idem.
Terminaison anormale du code
Lorsque le programme se termine anormalement, le code de sortie n'a pas de sens, d'une manière générale, l'instruction return correspondant au code de sortie n'est pas exécutée !
Alors pourquoi le programme plante-t-il ?
Comment terminer un processus avec du code
retour
Tout d'abord, nous savons que le code de sortie de retour termine le processus. Bien sûr, seule l'instruction return dans la fonction main termine le processus.
quitter et _exit★
Voyons d'abord l'introduction
Voyez ce que cela fait, c'est laisser le processus se terminer gracieusement.
Ensuite, le paramètre de la fonction est status, qui est utilisé pour identifier le code de sortie.
Elle est différente de la fonction main : la fonction exit est appelée n'importe où, et le processus se termine directement !
Voir l'exemple suivant :
S'il renvoie 200, il ne renverra que 200 à a et le programme ne se terminera pas.
On peut voir que l'instruction world de sortie après func() n'est pas exécutée et que le code de sortie est 200 pour la première fois.
Alors, qu'est-ce que _exit et quelle est la différence ?
Il décrit beaucoup de choses et est assez abstrait. Je vais utiliser un exemple et une image pour expliquer la différence entre it et exit.
Tout d'abord, l'exemple de tout à l'heure, nous changeons exit en _exit.
On peut constater que le résultat n'est pas différent de exit :
Mais nous changeons le programme en quelque chose comme ceci :
Pour le moment, nous nous attendons à ce que printf n'ait pas le caractère de nouvelle ligne '\n' pour actualiser le tampon, le contenu est stocké dans le tampon à ce moment, donc le programme affichera le contenu à l'écran après 1 seconde d'exécution , puis le code de sortie renvoie 111.
Comme il s'agit d'une image statique, l'effet ne peut pas être démontré, mais le résultat s'affiche après 1 seconde.
Mais si nous changeons exit en _exit.
Mais j'ai constaté que rien n'était sorti.
Voici la conclusion directement, exit rafraîchira le contenu du tampon à l'écran à la fin du programme, et _exit se terminera directement sans rafraîchir le contenu du tampon.
Comme le montre la figure suivante
Mais nous recommandons généralement d'utiliser exit.
Nous savons que la fonction de bibliothèque est une interface système encapsulée, et que la fonction de bibliothèque à l'intérieur est en fait exit, et que l'interface système est _exit.
Et où se trouve le buffer dont on parle habituellement, j'en reparlerai plus tard, mais il ne doit pas être à l'intérieur du système d'exploitation !
S'il se trouve à l'intérieur du système d'exploitation et est maintenu par le système d'exploitation, alors _exit peut toujours être actualisé, mais il ne peut pas être actualisé, indiquant qu'il ne doit pas être à l'intérieur du système d'exploitation, mais fourni par la bibliothèque de langage C.
Voilà pour la création et la terminaison de processus.