Regarding background processes using fork() and child processes in my dummy shell

50,526

You do not want to use the double-fork() method in a shell - that is for writing daemons that specifically want to escape from supervision by the shell that ran them.

Instead, the way to duplicate the behaviour of & in existing shells is to call fork(), then in the child call setpgid(0, 0); to put the child into a new process group. The parent just continues on (perhaps after printing the PID of the child) - it doesn't call wait().

Each terminal can have only one foreground process group, which is the process group which is currently allowed to send output to the terminal. This will remain the process group of which your shell is a member, so your shell will remain in the foreground.

If a background process group tries to read from the terminal, it will be sent a signal to stop it. Output to the terminal is still allowed - this is normal (try ls & in your regular shell). To implement the fg command in your shell, you will need the tcsetpgrp() function to change the foreground process group.

You will also want to call waitpid() with the WNOHANG option regularly - say, immediately before you display the shell prompt. This will allow you to detect when the background process has exited or stopped (or alternatively, you can handle the SIGCHLD signal).

Share:
50,526

Related videos on Youtube

tlw11591
Author by

tlw11591

Updated on August 22, 2020

Comments

  • tlw11591
    tlw11591 over 3 years

    I'm trying to create a simple shell program in C. What I need it to do is provide the user with a prompt in which they can run other local programs. I can do that part fine, using a fork() in which the parent process waits() on the child,and the child execvp()'s the program.

    However, if the '&' character is appended to the end of the user's command, I need their program to run in the background, meaning I need the parent to NOT wait on the child process but instead immediately return the prompt to the user, while allowing the background process to continue to run, but not allowing it to display anything on the screen. I just want to be able to check that it still exists via ps command.

    I tried to understand the idea behind using fork() to create a child, then have the child fork() again to create a grandchild of sorts, then immediately exit()-ing the child process. i.e., orphan the grandchild. Supposedly this allows the parent to still wait on the child, but since the child effectually ends almost immediately, it's like it doesn't wait at all? Something about zombie madness? I don't know. Several sites I've come across seem to recommend this as a way to run a process in the background.

    However, when I try to do this, I get wild things happening with the flow of the program, the 'background' process continues to display input on the screen, and I'm really not sure where to go from here.

    This is my implementation of the code, which I'm sure is quite wrong. I'm just wondering if this whole grandchild thing is even the route I need to take,and if so, what's wrong with my code?

    36 int main(int argc, char *argv[])
    37 {
    38     char buffer[512];
    39     char *args[16];
    40     int background;
    41     int *status;
    42     size_t arg_ct;
    43     pid_t pid;
    44 
    45     while(1)
    46     {
    47         printf("> ");
    48         fgets(buffer, 512, stdin);
    49         parse_args(buffer, args, 16, &arg_ct);
    50 
    51         if (arg_ct == 0) continue;
    52 
    53         if (!strcmp(args[0], "exit"))
    54         {
    55             exit(0);
    56         }
    57 
    58         pid = fork();  //here I fork and create a child process
    61 
    62         if (pid && !background)  //i.e. if it's the parent process and don't need to run in the background (this is the part that seems to work)
    63         {
    64             printf("Waiting on child (%d).\n", pid);
    65             pid = wait(status);
    66             printf("Child (%d) finished.\n", pid);
    67         }
    68         else
    69         {
    70             if (background && pid == 0) { //if it's a child process and i need to run the command in background
    71                
    72                 pid = fork(); //fork child and create a grandchild
    73                 if (pid) exit(0); //exit child and orphan grandchild
    74                 
    75                 execvp(args[0], args); //orphan execs the command then exits
    76                 exit(1);
    77 
    78             } else exit(0);
    79         }
    80     }
    81     return 0;
    82 }
    

    P.S. To be clear, I need the process that I'm running in the background to never make a sound again, even if it's got an infinite loop of print statements or something. I just want to make sure that it is still running in the background via ps -a or something.

    Sorry for the confusing explanation I just don't know how to explain it any better.

    Thanks in advance

    P.P.S I'll be implementing it so that each subsequent command will determine the boolean value of 'background', sorry for the confusion

    • Kerrek SB
      Kerrek SB over 12 years
      The child process should probably close all the file descriptors.
    • alhelal
      alhelal over 6 years
      @tlw11591 background process does not means that they can't print their output in the stdout. It means that it doesn't block you to execute new command.
    • Ac Hybl
      Ac Hybl about 2 years
      @alhelal sure, but I think the point IS to not display anything in this case, since that's what the asker desires
  • tlw11591
    tlw11591 over 12 years
    Okay, I think I followed all of that. It raises one question for me, though. You say "Output to the terminal is still allowed - this is normal (try ls & in your regular shell)." which makes sense (I did try it in my regular bash, and I see what you're saying.) However, is there a way to stop this from happening? Or is that what you're getting at with the rest of your comment? Thanks for the reply.
  • caf
    caf over 12 years
    @Cthing_Mad: You can set the TOSTOP attribute of the controlling terminal with tcsetattr().
  • tlw11591
    tlw11591 over 12 years
    okay, I see what you're saying. I don't need to implement the fg functionality, and I wasn't sure about the stopping output from bg processes. Judging by those functions you're referring to, and by the behavior of other shells when using the & operand, I'm guessing we don't have to worry about it.. Anyways, as far as the confusion regarding the use of a double fork, your response was mad helpful. Thanks a lot!
  • ajfbiw.s
    ajfbiw.s about 8 years
    using waitpid with WNOHANG option in the parent doesn't suspend the parent. Rather, it returns when the child is finished, correct? By using this, we can move the parent back to the foreground. This is for if there is a "&" at the end. @caf
  • caf
    caf about 8 years
    @ajfbiw.s: If there's an & at the end of the command, the parent stays in the foreground. waitpid(.., .., WNOHANG) returns immediately whether or not the child is finished, but returns a different value in each case.
  • ajfbiw.s
    ajfbiw.s about 8 years
    @caf: To clarify, this is the kind of usage I was asking about: collabedit.com/5xqwc
  • Tam211
    Tam211 over 6 years
    How can zombies be handled using SIGCHLD?
  • caf
    caf over 6 years
    @Tam211: You call waitpid() in the SIGCHLD handler (in a loop, until no more child processes have exited).