简体   繁体   中英

How to make a signal handler for SIGCHLD that will reap background processes in shell?

Currently I'm making a shell and it works fine for the foreground processes that it executes. Now I have to implement background processes and job control and I am honestly confused on how I should approach it. I understand that if I want to run the proccesses in the background, I should set their pgid and simply not wait for them, however I have hit a wall when trying to reap them...

I have two structs: job and process

typedef struct job {
    int is_background_job;
    pid_t pgid;
    int job_status;
    process *p;         // List of processes to execute for this job
    struct job *next;   // If a background job, it will be in a global linked list of job structs 
} job;

typedef struct process {
    char **argv;
    process *next;      // The next process to pipe to
} process;

The important part is that a job consists of a linked list of process structs and that there is a global list of job structs which represents all of my background jobs going on.

The shell algorithm is kind of standard.

shell:

get cmd from terminal
parse cmd and create a job struct filled with processes
if job is a background job:
  add it to global background job list
fork(), setpgid, and do the piping in a while loop of the job's processes   
if job is a foreground process:
  wait on the forked processes
else:
  don't wait // since it's a background process
continue to loop and read next cmd from terminal

Now here's my issue. If I have a bunch of processes that are being executed in the background, that means any one of them (from any one of the background jobs) could simply end and then send SIGCHLD. The annoying part is that if all processes for a certain job end, I have to remove that job from the global jobs list. I don't think I can call a loop of waitpid(-1, &status, WNOHANG) in my SIGCHLD handler here because on the off chance that the forground process currently executing finsihes while in the signal handler.

Does this mean I have to go through and waitpid() on every process of every job as soon as a get a SIGCHLD so that I only wait on non-fg processes?

I don't really need code, just an explanation on a good way to do this.

I know this is really late and probably irrelevent to you now, but I'm working on a shell as well and ran into your question while trying to figure out this same problem.

Anyways, it seems like the problem here is getting the main process to wait for all of the foreground children, but this is difficult do to the call to waitpid in the SIGCHLD handler. How I did this was instead of using a wait() call in the main process, I just reaped all processes in the handler and removed them from the appropriate list (I currently have a list for all foreground processes and list for all background processes), and then in the main process I did:

while(foreground process list is not empty)
    pause();

The pause() call just puts the process to sleep until it is woken up by a signal, and in this case SIGCHLD will always awaken the process so that is can again check if there are any foreground processes still running that is needs to wait for. Hope this helps someone!

Edit: Just realized this could cause a race condition if the child terminates right after you check that the list is not empty, and before you call pause() . Simple way around this is to just use nanosleep instead of pause, it will still be interrupted by a signal but gives you the option to check the list size periodically, which avoids the race condition in this case

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM