2017-05-17 4 views
0

J'essaie une implémentation de shell C à partir d'un cours ouvert, mais il y a quelque chose d'intrigant sur le comportement de la mise en mémoire tampon de sortie.Comportement de mise en mémoire tampon de sortie étrange dans C

Le code va comme ceci (notez la ligne où j'utilise pid = waitpid (-1, & r, WNOHANG)):

int 
main(void) 
{ 
    static char buf[100]; 
    int fd, r; 
    pid_t pid = 0; 

    // Read and run input commands. 
    while(getcmd(buf, sizeof(buf)) >= 0){ 
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){ 
     buf[strlen(buf)-1] = 0; // chop \n 
     if(chdir(buf+3) < 0) 
     fprintf(stderr, "cannot cd %s\n", buf+3); 
     continue; 
    } 
    if((pid = fork1()) == 0) 
     runcmd(parsecmd(buf)); 
    while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) { 
     if (errno == ECHILD) { 
      break; 
     } 
    } 
    } 
    exit(0); 
} 

La fonction runcmd est comme ceci (notez que dans tuyau de manutention Je crée 2 processus enfants et d'attendre pour eux de mettre fin):

void 
runcmd(struct cmd *cmd) 
{ 
    int p[2], r; 
    struct execcmd *ecmd; 
    struct pipecmd *pcmd; 
    struct redircmd *rcmd; 

    if(cmd == 0) 
    exit(0); 

    switch(cmd->type){ 
    case ' ': 
    ecmd = (struct execcmd*)cmd; 
    if(ecmd->argv[0] == 0) { 
     exit(0); 
    } 
    // Your code here ... 
    // fprintf(stderr, "starting to run cmd: %s\n", ecmd->argv[0]); 
    execvp(ecmd->argv[0], ecmd->argv); 
    fprintf(stderr, "exec error !\n"); 
    exit(-1); 

    break; 

    case '>': 
    case '<': 
    rcmd = (struct redircmd*)cmd; 
    // fprintf(stderr, "starting to run <> cmd: %s\n", rcmd->file); 
    // Your code here ... 
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 
    if (rcmd->type == '<') { 
     // input 
     close(0); 
     if (open(rcmd->file, O_RDONLY, mode) != 0) { 
     fprintf(stderr, "Opening file error !\n"); 
     exit(-1); 
     } 
    } else { 
     // output 
     close(1); 
     if (open(rcmd->file, O_WRONLY|O_CREAT|O_TRUNC, mode) != 1) { 
     fprintf(stderr, "Opening file error !\n"); 
     exit(-1); 
     } 
    } 

    runcmd(rcmd->cmd); 
    break; 

    case '|': 
    pcmd = (struct pipecmd*)cmd; 
    // fprintf(stderr, "starting to run pcmd\n"); 
    // Your code here ... 
    pipe(p); 

    if (fork1() == 0) { 
     // child for read, right side command 
     close(0); 
     if (dup(p[0]) != 0) { 
     fprintf(stderr, "error when dup !\n"); 
     exit(-1); 
     } 
     close(p[0]); 
     close(p[1]); 
     runcmd(pcmd->right); 
     fprintf(stderr, "exec error !\n"); 
    } 
    if (fork1() == 0) { 
     // left side command for writing 
     close(1); 
     if (dup(p[1]) != 1) { 
     fprintf(stderr, "dup error !\n"); 
     exit(-1); 
     } 
     close(p[0]); 
     close(p[1]); 
     runcmd(pcmd->left); 
     fprintf(stderr, "exec error !\n"); 
    } 
    close(p[0]); 
    close(p[1]); 
    int stat; 
    wait(&stat); 
    wait(&stat); 

    break; 

    default: 
    fprintf(stderr, "unknown runcmd\n"); 
    exit(-1); 
    }  
    exit(0); 
} 

la chose est étrange, quand j'execute « ls | sort » dans le terminal, je reçois constamment f Sortie uite

6.828$ ls | sort 
6.828$ a.out 
sh.c 
t.sh 

Cela indique que, avant la prochaine invite de commande « 6828 $ » est imprimé, la sortie du processus enfant est toujours pas rincée à la borne.

Cependant, si je ne me pid = waitpid (-1, & r, WNOHANG)) et utiliser pid = waitpid (-1, & r, 0)) (ou attente()), la sortie serait normal comme:

6.828$ ls | sort 
a.out 
sh.c 
t.sh 

J'ai pensé à la cause du problème depuis longtemps, mais ne suis pas venu avec une raison possible. Quelqu'un peut-il suggérer une raison possible?

Merci beaucoup!

Répondre

1

Ce code n'a pas un comportement bien défini:

while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) { 
    if (errno == ECHILD) { 
     break; 
    } 
} 

Les ruptures de boucle while immédiatement si waitpid retourne -1, ce qui est précisément ce qu'il retourne dans le cas d'une erreur. Donc, si le corps de la boucle est entré, waitpid a renvoyé une valeur non négative: soit 0 - indiquant que l'enfant est toujours en cours d'exécution - soit le pid d'un enfant qui a quitté. Ce ne sont pas des conditions d'erreur, donc la valeur de errno n'est pas significative. Il peut s'agir de ECHILD, auquel cas la boucle se rompra de manière incorrecte.

Vous devez uniquement vérifier la valeur de errno dans les cas où la valeur est significative. Ou, pour être plus précis, citant le Posix standard:

La valeur de errno est définie seulement après un appel à une fonction pour laquelle il est dit explicitement à régler et jusqu'à ce qu'il soit modifié par le prochain appel de fonction ou si l'application lui assigne une valeur. La valeur de errno ne doit être examinée que s'il est indiqué que sa valeur de retour est valide.

Mais je suis perplexe pourquoi vous pensez qu'il est nécessaire d'effectuer une boucle en utilisant WNOHANG. C'est un énorme gaspillage de ressources, puisque votre processus parent exécutera à plusieurs reprises l'appel système jusqu'à ce que l'enfant se termine réellement.Puisque vous avez vraiment l'intention d'attendre jusqu'à ce que l'enfant se termine, il serait beaucoup plus logique d'appeler simplement wait ou de spécifier 0 comme valeur d'indicateur à waitpid.

D'autre part, vous pouvez répéter la wait (ou waitpid) si elle renvoie -1 avec errno ensemble à EINTR. Et si elle renvoie -1 et errno n'est ni EINTR ni ECHILD, alors une erreur matérielle s'est produite que vous pourriez vouloir enregistrer. Mais ce n'est pas lié à votre problème, afaics.