Thread: Doing my own shell, how to properly execute processes in background/foreground?

  1. #1
    Registered User
    Join Date
    Mar 2006
    Posts
    158

    Doing my own shell, how to properly execute processes in background/foreground?

    Hi,

    I have this stuff to do for university that's basically a simple custom shell and I'm currently having some difficulties.

    The basic functionality is already done, I read a command from the stdin and execute it with execvp(). After that, with wait(NULL), I wait for that process to finish before returning to the shell. I now can properly run processes in the foreground one at a time. Time to implement background processes...

    The first step is to check if the "&" is the last argument, if it is, run the process in the background. To do that I simply check for the "&" and set a background variable to true or false accordingly. Then, if background == false do not wait for the process to finish and return to the shell.

    So far so good... Now my problem...

    When running processes in the background like this, it's likely they end up "zombies" right? I need to take care of them and for that, I believe I need to handle the SIGCHLD signal... At least I need to use the signal() function to handle something because that's part of the exercise. And that's what I did as you'll see in the code below...

    Now my questions:

    1) Is this code ok or should I do it differently? I ask this because if I run a process in the foreground, there will be two waits, the first one (wait(NULL)) right after executing the command and a second one (waitpid(-1, &status, WNOHANG)) right after the process terminates or is killed/stopped/whatever.

    I don't think that this should happen. First because it doesn't make sense to call wait two times for a foreground process, the first wait will turn the second one redundant. Second because part of my exercise is that I need to print the status changes of the processes but there's no reason to do this for foreground processes. So basically, I need to call the waitpid() in childSignalHandler() only for background processes.

    How should I solve this then?

    2) As I said in my first question, I need to print the status changes of the processes running in the background. Any changes how to handle this? My first guess is that I simply need to print the value of status after waitpid() in childSignalHandler(). Is that it?

    That's almost all I want to know, I still have a couple of more things that I want to accomplish but they are not part of the exercise for university, they are just some things I want to do to improve my custom shell a little bit, I'll talk about them later...

    I was just going to forget about the code lol, here it is:
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <wait.h>
    #include <signal.h>
    #include <sys/types.h>
    
    #include "data.h"
    
    
    void childSignalHandler(int signum) {
    	int status;
    	pid_t pid;
    	
    	pid = waitpid(-1, &status, WNOHANG);
    }
    
    int main(int argc, char **argv) {
    	char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
    	bool background;
    	ssize_t rBytes;
    	int aCount;
    	pid_t pid;
    	
    	signal(SIGCHLD, childSignalHandler);
    	
    	while(1) {
    		write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
    		rBytes = read(0, bBuffer, BUFSIZ-1);
    		
    		if(rBytes == -1) {
    			perror("read");
    			exit(1);
    		}
    		
    		bBuffer[rBytes-1] = '\0';
    		
    		if(!strcasecmp(bBuffer, "exit")) {
    			exit(0);
    		}
    		
    		sPtr = bBuffer;
    		aCount = 0;
    		
    		do {
    			aPtr = strsep(&sPtr, " ");
    			pArgs[aCount++] = aPtr;
    		} while(aPtr);
    
    		background = FALSE;
    		
    		if(!strcmp(pArgs[aCount-2], "&")) {
    			pArgs[aCount-2] = NULL;
    			background = TRUE;
    		}
    		
    		if(strlen(pArgs[0]) > 1) {
    			pid = fork();
    			
    			if(pid == -1) {
    				perror("fork");
    				exit(1);
    			}
    
    			if(pid == 0) {
    				execvp(pArgs[0], pArgs);
    				exit(0);
    			}
    			
    			if(!background) {
    				wait(NULL);
    			}
    		}
    	}
    
    	return 0;
    }
    Last edited by Nazgulled; 05-23-2009 at 01:52 PM.

  2. #2
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    Anyone please? I really need to finish this...

  3. #3
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by Nazgulled View Post
    When running processes in the background like this, it's likely they end up "zombies" right?
    Why do you believe that?

    Anyway, I would say your whole approach is flawed. If you want the difference between background and foreground, consider 1) forking vs. not forking 2) exec vs. system. I would not try to write a function that does both things; you should parse the input somewhere and then send it to one of a number of functions depending upon what the input is.

    Also look into popen().
    Last edited by MK27; 05-23-2009 at 02:55 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  4. #4
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    This is a school exercise, I can't just do what I want the way I want... It doesn't matter if the approach is flawed, I can't do what what you are suggesting, I need to do it like this because this is what the exercise requires.

  5. #5
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    So you are saying this:

    Quote Originally Posted by Nazgulled View Post
    The first step is to check if the "&" is the last argument, if it is, run the process in the background. To do that I simply check for the "&" and set a background variable to true or false accordingly. Then, if background == false do not wait for the process to finish and return to the shell.
    is dictated by the assignment specs? And that you are to use a single function for both background and foreground processes? AND that you are to use exec, and not system() or popen()?

    Those are some tough specs. I would *especially* make sure that you are supposed to fork a "foreground" process...as you point out, this creates an unnecessary complication with the signal handling. It is also counter intuitive. With bash, & will fork, but I don't *think* the shell forks foreground process. I would almost bet money it doesn't. You don't have to fork() to exec().

    I've never done anything like this tho. Sorry...
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  6. #6
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    Yes, I'm almost certain of all those things...

    You see, this whole thing I have to do, a basic shell, is divided into various tasks. But they are not tasks as in, you have to do this and that and then you think how you are going to put everything together, no...

    The word "task" is probably not the best one but I can't think of a better one right now.

    You look at the first task and do it, only after that is completed you move on to the next one. Each task tells you what you're supposed to do of course and it also points out the functions we are supposed to use.

    As I've told, the idea is to create a simple and basic shell where the first task is simply to run commands and their arguments, nothing else. Now, what would be the point of creating an application and simply call system() with whatever the user typed into the standard input? That would be exactly the same as typing them in the bash... This first task suggests the use of the exec() set of functions. But like the system() call above, it doesn't make sense to just call it like that, what would be the purpose of that?

    As you probably know, the exec set of functions replace the current process with the one in the command for exec(). The system() call will create a child and wait for it but gives you no control on that. Basically, the essence of the first task is to mimic the system() call and for that, I need to fork the current process so I have a child process where to run the command in exec(). This first task doesn't talk about foreground or background processes and so it's safe to assume they are all foreground which could be described as default behavior. I type a command and I expect to run it right way and should return to the shell prompt only after the executed program finished running.

    This first task is basically to understand the idea behind forking processes, running commands and wait for them to be completed and then return to the shell prompt. There would be no point in archiving this with the system() call, what would I learn with that about forking, executing and waiting? Nothing... Making more sense?

    This is getting quite big...

    Now, the purpose of the second task is to build up on the first task code and improve the shell a little bit. And one of the steps on this second task, is to have the ability to run foreground and background processes. Removing the forking for foreground processes and replace it with system() would still make no sense because the idea is to keep developing on the previous task's code.

    So, my problem persists... For foreground processes I need to wait for them with wait(NULL), this is obvious, I don't want to return to the shell prompt until the command/process is finished running. And I also need to wait for the background processes as to prevent them from becoming zombie processes until the parent process (my bash) is terminated. And to do that (for background processes), I need to handle the SIGCHLD signal and do something there, but I'm not sure I'm doing it right or what I'm supposed to do...

  7. #7
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Good luck
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  8. #8
    Registered User
    Join Date
    Sep 2007
    Posts
    1,012
    You can use sigprocmask() to block SIGCHLD when you're running your foreground process, so you won't have to worry about a signal being sent.

    The problem with signal handlers is that you're somewhat limited in what you're allowed to do in one. You can't, for example, call printf(). I'm not sure what your teacher expects from you.

    What I'd do is, each iteration of your event loop, simply loop on waitpid() with WNOHANG until it returns -1. If your teacher requires you to catch SIGCHLD, I'd set a global flag (of type volatile sig_atomic_t) in the handler and then check that flag in the main loop, and do the whole waitpid() thing. If your teacher doesn't realize that you can't call printf() from a signal handler and expects you to print from it, then I suppose you catch SIGCHLD and print out whatever you need to, all the while keeping in mind you're not supposed to be doing that.

  9. #9
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    Quote Originally Posted by cas View Post
    You can use sigprocmask() to block SIGCHLD when you're running your foreground process, so you won't have to worry about a signal being sent.
    Can you provide a basic example please?

    Quote Originally Posted by cas View Post
    The problem with signal handlers is that you're somewhat limited in what you're allowed to do in one. You can't, for example, call printf(). I'm not sure what your teacher expects from you.
    I'm not sure what you mean because I just added a printf("CALLING WAITPID...\n"); right before pid = waitpid(-1, &status, WNOHANG); in childSignalHandler() and it's working just fine...

    Quote Originally Posted by cas View Post
    (...)then I suppose you catch SIGCHLD and print out whatever you need to, all the while keeping in mind you're not supposed to be doing that.
    Not sure I understand this either but why not? If I call, say, "gedit &" in bash, gedit will be executed in the background and be able to keep doing my work in the bash. Whenever I close gedit, a message will be printed in the bash saying the process was terminated. The bash does it, why can't I?
    Last edited by Nazgulled; 05-23-2009 at 10:12 PM.

  10. #10
    int x = *((int *) NULL); Cactus_Hugger's Avatar
    Join Date
    Jul 2003
    Location
    Banks of the River Styx
    Posts
    902
    With bash, & will fork, but I don't *think* the shell forks foreground process. I would almost bet money it doesn't. You don't have to fork() to exec().
    Of course it does. As a shell, you have to fork() regardless of whether you're creating a background or a foreground process. If you exec'd without the fork - you'd be gone. From TFM:
    Quote Originally Posted by man 3 fork
    replaces the current process image with a new process image.
    I never bothered with signals when I wrote my shell. Too much crap to deal with. It's like multithreading, but much more painful. I notice that you have two wait()'s. One of them is going to error:
    Code:
    29 wait4(-1, NULL, 0, NULL)                = 12198
    30 --- SIGCHLD (Child exited) @ 0 (0) ---
    31 wait4(-1, 0x7fff12bc25a8, WNOHANG, NULL) = -1 ECHILD (No child processes)
    Here, you signal handler got beat.

    The bash does it, why can't I?
    But does bash write to stdout from within the signal handler? Does it use a signal handler at all?
    Last edited by Cactus_Hugger; 05-23-2009 at 10:49 PM.
    long time; /* know C? */
    Unprecedented performance: Nothing ever ran this slow before.
    Any sufficiently advanced bug is indistinguishable from a feature.
    Real Programmers confuse Halloween and Christmas, because dec 25 == oct 31.
    The best way to accelerate an IBM is at 9.8 m/s/s.
    recursion (re - cur' - zhun) n. 1. (see recursion)

  11. #11
    Registered User
    Join Date
    Sep 2007
    Posts
    1,012
    Quote Originally Posted by Nazgulled View Post
    Can you provide a basic example please?
    Code:
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_BLOCK, &set, NULL);
    Quote Originally Posted by Nazgulled View Post
    I'm not sure what you mean because I just added a printf("CALLING WAITPID...\n"); right before pid = waitpid(-1, &status, WNOHANG); in childSignalHandler() and it's working just fine...
    It's unspecified behavior. That means that it sometimes might do what you expect. The problem is that the signal is delivered asynchronously, meaning that it could happen at any point, including when a function (like printf()) is running. If the function is not designed to be called in this manner (that is, multiple times simultaneously), bad things can happen. If you never call the function outside of the signal handler, you're safe... but do be careful.
    Quote Originally Posted by Nazgulled View Post
    Not sure I understand this either but why not? If I call, say, "gedit &" in bash, gedit will be executed in the background and be able to keep doing my work in the bash. Whenever I close gedit, a message will be printed in the bash saying the process was terminated. The bash does it, why can't I?
    First, bash isn't necessarily printing from the handler. Second, the fact that bash does something doesn't mean it's standards compliant, so if it is printing from the handler, it might be doing so in an unsafe manner.

  12. #12
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    Quote Originally Posted by Nazgulled View Post
    When running processes in the background like this, it's likely they end up "zombies" right? I need to take care of them and for that, I believe I need to handle the SIGCHLD signal...
    Why would they become zombies. They will just finish when they finish, and no additional cleanup is needed, unless the assignment requires you to print when they close or something.


    1) Is this code ok or should I do it differently? I ask this because if I run a process in the foreground, there will be two waits, the first one (wait(NULL)) right after executing the command and a second one (waitpid(-1, &status, WNOHANG)) right after the process terminates or is killed/stopped/whatever.

    I don't think that this should happen. First because it doesn't make sense to call wait two times for a foreground process, the first wait will turn the second one redundant. Second because part of my exercise is that I need to print the status changes of the processes but there's no reason to do this for foreground processes. So basically, I need to call the waitpid() in childSignalHandler() only for background processes.

    How should I solve this then?
    The wait(NULL) is definitely wrong, because if you have a background and a foreground process running at once, and quit the background process first, then your foreground process will become a background process.

    I don't see why you need a signal handler.


    2) As I said in my first question, I need to print the status changes of the processes running in the background. Any changes how to handle this? My first guess is that I simply need to print the value of status after waitpid() in childSignalHandler(). Is that it?
    Put it after the exec function, before exit(). But you probably want to synchronize the output, somehow (how depends on the assignment). That's were signal() comes in.
    Last edited by King Mir; 05-23-2009 at 10:53 PM.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

  13. #13
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    Quote Originally Posted by Cactus_Hugger View Post
    I never bothered with signals when I wrote my shell. Too much crap to deal with. It's like multithreading, but much more painful. I notice that you have two wait()'s. One of them is going to error:
    Code:
    29 wait4(-1, NULL, 0, NULL)                = 12198
    30 --- SIGCHLD (Child exited) @ 0 (0) ---
    31 wait4(-1, 0x7fff12bc25a8, WNOHANG, NULL) = -1 ECHILD (No child processes)
    Here, you signal handler got beat.
    I'm not sure what all that means but you pointed the fact that I have two wait()'s, yes, I'm aware of that and that's what I want to solve. I need to do something to call one of the wait()'s for the foreground processes and the other for the background ones.

    Quote Originally Posted by Cactus_Hugger View Post
    But does bash write to stdout from within the signal handler? Does it use a signal handler at all?
    That I don't know, doesn't matter how bash does it anyway. I need to print a message when a background process child is terminated and the best place I can think of to do that is the signal handler.

    @cas
    Thanks for the example, I'll be reading more about that so I understand it properly and maybe ask my teacher about this problem to see if I should really care about it or not. The thing is, my next class is not until Friday and I wanted to have this done by then, I might e-mail me, but he doesn't reply that often...

    Quote Originally Posted by King Mir View Post
    Why would they become zombies. They will just finish when they finish, and no additional cleanup is needed, unless the assignment requires you to print when they close or something.
    They do become zombies, I just tested my code and when I run, say, "ls &", the wait(NULL) will not be called because it's a background process and if I then run "ps x", I'll see that the previous "ls" command has become "defunct".

    And yes, the assignment requires me to print when they close.

    Quote Originally Posted by King Mir View Post
    The wait(NULL) is definitely wrong, because if you have a background and a foreground process running at once, and quit the background process first, then your foreground process will become a background process.
    You are right, I'm not sure it was exactly that that happened to me last night but I saw some weird behavior going on... That's why I replaced wait(NULL) for waitpid(pid, NULL, 0). This fixes a few issues I was having, probably the one you mentioned.

    Quote Originally Posted by King Mir View Post
    I don't see why you need a signal handler.
    Again, if I don't use a signal handler how am I supposed to clean the zombie processes? As I've said above, they do become zombies, I tested my code, I listed all the processes and they remain in the process table until the parent process is closed. I need to clean them up...

    Quote Originally Posted by King Mir View Post
    Put it after the exec function, before exit(). But you probably want to synchronize the output, somehow (how depends on the assignment). That's were signal() comes in.
    But how do I get the status exactly? I mean, signal() or no signal(), I have an exit(0) there, so, no matter what, the status will always be 0, right? Or maybe the status I'm looking for has nothing to do with the exit() parameter?

  14. #14
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Did you try this?

    Code:
    			if(pid == 0) {
    				x=execvp(pArgs[0], pArgs);
                                    fprintf(stderr, "%s exited (%d)\n",pArgs[0],x);
    				exit(0);
    			}
    Doesn't help with the zombies of course. I think they are actually bash's fault because you are running a shell in a shell (of course I have been wrong once already in this thread).
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  15. #15
    Registered User
    Join Date
    Mar 2006
    Posts
    158
    That won't work... Cause, execvp() will only return in case of an error, it will never return if it was successful. Making the exit(0) call redundant and unnecessary.

    I just learned about this... So I need to improve that piece of code and check for the execvp() return value, if it's -1, than it didn't work and I need to call exit(0) or maybe exit(errno) is more appropriate...

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Develop. Shell , Backgrounds processes
    By Barassa in forum C Programming
    Replies: 0
    Last Post: 10-22-2008, 02:42 PM
  2. binary tree of processes
    By gregulator in forum C Programming
    Replies: 1
    Last Post: 02-28-2005, 12:59 AM
  3. Program to execute shell commands.
    By LiquidLithium in forum C++ Programming
    Replies: 6
    Last Post: 09-01-2004, 12:22 PM
  4. Shell execute... but piping
    By nickname_changed in forum C++ Programming
    Replies: 2
    Last Post: 05-21-2003, 07:39 AM
  5. Problems with Shell Execute
    By golfinguy4 in forum Windows Programming
    Replies: 3
    Last Post: 12-03-2002, 12:37 PM