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

Hybrid View

Previous Post Previous Post   Next Post Next Post
  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
    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.

  11. #11
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Not so fast brewbuck, listen to the OP

    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...
    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

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by MK27 View Post
    Not so fast brewbuck, listen to the OP
    There are plenty of things you can do in C which seem to work but are completely wrong.

    On average, you survive Russian Roulette -- wanna play?
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  13. #13
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by brewbuck View Post
    There are plenty of things you can do in C which seem to work but are completely wrong.
    Yep. I might have been being tongue in cheek there, I don't remember

    The point is, Nazgulled, your prof will know the standard and doc you for this. At least s/he should.

    Look:
    Code:
    #include <stdio.h>
    #include <string.h>
    
     int main () {
    	char X[2];
    	strcpy(X,"hello world");
    	printf("%s",X);
    	return 0;
    }
    Works for me! I guess that is correct then!
    Last edited by MK27; 05-27-2009 at 11:39 AM.
    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

  14. #14
    int x = *((int *) NULL); Cactus_Hugger's Avatar
    Join Date
    Jul 2003
    Location
    Banks of the River Styx
    Posts
    902
    Quote Originally Posted by MK27 View Post
    Not so fast brewbuck, listen to the OP
    Not so fast MK27, listen to the man page: ;-)
    Quote Originally Posted by man 7 signal
    A signal handling routine established by sigaction(2) or signal(2) must
    be very careful, since processing elsewhere may be interrupted at some
    arbitrary point in the execution of the program. POSIX has the concept
    of "safe function". If a signal interrupts the execution of an unsafe
    function, and handler calls an unsafe function, then the behavior of
    the program is undefined.

    POSIX.1-2004 (also known as POSIX.1-2001 Technical Corrigendum 2)
    requires an implementation to guarantee that the following functions
    can be safely called inside a signal handler:

    *long list of functions follows, of which printf is not one.*
    ...which is what brewbuck hinted at. But I myself never found any way to notify the main thread of an event from a signal handler. (and hence, I dropped signals altogether.) It would be interesting if someone could enlighten us -- If you have a SIGCHLD handler, how do you have the main thread wait for it to fire, and get information back from it?
    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)

  15. #15
    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)

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