Home > Enterprise >  Making a process run in the background by using fork
Making a process run in the background by using fork

Time:11-01

I am trying to make a simple shell. When I execute a command in the child and wait for it, the code works fine. However, when I want to execute it in the background, the program just hangs there, and the prompt symbol did not show up. What else code should I add to make my child process do its work, while the parent continues on receiving another user command?

int
lsh_launch(char **args)
{

    pid = fork();

    if (pid == 0) {
        // child process
        if (execvp(args[0], args) == -1) {
            perror("lsh");
        }
        exit(0);
    }
    else if (pid < 0) {
        perror("lsh");
    }
    else {
        // parent process
        if (!background) {
            wait(NULL);                 // works fine
        }
        else {
            printf("[PID]: %d\n", pid);
            // after printing the pid, the prompt symbol does not show again
        }
    }

    return 1;
}

My caller function: After lsh_execute, the code will go back to main, and go back to the top of the while loop, and print another command prompt.

int lsh_execute(char **args)
{
  int i;
  if (args[0] == NULL) { 
    // An empty command was entered, return
    return 1;
  }

  //if the command is in my predefined function list, execute
  for (i = 0; i < lsh_num_builtins(); i  ) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      return (*builtin_func[i])(args);
    }
  }
  //if not, go to launch and fork a child and execute
  return lsh_launch(args);
}

Code Link: myshell The problem is when I type "ls &", the program do output a list of file names in the current directory, but it hangs there.

CodePudding user response:

Okay, I've got it working ...

There were a few issues:

  1. After the printf for the prompt, we have to fflush(stdout) to force out the text because there is no newline.
  2. There is no reaping of background children, so they become zombies.
  3. This would interfere with wait(NULL) for the next foreground command. (e.g.) sleep 5 & ; pwd would mess up the wait for the second command.
  4. To fix this, the program should periodically call waitpid(-1,NULL,WNOHANG) to reap the zombie background children.
  5. Also, with background jobs, a previous background job might complete before the next foreground job completes. So, we want to change the foreground from wait to waitpid with the PID of the foreground job.
  6. Doing signal(SIGCHLD,SIG_IGN) prevents wait et. al. from seeing/reaping a background pid.
  7. This would interfere with subsequent wait(NULL) for foreground commands.
  8. The code for the replay command that attaches to the linked list was wrong. The compiler flagged this when compiled with warnings (e.g. -Wall).
  9. A number of compiler warnings about "unused" or "set but not used" variables.
  10. We should fflush both stdout and stderr prior to a fork.

In addition to fixing the above, I add some improvements:

  1. Added "job control" for background jobs to implement reaping of background pids.
  2. Added jobs and wait commands.
  3. Added debug printing with dbgprt.

In the code below, I've used cpp conditionals to denote old vs. new code:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

Note: this can be cleaned up by running the file through unifdef -k


Here is the refactored code. It is annotated with the bugs and fixes:

Edit: Updated to use waitpid instead of wait on foreground jobs.

#ifndef _POSIX_C_SOURCE
#define  _POSIX_C_SOURCE 200809L
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <dirent.h>

#define BUFSIZE 1024
#define TOK_BUFSIZE 64
#define TOK_DELIM " \t\r\n\a"

char *currentdir;
int tokencount;
typedef struct node {
    char str[64];
    struct node *next;
} node;

node *top = NULL;
node *cur = NULL;

#if 1
#ifdef DEBUG
#define dbgprt(_fmt...) \
    do { \
        int sverr = errno; \
        printf(_fmt); \
        errno = sverr; \
    } while (0)
#else
#define dbgprt(_fmt...) \
    do { } while (0)
#endif
#endif

// NOTE/FIX: implement job control
#if 1
struct jobctl {
    int job_jobno;                      // job number
    pid_t job_pid;                      // child pid
    int job_status;                     // child status
    struct jobctl *job_next;            // list link
};
struct jobctl *joblist;                 // list of background jobs
int jobcount;                           // highest job number

