Question

So i am implementing a mini C shell, it's supporting background processes. My idea was that for the background mode, the parent process doesn't wait for its children process to finish but rather register them in a job list and when they are done, i capture the SIGCHLD sig to empty their entry on my job list. Here's the code.

#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;
        }
    }
}

With this code i have two problems, first the sigchldHandler works and clean the list only if the BG command (let's say "ls &") is the very first one that was executed. second problem is that with my aptaches command (equivalent to shell jobs) the string of the command name for all processes always takes the last command value, why is that ? Here is a sample of execution so that you see what i am talking about.

**************************************************
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%>

and another one:

**************************************************
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%>
Was it helpful?

Solution

Running a command in the foreground or in the background isn't that different -- in fact, the only difference is that in one case you wait for the program to exit before redisplaying the prompt.

Child programs can terminate in any order, e.g.:

sleep 1 &
sleep 2

With your code, when you execute the "sleep 2", the wait(NULL) call will actually pick up the "sleep 1" exiting, and throw away that information, leaving nothing to do for the signal handler.

Also, the signal(2) manpage states that signal handlers are automatically reset when the signal is received, unless BSD semantics for signals are used. You may want to read the "Portability" section of that manual page.

The issue with the command names is simply that you are reusing the buffer where the command name was stored in. You may want to use strdup to make a copy of the command name, and free to reclaim the memory for the copy when the job exits.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top