J'essaie d'implémenter plusieurs canaux dans mon shell en C. J'ai trouvé un tutoriel sur ce website et la fonction que j'ai créée est basée sur cet exemple. Voici la fonction
void executePipes(cmdLine* command, char* userInput) {
int numPipes = 2 * countPipes(userInput);
int status;
int i = 0, j = 0;
int pipefds[numPipes];
for(i = 0; i < (numPipes); i += 2)
pipe(pipefds + i);
while(command != NULL) {
if(fork() == 0){
if(j != 0){
dup2(pipefds[j - 2], 0);
}
if(command->next != NULL){
dup2(pipefds[j + 1], 1);
}
for(i = 0; i < (numPipes); i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
}
else{
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
}
}
Après l’exécution et la saisie d’une commande comme par exemple ls | grep bin
, le shell s’y bloque et ne produit aucun résultat. Je me suis assuré de fermer tous les tuyaux. Mais ça reste juste là. Je pensais que c’était la waitpid
qui était le problème. J'ai enlevé la waitpid
et après exécution je n'obtiens aucun résultat. Qu'ai-je fait de mal? Merci.
Code ajouté:
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0, j = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < 2*(numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
}
while(command) {
pid = fork();
if(pid == 0) {
//if not first command
if(j != 0){
if(dup2(pipefds[(j-1) * 2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
exit(EXIT_FAILURE);
//printf("j != 0 dup(pipefd[%d], 0])\n", j-2);
}
//if not last command
if(command->next){
if(dup2(pipefds[j * 2 + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*numPipes; i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
} else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
command = command->next;
j++;
}
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
puts("closed pipe in parent");
}
while(waitpid(0,0,0) <= 0);
}
}
Je pense que le problème ici est que votre attente et votre fermeture dans la même boucle qui crée des enfants. À la première itération, l’enfant exécutera (ce qui détruira le programme enfant et le remplacera avec votre première commande), puis le parent fermera tous ses descripteurs de fichier et attendra que l’enfant soit terminé avant qu’il ne crée le prochain enfant. . À ce stade, étant donné que le parent a fermé tous ses canaux, tout enfant supplémentaire n'aura plus rien pour écrire ou lire. Puisque vous ne vérifiez pas le succès de vos appels dup2, cela ne se remarque pas.
Si vous souhaitez conserver la même structure de boucle, vous devez vous assurer que le parent ne ferme que les descripteurs de fichier déjà utilisés, mais laisse ceux qui ne sont pas seuls. Ensuite, une fois que tous les enfants ont été créés, votre parent peut attendre.
EDIT: J'ai mélangé le parent/enfant dans ma réponse, mais le raisonnement reste le même: le processus qui va continuer à brancher ferme à nouveau toutes ses copies des tuyaux, donc tout processus après le premier fork n'aura pas de descripteur de fichier valide pour lire/écrire.
pseudo-code, utilisant un tableau de pipes créé en amont:
/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
if( pipe(pipefds + i*2) < 0 ){
perror and exit
}
}
commandc = 0
while( command ){
pid = fork()
if( pid == 0 ){
/* child gets input from the previous command,
if it's not the first command */
if( not first command ){
if( dup2(pipefds[(commandc-1)*2], 0) < ){
perror and exit
}
}
/* child outputs to next command, if it's not
the last command */
if( not last command ){
if( dup2(pipefds[commandc*2+1], 1) < 0 ){
perror and exit
}
}
close all pipe-fds
execvp
perror and exit
} else if( pid < 0 ){
perror and exit
}
cmd = cmd->next
commandc++
}
/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
close( pipefds[i] );
}
Dans ce code, le processus parent d'origine crée un enfant pour chaque commande et survit donc à l'épreuve entière. Les enfants vérifient s'ils doivent obtenir leur entrée de la commande précédente et s'ils doivent envoyer leur sortie à la commande suivante. Ensuite, ils ferment toutes leurs copies des descripteurs de fichiers de pipe, puis exec. Le parent ne fait rien que bifurquer jusqu'à ce qu'il ait créé un enfant pour chaque commande. Il ferme ensuite toutes les copies des descripteurs et peut attendre.
Tout d'abord, créer tous les canaux dont vous avez besoin, puis les gérer en boucle, est délicat et nécessite quelques calculs arithmétiques. Le but, cependant, ressemble à ceci:
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
En réalisant que, à un moment donné, vous n'avez besoin que de deux ensembles de tuyaux (le tuyau pour la commande précédente et le tuyau pour la commande suivante), cela simplifiera votre code et le rendra un peu plus robuste. Ephemient donne un pseudo-code pour ceci ici . Son code est plus propre, car le parent et l'enfant n'ont pas besoin de boucler inutilement pour fermer les descripteurs de fichiers inutiles et parce que le parent peut facilement fermer ses copies des descripteurs de fichiers immédiatement après le fork.
Remarque: vous devez toujours vérifier les valeurs de retour de pipe, dup2, fork et exec.
EDIT 2 : faute de frappe en pseudo-code. OP: num-pipes serait le nombre de pipes. Par exemple, "ls | grep foo | sort -r" aurait 2 pipes.
Voici le code de fonctionnement correct
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < (numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("couldn't pipe");
exit(EXIT_FAILURE);
}
}
int j = 0;
while(command) {
pid = fork();
if(pid == 0) {
//if not last command
if(command->next){
if(dup2(pipefds[j + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
//if not first command&& j!= 2*numPipes
if(j != 0 ){
if(dup2(pipefds[j-2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*numPipes; i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
} else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
command = command->next;
j+=2;
}
/**Parent closes the pipes and wait for children*/
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
}
for(i = 0; i < numPipes + 1; i++)
wait(&status);
}
Le code pertinent (abrégé) est:
if(fork() == 0){
// do child stuff here
....
}
else{
// do parent stuff here
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
Ce qui signifie que le processus parent (de contrôle) effectue ceci:
Mais cela devrait être quelque chose comme ça:
Partant de l’idée d’utiliser un maximum de deux pipes à un moment donné, évoquée par Christopher Neylan, j’ai construit le pseudocode pour les n-pipes. args est un tableau de pointeurs de caractère de taille 'args_size' qui est une variable globale.
// MULTIPLE PIPES
// Test case: char *args[] = {"ls", "-l", "|", "head", "|", "tail", "-4",
0};// "|", "grep", "Txt", 0};
enum fileEnd{READ, WRITE};
void multiple pipes( char** args){
pid_t cpid;
// declare pipes
int pipeA[2]
int pipeB[2]
// I have done getNumberofpipes
int numPipes = getNumberOfPipes;
int command_num = numPipes+1;
// holds sub array of args
// which is a statement to execute
// for example: cmd = {"ls", "-l", NULL}
char** cmd
// iterate over args
for(i = 0; i < args_size; i++){
//
// strip subarray from main array
// cmd 1 | cmd 2 | cmd3 => cmd
// cmd = {"ls", "-l", NULL}
//Open/reopen one pipe
//if i is even open pipeB
if(i % 2) pipe(pipeB);
//if i is odd open pipeA
else pipe(pipeA);
switch(cpid = fork(){
case -1: error forking
case 0: // child process
childprocess(i);
default: // parent process
parentprocess(i, cpid);
}
}
}
// parent pipes must be closed in parent
void parentprocess(int i, pid_t cpid){
// if first command
if(i == 0)
close(pipeB[WRITE]);
// if last command close WRITE
else if (i == numPipes){
// if i is even close pipeB[WRITE]
// if i is odd close pipeA[WRITE]
}
// otherwise if in middle close READ and WRITE
// for appropriate pipes
// if i is even
close(pipeA[READ])
close(pipeB[WRITE])
// if i is odd
close(pipeB[READ])
close(pipeA[WRITE])
}
int returnvalue, status;
waitpid(cpid, returnvalue, status);
}
void childprocess(int i){
// if in first command
if(i == 0)
dup2(pipeB[WRITE], STDOUT_FILENO);
//if in last command change stdin for
// the necessary pipe. Don't touch stdout -
// stdout goes to Shell
else if( numPipes == i){
// if i is even
dup2(pipeB[READ], STDIN_FILENO)
//if i is odd
dup2(pipeA[READ], STDIN_FILENO);
}
// otherwise, we are in middle command where
// both pipes are used.
else{
// if i is even
dup2(pipeA[READ], STDIN_FILENO)
dupe(pipeB[WRITE], STDOUT_FILENO)
// if i is odd
dup2(pipeB[READ], STDIN_FILENO)
dup2(pipeA[WRITE], STDOUT_FILENO)
}
// execute command for this iteration
// check for errors!!
// The exec() functions only return if an error has occurred. The return value is -1, and errno is set to indicate the error.
if(exec(cmd, cmd) < 0)
printf("Oh dear, something went wrong with read()! %s\n", strerror(errno));
}
}
Fondamentalement, ce que vous voulez faire est une fonction récursive dans laquelle l'enfant exécute la première commande et le parent exécute la deuxième si aucune autre commande n'est laissée ou appelle à nouveau la fonction.