// jobreap -- reap all detached/background jobs (children)
void
jobreap(void)
{

    // wait for all background jobs
    while (joblist != NULL) {
        int status;

        // reap any child (non-blocking)
        pid_t pid = waitpid(-1,&status,WNOHANG);

        // more children are pending [but not yet finished]
        if (pid == 0)
            break;

        dbgprt("jobreap: WAITPID pid=%d status=%8.8X\n",pid,status);

        if (pid < 0) {
            printf("jobreap: error -- %s\n",strerror(errno));
            break;
        }

        // find it in our list of detached/background jobs
        struct jobctl *cur;
        struct jobctl *prev = NULL;
        for (cur = joblist;  cur != NULL;  cur = cur->job_next) {
            if (cur->job_pid == pid)
                break;
            prev = cur;
        }

        // reaping child of one of our children (i.e. grandchild) that is _not_
        // in our list
        if (cur == NULL) {
            printf("[grandchild]: pid %d, status %8.8X\n", pid, status);
            continue;
        }

        printf("[background]: pid %d, %8.8X, job %d\n",
            pid, status, cur->job_jobno);

        // we don't do anything with this but ...
        cur->job_status = status;

        // remove our child from list
        if (prev != NULL)
            prev->job_next = cur->job_next;
        else
            joblist = cur->job_next;

        // release storage
        free(cur);

        // reset job count if we've reaped _all_ of our direct children
        if (joblist == NULL)
            jobcount = 0;
    }
}

int
lsh_jobs(char **args)
{

    for (struct jobctl *cur = joblist;  cur != NULL;  cur = cur->job_next)
        printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);

    return 1;
}

int
lsh_wait(char **args)
{

    while (1) {
        jobreap();
        if (joblist == NULL)
            break;
    }

#if 0
    for (struct jobctl *cur = joblist;  cur != NULL;  cur = cur->job_next)
        printf("[%d] pid %d\n",cur->job_jobno,cur->job_pid);
#endif

    return 1;
}

#endif

