Ryan Schulze
Ryan Schulze

Reputation: 87

Child Process in Background Issues for Handmade Shell

I'm having a bit of trouble implementing my own handmade shell. I've been able to fork a process and run it in the foregroud with waitpid, but when I try and run simple processes in the background such as 'sleep 5 &', the process seems to run forever. checkListJobs will determine if the process is done running, but it never stops. Any help would be greatly appreciated. I assume the error is in my "foo" function.

void insertJob(int pid) {
    printf("beginning job %d.\n", pid);
    struct job *node = malloc(sizeof(struct job));
    node->pid = pid;
    node->next = NULL;

    if(root == NULL) {
        root = node;
    } else {
        node->next = root;
        root = node;
    }
}    

void checkListJobs(int z) {
    curr = root;
    while(curr!=NULL) {
        if(kill(curr->pid,0) != 0)   {
            if(prev==NULL) {
                prev = curr;
                root = curr;
            } else {
                prev->next = curr->next;
            }
        } else {
            if(!z) printf("%d is still running.\n", curr->pid);
        }
        prev = curr;
        curr = curr->next;
    }
}   


//code for child forking
void foo(char *cmd, char *argv[], int args) {
    int bgFlag;

    if(!strcmp(argv[args], "&")){
        argv[args] = '\0';
        bgFlag = 1;
    }

    int pid = fork();
    int status = 0;

    if(pid==0){
        if(bgFlag) {
            fclose(stdin); // close child's stdin
            fopen("/dev/null", "r"); // open a new stdin that is always empty
        }
        execvp(cmd, argv);
        // this should never be reached, unless there is an error
            fprintf (stderr, "unknown command: %s\n", cmd);
            exit(0);
    } else {
        if(!bgFlag) {
            waitpid(pid, &status, 0);
        } else {
            insertJob(pid);
        }
        if (status != 0) {
            fprintf  (stderr, "error: %s exited with status code %d\n", cmd,     status);
        } else {
            // cmd exec'd successfully
        }
    }

    // this is the parent still, since the child always terminates from exec or exit

    // continue being a shell...
}

Upvotes: 0

Views: 509

Answers (1)

Sean Conner
Sean Conner

Reputation: 416

You will need to install a signal handler for SIGCHLD, as that will tell your program when the child process has finished. Upon receiving SIGCHLD, you should then call wait() (or waitpid() with a PID value of -1, since you won't know which child finished, just that a child has finished).

The safest way to write a handler is:

volatile sig_atomic_t sigchld;
int handle_child(int sig)
{
  if (sig == SIGCHLD)
    sigchld = 1;
}

And in your main loop, check to see if sigchld is 1. If it is, a child process ended, and you can then call waidpid() (use a PID of -1, since you won't know which child ended) in a loop (see below) because multiple children might end at the same time. Also, if any system call return an error and errno is EINTR then it was interrupted by a signal, so either return to the top of your main loop, or check sigchld and handle accordingly (and don't forget to reset sigchld back to 0 as quickly as possible).

for(;;)
{
  int status;
  pid_t child;

  child = waitpid(-1,&status,WNOHANG);
  if (child == -1) 
  {
    if (errno == ECHILD) break; /* no more children */
    /* error, handle how you wish */
  }
  /* handle the return status of the child */
}
sigchld = 0;

You can call waitpid() from within the signal handler (POSIX says it's safe to do so) but you really shouldn't do anything else in the signal handler as it could lead to very subtle bugs (say, SIGCHLD is raised during a call to malloc()---any code in the signal handler that results in a call to malloc() will lead to very nasty problems. That's why I suggested setting a flag in the signal handler---the less you do in a signal handler, the better).

Upvotes: 1

Related Questions