Comment utilisez-vous dup2 et fork ensemble?
Question
Je suis un cours sur les systèmes d'exploitation et j'ai du mal à rediriger les entrées avec dup2 lorsque vous avez des fourches. J'ai écrit ce petit programme pour essayer d'en comprendre le sens, mais je n'ai pas réussi à transmettre la sortie d'un petit-enfant à un enfant. J'essaye d'imiter la commande unix: ps -A | wc -l. Je suis nouveau sous Unix, mais je pense que cela devrait compter les lignes de la liste des processus en cours d'exécution que j'obtiens. Ma sortie devrait donc être un seul nombre.
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main( int argc, char *argv[] ) {
char *searchArg = argv[ 1 ];
pid_t pid;
if ( ( pid = fork() ) > 0 ) {
wait( NULL );
cout << "Inside parent" << endl;
}
else if ( pid == 0 ) {
int fd1[ 2 ];
pipe( fd1 );
cout << "Inside child" << endl;
if ( pid = fork() > 0 ) {
dup2( fd1[ 0 ], 0 );
close( fd1[ 0 ] );
execlp( "/bin/wc", "-l", NULL );
}
else if ( pid == 0 ) {
cout << "Inside grand child" << endl;
execlp( "/bin/ps", "-A", NULL );
}
}
return 0;
}
Je ne l'ai pas dans le code ci-dessus, mais voici mon estimation de la façon dont les choses devraient se passer:
- Nous devons rediriger la sortie standard de la commande ps -A (tout ce qui est généralement affiché à l'écran, n'est-ce pas?) afin que la commande wc -l puisse l'utiliser pour compter les lignes.
- Cette sortie standard peut être redirigée en utilisant dup2, comme dup2 (?, 1) ce qui signifie rediriger la sortie standard vers?. Alors vous fermez?.
Question: Où dois-je le rediriger? Je sais qu'il devrait être l'un des descripteurs de fichier, mais où doit-il être redirigé pour que wc puisse le traiter?
- wc reçoit en quelque sorte la sortie standard.
Question: Comment wc reçoit-il la sortie? Via un paramètre execlp? Ou le système d'exploitation vérifie-t-il l'un des descripteurs de fichier?
- Exécutez wc -l.
Lequel de ces éléments est fermé et laissé ouvert pour que wc reçoive et traite la sortie de ps? Je n'arrête pas de penser que cela doit être repensé à l'envers car ps doit donner sa sortie à wc ... mais cela ne semble pas logique puisque l'enfant et le petit-enfant sont traités en parallèle.
La solution
Tout d'abord, corrigeons votre code pour y ajouter un tout petit peu plus de vérification des erreurs, et pour qu'il fonctionne; remplacez le bit du bas par:
else if ( pid == 0 ) {
int fd1[ 2 ];
pipe( fd1 );
cout << "Inside child" << endl;
if ( pid = fork() > 0 ) {
if (dup2( fd1[ 0 ] , 0 ) < 0) {
cerr << "Err dup2 in child" << endl;
}
close( fd1[ 0 ] );
close( fd1[ 1 ] ); // important; see below
// Note: /usr/bin, not /bin
execlp( "/usr/bin/wc", "wc", "-l", NULL );
cerr << "Err execing in child" << endl;
}
else if ( pid == 0 ) {
cout << "Inside grand child" << endl;
if (dup2( fd1[ 1 ] , 1 ) < 0) {
cerr << "Err dup2 in gchild" << endl;
}
close( fd1[ 0 ] );
close( fd1[ 1 ] );
execlp( "/bin/ps", "ps", "-A", NULL );
cerr << "Err execing in grandchild" << endl;
}
}
Maintenant, vos questions:
-
Question: Où dois-je le rediriger? Je sais qu'il devrait être l'un des descripteurs de fichier, mais où doit-il être redirigé pour que wc puisse le traiter?
Les descripteurs de fichier
0
,1
et2
sont spéciaux sous Unix en ce qu'ils sont l'entrée standard, la sortie standard et l'erreur standard.wc
lit à partir de l'entrée standard, donc tout ce qui estdup
en0
. -
Question: Comment wc reçoit-il la sortie? Via un paramètre execlp? Ou le système d'exploitation vérifie-t-il l'un des descripteurs de fichier?
En général, après qu'un processus a eu son image échangée avec
exec
, il aura tous les descripteurs de fichier ouverts qu'il avait avantexec
. (Sauf pour les descripteurs avec l'indicateurCLOSE_ON_EXEC
défini, mais ignorez cela pour le moment) Par conséquent, si vous générez un code générique pourdup2
, alors0
le lira. -
Lequel de ces éléments est fermé et laissé ouvert pour que wc reçoive et traite la sortie de ps?
Comme indiqué ci-dessus, vous pouvez fermer les deux extrémités du tuyau chez l'enfant et le petit-enfant, et ce sera très bien. En fait, la pratique courante vous recommande de le faire. Cependant, la seule ligne
wc
vraiment nécessaire dans cet exemple spécifique est celle que je commente comme "importante" - qui ferme l'extrémité write du tube dans le fils.L'idée est la suivante: l'enfant et le petit-enfant ont les deux extrémités du tuyau ouvertes lorsqu'ils démarrent. Maintenant, via
close
, nous avons connectédup
à l'extrémité de lecture du tube.wc
va continuer à sucer ce tube jusqu'à ce que tous les descripteurs à l'extrémité d'écriture du tube soient fermés, à quel point il verra qu'il est arrivé à la fin du fichier et s'arrêtera. Maintenant, chez le petit-enfant, nous pouvons nous en sortir sans rien fermer carwc
ne va rien faire avec aucun des descripteurs mais écrire dans le descripteurps -A
, et après que1
ait fini de cracher des informations sur certains processus, il se terminera , fermant tout ce qu'il avait. Dans l'enfant, nous n'avons pas vraiment besoin de fermer le descripteur de lecture stocké dansps -A
carfd[0]
n'essaiera pas de lire à partir d'autre chose que du descripteurwc
. Cependant, nous devons fermer la fin d'écriture du tube dans le fils, sinon0
va simplement rester là avec un tube qui n'est jamais complètement fermé.Comme vous pouvez le voir, la raison pour laquelle nous n'avons pas vraiment besoin de l'une des lignes
wc
à l'exception de celle marquée "important" dépend des détails du comportement declose
etwc
, donc la pratique standard est de fermez l'extrémité d'un tuyau que vous n'utilisez pas complètement et gardez ouverte l'extrémité que vous utilisez avec un seul descripteur. Puisque vous utilisezps
dans les deux processus, cela signifie quatre instructionsdup2
comme ci-dessus.
EDIT: mise à jour des arguments pour close
.