C mini shell,处理SIGCHLD的问题

时间:2014-02-11 17:17:04

标签: c shell process sigchld

所以我正在实现一个迷你C shell,它支持后台进程。我的想法是,对于后台模式,父进程不等待其子进程完成,而是将它们注册到作业列表中,当它们完成时,我捕获SIGCHLD sig以清空其在我的作业列表上的条目。这是代码。

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <wait.h>

#define DEFAULT_PROMPT "\nLog710H2014%>"
#define EXIT_CMD "exit"
#define CD_CMD "cd"
#define JOB_LIST_CMD "aptaches"
#define HOME_ENV_VAR "HOME"
#define NEW_LINE "\n**************************************************\n"
#define BCG_CMD_FLAG "&"

void cd_handler(int argc, char *argv[]);
int lire(char *chaine, int longueur);
char** init_command(int* size,char *str);
int execProg(int *argc, char **argv);
int execProgBg(int *argc, char **argv);
void sigchldHandler(int sig_num);
void aptachesHandler();
void cleanJobList(pid_t *childpid);

struct beanProcess {
    pid_t pid;
    int job_num;
    char *command;
};
void ajoutProcess(struct beanProcess bp);


struct beanProcess beans[20];
int jobCount = 1;

int main() {
    signal(SIGCHLD, sigchldHandler);

    printf(NEW_LINE);
    printf("Bienvenue sur le shell de l'equipe 1");
    printf(NEW_LINE);

    while(1){
        char str[200]="";
        printf(DEFAULT_PROMPT);

        lire(str, 200);

        int commArgsC = 0, bg = 0;
        char** comms  = init_command(&commArgsC, str);

        if(commArgsC == 0){
            //printf("Saisie vide, veuillez entrez une commande.");
            continue;
        }

        if(strcmp(comms[commArgsC-1], BCG_CMD_FLAG) == 0){
            bg = 1;
            comms[commArgsC-1] = 0;
        }

        if(strcmp(comms[0], CD_CMD) == 0){
            cd_handler(commArgsC, comms);
            commArgsC = commArgsC -1;
        }

        else if (strcmp(comms[0], JOB_LIST_CMD) == 0){
            aptachesHandler();
        }

        else if (strcmp(comms[0], EXIT_CMD) == 0){
            int beansVide = 1;
            for(int i = 0; i < jobCount -1 ; i++){
                if(beans[i].pid != 0){
                    beansVide = 0;
                }
            }
            if(beansVide){
                exit(0);
            }else{
                printf("\nImpossible d'arreter le programme, des processus sont encore en cours d'éxécution\n");
            }
        }
        else {
            if(bg){
                execProgBg(&commArgsC, comms);
            }
            else{
                execProg(&commArgsC, comms);
            }
        }
    }
    return 0;
}
void cd_handler(int argc, char *argv[]){
    char buff[512];
    char * directory;

    if(argc < 2){
        directory  = getenv(HOME_ENV_VAR);
    }else if (argc == 2){
        directory = argv[1];
    }else{
        exit(1);
    }

    if (chdir(directory) == -1) {
        printf ("Erreur de changement de repertoire actif", strerror (errno));
    }else{
        if (getcwd(buff, sizeof(buff)) == NULL)
            perror("Impossible d'afficher le repertoire courant");
        else
            printf("le repertoire courant est: %s\n", buff);
    }
}
//Cette fonction est adaptée a partir du code de refp sur http://stackoverflow.com/questions/11198604/c-split-string-into-an-array-of-strings
char** init_command(int* size, char* str){
    char ** res  = NULL;
    char *  p    = strtok (str, " ");
    int n_spaces = 0;

    while (p) {
        res = realloc (res, sizeof (char*) * ++n_spaces);

        if (res == NULL){
            exit (-1);
        }
        res[n_spaces-1] = p;
        p = strtok (NULL, " ");
    }
    res = realloc (res, sizeof (char*) * (n_spaces+1));
    res[n_spaces] = 0;
    *size = n_spaces;
    return res;
}
//cette fonction est tirée d'un exemple de http://fr.openclassrooms.com/informatique/cours/apprenez-a-programmer-en-c/recuperer-une-chaine-de-caracteres
int lire(char *chaine, int longueur)
{
    char *positionEntree = NULL;
    //printf ("\nje suis avant fgets et char est %s", chaine);
    if (fgets(chaine, longueur, stdin) != NULL)
    {
        //printf ("\nje suis apres fgets");
        positionEntree = strchr(chaine, '\n');
        if (positionEntree != NULL)
        {
            *positionEntree = '\0';
        }
        return 1;
    }
    else
    {
        return 0;
    }
}
int execProg(int *argc, char **argv){
    char path[30] = "/bin/";
    strcat(path,argv[0]);
    //printf("\nThis is the %d process executing the code in non bg mode\n", getpid());
    printf("Voici le resultat de l'execution de votre commande\n");
    pid_t  pid;
    pid = fork();

    if (pid < 0) {
        perror("Creation de processus avec fork echouee");
        exit(-1);
    }
    else if (pid == 0) {
        if(execvp(argv[0], argv) == -1){
            //printf("\nthis is the child process %d executing the command in non bg mode\n", getpid());
            perror("execv");
            return EXIT_FAILURE;
        }
    }
    else {
        //printf("\nthis is the parent process %d showing the stats in non bg mode\n", getpid());
        struct rusage rusg;
        long temp, tempCpu;
        wait (NULL);
        getrusage(RUSAGE_CHILDREN, &rusg);
        printf("\nStatistique de la commande %s:\n", argv[0]);

        temp = (rusg.ru_utime.tv_sec * 1000) + (rusg.ru_utime.tv_usec / 1000);
        tempCpu = (rusg.ru_stime.tv_sec * 1000) + (rusg.ru_stime.tv_usec / 1000);

        printf("\nLe temps wall-clock (ms): %ld", temp);
        printf("\nLe temps CPU (ms) %ld", tempCpu);
        printf("\nNB interruptions volontaires: %ld", rusg.ru_nvcsw);
        printf("\nNB interruptions involontaires: %ld", rusg.ru_nivcsw);
        printf("\nNB defaults de pages: %ld", rusg.ru_majflt);
        printf("\nNB defaults de pages satifaits du noyau : %ld", rusg.ru_minflt);
    }
    return EXIT_SUCCESS;
}
int execProgBg(int *argc, char **argv){

    //printf("\nThis is the %d process executing the code in  bg mode\n", getpid());

    pid_t  pid;
    pid = fork();

    if (pid < 0) {
        perror("Creation de processus avec fork echouee");
        return EXIT_FAILURE;
    }
    else if (pid == 0) {
        //printf("This is the pid %d", getpid());
        //printf("\nthis is the child process %d executing the command in  bg mode\n", getpid());

        if(execvp(argv[0], argv) == -1){
            perror("execvp");
            return EXIT_FAILURE;
        }
    }
    else {
        //printf("\nthis is the parent process %d showing the queue in bg mode\n", getpid());

        printf("[%d] %d", jobCount, pid);

        struct beanProcess bP;
        bP.pid = pid;
        bP.job_num = jobCount;
        bP.command = argv[0];

        ajoutProcess(bP);
    }
    return EXIT_SUCCESS;
}
void sigchldHandler(int sig_num)
{
    int status;
    pid_t childPid;
    childPid = waitpid(-1, &status, WNOHANG);
    cleanJobList(&childPid);
}
void ajoutProcess(struct beanProcess bP){
    beans[jobCount-1] = bP;
    jobCount++;
}
void aptachesHandler(){
    for(int i = 0; i < jobCount-1 ; i++){
        printf("[%d] %d %s\n", beans[i].job_num, beans[i].pid, beans[i].command) ;

    }
}
void cleanJobList(pid_t *childpid){
    printf("clean performed on %d", *childpid);
    for(int i = 0; i < jobCount-1 ; i++){
        if(beans[i].pid == *childpid){
            beans[i].pid = 0;
            beans[i].job_num = 0;
            beans[i].command = NULL;
        }
    }
}