char *
read_line(void)
{
    int bufsize = BUFSIZ;
    int position = 0;
    char *buffer = (char *) malloc(sizeof(char) * bufsize);
    int c;

    if (!buffer) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    while (1) {
        c = getchar();
        if (c == EOF || c == '\n') {
            buffer[position] = '\0';
            return buffer;
        }
        else {
            buffer[position] = c;
        }
        position  ;

        if (position >= bufsize) {
            bufsize  = BUFSIZ;
            buffer = realloc(buffer, bufsize);
            if (!buffer) {
                fprintf(stderr, "lsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }

}

char **
split_line(char *line)
{
    int bufsize = BUFSIZ;
    int position = 0;
    char **tokens = (char **) malloc(sizeof(char *) * bufsize);
    char *token;

    if (!tokens) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    token = strtok(line, TOK_DELIM);
    while (token != NULL) {
        tokens[position] = token;
        position  ;

        if (position >= bufsize) {
            bufsize  = TOK_BUFSIZE;
            tokens = realloc(tokens, bufsize * sizeof(char *));
            if (!tokens) {
                fprintf(stderr, "lsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
        token = strtok(NULL, TOK_DELIM);
    }
    tokens[position] = NULL;
    tokencount = position;
    return tokens;

}

void
catHeadTail(char *filename, int headtail, char *line)
{

    char *ptr;
    long num;

    num = strtol(line, &ptr, 10);
    num *= -1;
    char cwd[256];

    if (getcwd(cwd, sizeof(cwd)) == NULL) {
        perror("getcwd() error");
    }
    else {
        strcat(cwd, "/");
        strcat(cwd, filename);
    }

    char lines[1000][256];
    int linecount = 0;
    FILE *input = fopen(cwd, "r");

    if (!input) {
        perror("lsh: file open error");
    }

    while (fgets(lines[linecount], 256, input) != NULL) {
        linecount  ;
    }
    fclose(input);

    if (headtail == 1) {
        for (int i = 0; i < num; i  ) {
            printf("%s", lines[i]);
        }
    }
    else {
        for (int i = linecount - num; i <= linecount; i  ) {
            printf("%s", lines[i]);
        }
    }

}

int
lsh_launch(char **args)
{
    pid_t pid;
// NOTE/BUG: unused
#if 0
    pid_t wpid;
    int state;
#endif
    int background = 0;

    if (!strcmp(args[tokencount - 1], "&")) {
        background = 1;
        args[tokencount - 1] = '\0';
    }

    dbgprt("lsh_launch: PARENT pid=%d pgrp=%d\n",getpid(),getpgrp());

#if 1
    fflush(stdout);
    fflush(stderr);
#endif
    pid = fork();

    if (pid == 0) {
        // child process
        dbgprt("lsh_launch: BEF pgid=%d\n",getpgrp());
        setpgid(0, 0);
        dbgprt("lsh_launch: AFT pgid=%d\n",getpgrp());

        if (tokencount >= 4 && !strcmp(args[2], "|"))   // pipleline
        {
            if (!strcmp(args[0], "cat") && !strcmp(args[2], "|")) {
                if (!strcmp(args[3], "head"))
                    catHeadTail(args[1], 1, args[4]);   // head
                else
                    catHeadTail(args[1], 0, args[4]);   // tail
            }
        }
        else if (tokencount >= 3 && !strcmp(args[1], "<")) {
            char *temp[3];

            temp[0] = args[0];
            temp[1] = args[2];
            temp[2] = NULL;
            if (execvp(temp[0], temp) == -1) {
                perror("lsh");
            }
        }
        else if (tokencount >= 4 && !strcmp(args[2], ">")) {
            char lines[1000][256];
            int linecount = 0;
            FILE *input = fopen(args[1], "r");
            FILE *output = fopen(args[3], "w");

            if (!input || !output) {
                perror("lsh: file open error");
            }

            while (fgets(lines[linecount], 256, input) != NULL) {
                linecount  ;
            }

            for (int i = 0; i < linecount; i  ) {
                fputs(lines[i], output);
            }
        }
        else if (execvp(args[0], args) == -1) {
            perror("lsh");
        }
        exit(0);
    }
    else if (pid < 0) {
        perror("lsh");
    }
    else {
        // parent process
        if (! background) {
            dbgprt("lsh_launch: WAIT/BEF pid=%d\n",pid);
            int status;
// NOTE/BUG: with detached jobs, a detached job might complete _before_ this
// one -- so wait for pid specifically
#if 0
            pid = wait(&status);
#else
            pid = waitpid(pid,&status,0);
#endif
            dbgprt("lsh_launch: WAIT/AFT pid=%d status=%8.8X\n",pid,status);
        }
        else {
// NOTE/BUG: doing this prevents wait from working correctly on the child
#if 0
            signal(SIGCHLD, SIG_IGN);
#endif
// NOTE/FIX: implement "job control" for background job
#if 1
            // get new job control
            struct jobctl *job = calloc(1,sizeof(*job));

            job->job_pid = pid;
            job->job_jobno =   jobcount;

            printf("[PID]: %d job %d\n", pid, job->job_jobno);

            // add it to list
            job->job_next = joblist;
            joblist = job;
#endif
        }
    }
    return 1;
}

int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_echo(char **args);
int lsh_record(char **args);
int lsh_replay(char **args);
int lsh_mypid(char **args);

/*
  List of builtin commands, followed by their corresponding functions.
 */
char *builtin_str[] = {
    "cd",
    "help",
    "exit",
    "echo",
    "record",
    "replay",
    "mypid",
#if 1
    "jobs",
    "wait",
#endif
};

/* all the buildin func*/
int (*builtin_func[]) (char **) = {
    lsh_cd,
    lsh_help, lsh_exit, lsh_echo, lsh_record, lsh_replay, lsh_mypid,
#if 1
    lsh_jobs,
    lsh_wait,
#endif
};

int
lsh_num_builtins()
{
    return sizeof(builtin_str) / sizeof(char *);
}

int
lsh_cd(char **args)
{
    if (args[1] == NULL) {
        fprintf(stderr, "lsh: expected argument to \"cd\"\n");
    }
    else {
        if (chdir(args[1]) != 0) {
            perror("lsh");
        }
    }
    return 1;
}

int
lsh_help(char **args)
{
    int i;

    printf("Stephen Brennan's LSH\n");
    printf("Type program names and arguments, and hit enter.\n");
    printf("The following are built in:\n");

    for (i = 0; i < lsh_num_builtins(); i  ) {
        printf("  %s\n", builtin_str[i]);
    }

    printf("Use the man command for information on other programs.\n");
    return 1;
}

int
lsh_exit(char **args)
{
    return 0;
}

int
lsh_echo(char **args)
{
    if (!strcmp(args[1], "-n")) {
        for (int i = 2; i < tokencount; i  ) {
            printf("%s ", args[i]);
        }
    }
    else {
        for (int i = 1; i < tokencount; i  ) {
            printf("%s ", args[i]);
        }
        printf("\n");
    }
    return 1;
}

int
lsh_record(char **args)
{
    int i = 1;
    node *go = top;

    while (go != NULL) {
        printf("%d: %s\n", i, go->str);
        go = go->next;
        i  ;
    }
    return 1;
}

int
lsh_replay(char **args)
{
    node *go;

    go = top;

    for (int i = 0; i < atoi(args[1]) - 1; i  ) {
        go = go->next;
    }
    node *add = (node *) malloc(sizeof(node));

    add->next = NULL;
    strcpy(add->str, go->str);
    cur->next = add;
    cur = cur->next;

    char **command = split_line(go->str);

    pid_t wpid;
    int state;

#if 1
    fflush(stdout);
    fflush(stderr);
#endif
    pid_t pd = fork();

    if (pd == 0) {
        if (execvp(command[0], command) == -1) {
            perror("lsh");
        }
        exit(EXIT_FAILURE);
    }
    else if (pd < 0) {
        perror("lsh");
    }
    else {
        do {
            wpid = waitpid(pd, &state, WUNTRACED);
            dbgprt("lsh_replay: pd=%d wpid=%d state=%8.8X\n",
                pd,wpid,state);
// NOTE/FIX: wpid is set but not used -- this is a dummy test
#if 1
            if (wpid != pd)
                continue;
#endif
        } while (!WIFEXITED(state) && !WIFSIGNALED(state));
    }

    return 1;
}

int
lsh_mypid(char **args)
{
    int pid = getpid();

    if (!strcmp(args[1], "-i")) {
        printf("%d", pid);
    }
    else if (!strcmp(args[1], "-p")) {
#if 0
        int c;
#endif
        FILE *file;
        char path[30] = "/proc/";
        char content[512];
        char *token;

        strcat(path, args[2]);
        strcat(path, "/stat");
        file = fopen(path, "r");
        if (file) {
            int i = 1;

            fgets(content, 512, file);
            token = strtok(content, TOK_DELIM);
            while (i < 4) {
                token = strtok(NULL, TOK_DELIM);
                i  ;
            }
            fclose(file);
        }
        else {
            printf("not existed");
        }

        printf("%d", atoi(token));
    }
    else if (!strcmp(args[1], "-c")) {
#if 0
        int c;
#endif
        FILE *file;
        char path[30] = "/proc/";
#if 0
        char pids[10];
#endif
        char content[512];
        char *token;

        strcat(path, args[2]);
        strcat(path, "/task/");
        strcat(path, args[2]);
        strcat(path, "/children");
        file = fopen(path, "r");
        if (file) {
            fgets(content, 512, file);
            token = strtok(content, TOK_DELIM);
            printf("%s", token);
            while (token != NULL) {
                token = strtok(NULL, TOK_DELIM);
                printf("%s", token);
            }
            fclose(file);
        }
        else {
            fprintf(stderr, "can not open the file\n");
        }
        return 1;

    }
    return 1;
}

int
lsh_execute(char **args)
{
    int i;

    if (args[0] == NULL) {
        // An empty command was entered.
        return 1;
    }

    for (i = 0; i < lsh_num_builtins(); i  ) {
        if (strcmp(args[0], builtin_str[i]) == 0) {
            return (*builtin_func[i]) (args);
        }
    }

    return lsh_launch(args);
}

void
shell_loop()
{
    int counter = 0;
    char *line;
    char **args;
    char *allline = malloc(sizeof(char) * 64);
    int state = 1;

    while (state == 1) {
#if 1
        jobreap();
#endif

        printf(">>>$ ");
// NOTE/FIX: must flush the output because the printf does _not_ end in newline
#if 1
        fflush(stdout);
#endif
        line = read_line();
#if 1
        jobreap();
#endif
        strcpy(allline, line);
        args = split_line(line);
        if (args[0] == NULL) {          // space or \t
            continue;
        }
        else if (strcmp(args[0], "replay")) // not replay
        {
            if (counter == 0) {
                top = (node *) malloc(sizeof(node));
                strcpy(top->str, allline);
                top->next = NULL;

                cur = top;
            }
            else {
                node *m = (node *) malloc(sizeof(node));

                strcpy(m->str, allline);
                m->next = NULL;

                cur->next = m;
                cur = m;
            }
            if (counter >= 16) {
                node *del;

                del = top;
                top = top->next;
                free(del);
            }
        }
        else if (!strcmp(args[0], "replay"))    // replay
        {
            node *go = top;

            for (int i = 0; i < atoi(args[1]) - 1; i  ) {
                go = go->next;
            }
            node *add = (node *) malloc(sizeof(node));

            strcpy(add->str, go->str);
            add->next = NULL;
            cur->next = add;

            if (counter >= 16) {
                node *del = (node *) malloc(sizeof(node));
// NOTE/BUG: this is flagged by the compiler (i.e.) it sees that the value
// is discarded
#if 0
                del = top;
                top = top->next;
#else
                del->next = top;
                top = del;
#endif
                // free(del);
            }
        }
        state = lsh_execute(args);

        free(line);
        free(args);
        // free(allline); don't really know why it can not be freed
        counter  ;

    }
}

int
main()
{

    shell_loop();

    return EXIT_SUCCESS;

}
  • Related