使用这段代码我有两个问题,首先sigchldHandler工作并清理列表只有当BG命令(让我们说“ls&amp;”)是第一个被执行的命令时。第二个问题是,使用我的aptaches命令(相当于shell作业),所有进程的命令名字符串始终采用最后一个命令值,为什么会这样?这是一个执行样本,以便您了解我在说什么。

**************************************************
Bienvenue sur le shell de l'equipe 1
**************************************************

Log710H2014%>ls &
[1] 10466
Log710H2014%>Debug  PARTIE3.c
clean performed on 10466
Log710H2014%>pwd &
[2] 10467
Log710H2014%>/home/shong/workspace/TP1_PARTIE_3


Log710H2014%>aptaches
[0] 0 (null)
[2] 10467 aptaches

Log710H2014%>

另一个:

**************************************************
Bienvenue sur le shell de l'equipe 1
**************************************************

Log710H2014%>ls
Voici le resultat de l'execution de votre commande
Debug  PARTIE3.c
clean performed on -1
Statistique de la commande ls:

Le temps wall-clock (ms): 1
Le temps CPU (ms) 0
NB interruptions volontaires: 1
NB interruptions involontaires: 3
NB defaults de pages: 0
NB defaults de pages satifaits du noyau : 315
Log710H2014%> ls &
[1] 10483
Log710H2014%>Debug  PARTIE3.c


Log710H2014%>pwd &
[2] 10484
Log710H2014%>/home/shong/workspace/TP1_PARTIE_3


Log710H2014%>aptaches
[1] 10483 ptaches
[2] 10484 aptaches

Log710H2014%>

1 个答案:

答案 0 :(得分:1)

在前台或后台运行命令并没有什么不同 - 实际上,唯一的区别是在一种情况下,在重新显示提示之前等待程序退出。

子程序可以以任何顺序终止,例如:

sleep 1 &
sleep 2

使用你的代码,当你执行“sleep 2”时,wait(NULL)调用实际上会接收退出的“sleep 1”,并丢弃该信息,不会为信号处理程序做任何事情。< / p>

此外,signal(2)联机帮助页指出信号处理程序在接收到信号时会自动重置,除非使用信号的BSD语义。您可能需要阅读该手册页的“可移植性”部分。

命令名称的问题只是您正在重用存储命令名称的缓冲区。您可能希望使用strdup复制命令名称,free在作业退出时回收副本的内存。