Educational Objectives:
Operational Objectives:
Deliverables: Two files: myshell.c and makefile
Assessment Rubric:
Your code uses good programming practices: [0...30] xx Your shell can run background commands correctly: [0...35] xx Your shell can kill processes correctly: [0...15] xx Your shell has the jobs command: [0...20] xx Late Fees: [-4 pts per dated submission] (xx) Project 1 source code distribution: [-4 pts] (xx) --- Score: [0..100] xxx
Example Project 1 Code
Note that you may begin with example solution code for project 1 if you are not confident with your shell submitted for project 1. Obtaining this "head start" code will cost 4 points toward your grade in project 2. To obtain a copy of this code:
- cp -r ~cop4610p/LIB/proj1Code ~/cop4610
This should create the directory and give you copies of deliverables.sh and request.txt.- Edit request.txt to place your CS username in the designated place in the document.
- Execute submit.sh
The code will be sent to your CS email account.
NOTE: There is a non-disclosure agreement in "request.txt" that is binding, enforced by the FSU honor code.
Details
Your task is to take your project 1 shell and add the ability to run processes in the background. Note that you already learned how to spawn parallel processes in project 1, but background processing will allow your shell to continue running while another process is running. In other words, you won't have to wait on it to finish. The symbol to denote that a command should run in the background is the ampersand (&). Just like the redirection operators from project 1 (< and >), you can assume whitespace on both sides of the & symbol. Your parsing algorithm will simply add in a check for this symbol. Also, you will want to store some type of flag in your command structure to decide whether a command should be executed normally or in the background.
When a command is set to run in the background, you will follow these steps when executing:
(1) Give the command a job ID (JID) and store the JID, process ID (PID), and command itself in an array. This will require some type of structure to hold the JID, PID, and command. In other words, the type of elements in the array are structures with the JID, PID, and command. Each structure will be placed in the array after the last existing structure. The JID will start at 1 and will always be one more than the last existing structure's JID. For example, if your array is empty, the next background command will occupy the first spot in the array and have JID 1. As another example, if the last existing structure is at the fourth spot in the array and has JID 8, the next one will be added in the fifth spot and have JID 9. You can assume a max of 100 items in the array at any one time.
(2) Print the following message to the screen:
[JID] [PID]
where [JID] is the job ID in brackets and [PID] is the process ID in brackets.
(3) Run the command like normal in your child process.
(4) Your parent process will not wait on a background command in the same way you waited in project 1. Instead, your command prompt loop will periodically wait with a special flag called WNOHANG. This will simultaneously let you check for terminated children and not cause your program to stop and wait. Note that the wait with the special flag is still necessary to avoid zombie processes.
(5) You don't need to print the moment a child ends, but the next time the jobs command is called, you will print the following message to the screen:
[JID] Done COMMAND
where [JID] is the job ID in brackets, Done is literally the word "Done", and COMMAND is the command that was run. You can space them out a bit more if you want with tabs, for example.
You will also create a new built-in command called jobs. While we aren't going to implement job control functionality, you should make jobs simply print the entire array of background tasks in the format of (5) above, except printing [PID] instead of Done. Only make it print actual jobs, not free array slots.
Backgound processes should be able to be terminated prematurely by using the kill command. This is an external command, so you don't actually have to worry about killing the process, but you do have to make sure your jobs array correctly reflects this. You may assume that the PID is the only argument the kill command will have. After calling kill PID, you will print something like this: [JID] Terminated COMMAND.
Background Processing
Background processes are one way to achieve concurrency. The upside is that they are easy to work with compared to threads. The downside, among other things, is the overhead with creating new processes as opposed to new threads. So, let's take a closer look at how we run processes in the background.
First, the & operator will indicate a command is to be run in the background. You will want your command structure to have some type of flag set to "yes" or "no" for running in the background. Otherwise, you are simply parsing commands like the first project.
Next, you'll need some type of array to hold information for background processes. This information includes the job ID (JID), process ID (PID), and command itself. So, you will need a structure similar to your command structure to hold this information. Since we're assuming a max of 100 jobs, you can simply create a global array with 100 slots. You will also find that keeping a current size variable is useful.
When a command is to be executed in the background, you will have to add it after the last existing job in the array. Your new entry will have a JID of one more than this last job. Of course, if the array is empty, your new entry will have JID 1. Next, you can print the JID and PID. Remember, the PID is returned to the parent process, so you'll have this information. Now, the crucial difference here is not to wait for the child process to finish, but to realize that waiting is still needed periodically. Take a look at background.c. Essentially, it loops forever asking how long you want to sleep. Each time you give it an amount to sleep, a new child process is created for that sleep command. Notice that the sleeping won't actually stop it from constantly asking the question. This is because the waitpid() is using the WNOHANG flag. This means it returns immediately. But, remember that this function also does necessary clean up, so we need to include it. Our strategy is then to periodically check for a terminated child. This has the downside of not necessarily informing us as early as possible. However, an alternate strategy using signals can do this. But, signals can cause their own strange issues, such as causing certain I/O functions to fail. So, we'll stick with our first strategy. Also notice that we give waitpid() a PID of -1 to wait on. This means we want to check if any child process has terminated. You could have multiple children processes running in the background simultaneously. This is a good way to check for any that have terminated. Note that you don't want to use this strategy for foreground processes (from project 1), because it will also try waiting on any background processes that may be running. So, don't change any of your waiting code from project 1 for foreground processes. The main difference in my example and what your shell will do is that you're recording and printing more information.
If a user tries to exit whille there are still background process(es) running, the request to exit shall be denied, and a message should print to the screen that the user must kill all processes before exiting.
Finally, we have the jobs command. This will simply print out your array. Let's take a look at a potential array structure:
jobs[0]: {1, 2948, "sleep 20"}
jobs[1]: {2, 2949, "sleep 10"}
jobs[2]: {3, 2950, "sleep 30"}
jobs[3]: {..., ..., ...}
jobs[4]: {..., ..., ...}
...The brackets are just a way of indicating a set of data. The first item is the JID, second is the PID, third is the command itself. So, when printing the info, we'll print indices 0 through 2. When going to add a new job, we'll add it at index 3 like so:
jobs[0]: {1, 2948, "sleep 20"}
jobs[1]: {2, 2949, "sleep 10"}
jobs[2]: {3, 2950, "sleep 30"}
jobs[3]: {4, 2951, "sleep 40"}
jobs[4]: {..., ..., ...}
...Each time you add a job, you increment your size variable to keep track of how many jobs are currently in your array. As for removing jobs, you may need to do some shifting. For example, let's say that jobs[1] will finish before the others. So, when we go to remove it, we'll shift the stuff below it up like so:
jobs[0]: {1, 2948, "sleep 20"}
jobs[1]: {3, 2950, "sleep 30"}
jobs[2]: {4, 2951, "sleep 40"}
jobs[3]: {4, 2951, "sleep 40"}
jobs[4]: {..., ..., ...}
...Notice that jobs[3] still contains old data. But, this doesn't matter because we're keeping track of the size. After removing jobs[1], we know we only have 3 jobs. Hence, you don't have to worry about "clearing out" old data. Just do the shifting. The only case where shifting isn't necessary is when removing the last job.
Ok, this should be enough info to complete the project!
Sample Runs
Here are some sample runs that you can use to compare your output to. Note that your output doesn't have to match exactly, but it couldn't hurt.
Note that we type jobs every few seconds to let you see a couple things. First, that you can keep doing things while the sleeps are running. Second, to illustrate that the termination status is being checked during each command-loop iteration.
xxxxxx@myshell:/home/grads/xxxxxx>sleep 5 &
[1] 24204
xxxxxx@myshell:/home/grads/xxxxxx>sleep 10 &
[2] 24208
xxxxxx@myshell:/home/grads/xxxxxx>sleep 20 &
[3] 24209
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] 24204 sleep 5
[2] 24208 sleep 10
[3] 24209 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] Done sleep 5
[2] 24208 sleep 10
[3] 24209 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[2] Done sleep 10
[3] 24209 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[3] 24209 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[3] Done sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
xxxxxx@myshell:/home/grads/xxxxxx>sleep 10 &
[1] 24285
xxxxxx@myshell:/home/grads/xxxxxx>sleep 9 &
[2] 24286
xxxxxx@myshell:/home/grads/xxxxxx>sleep 8 &
[3] 24287
xxxxxx@myshell:/home/grads/xxxxxx>sleep 7 &
[4] 24288
xxxxxx@myshell:/home/grads/xxxxxx>sleep 6 &
[5] 24293
xxxxxx@myshell:/home/grads/xxxxxx>sleep 5 &
[6] 24294
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] Done sleep 10
[2] Done sleep 9
[3] Done sleep 8
[4] 24288 sleep 7
[5] 24293 sleep 6
[6] 24294 sleep 5
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[4] Done sleep 7
[5] Done sleep 6
[6] Done sleep 5
xxxxxx@myshell:/home/grads/xxxxxx>jobs
xxxxxx@myshell:/home/grads/xxxxxx>
xxxxxx@myshell:/home/grads/xxxxxx>sleep 5 &
[1] 24930
xxxxxx@myshell:/home/grads/xxxxxx>sleep 10 &
[2] 24931
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] 24930 sleep 5
[2] 24931 sleep 10
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] Done sleep 5
[2] 24931 sleep 10
xxxxxx@myshell:/home/grads/xxxxxx>sleep 20 &
[3] 24935
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[2] Done sleep 10
[3] 24935 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[3] 24935 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[3] Done sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>sleep 20 &
[1] 28737
xxxxxx@myshell:/home/grads/xxxxxx>sleep 30 &
[2] 28738
xxxxxx@myshell:/home/grads/xxxxxx>kill 28738
[2] Terminated sleep 30
xxxxxx@myshell:/home/grads/xxxxxx>jobs
[1] 28737 sleep 20
xxxxxx@myshell:/home/grads/xxxxxx>exit
You must kill background processes before exiting
Grading
Your code uses good programming practices (30%).
Your shell can run background commands correctly (35%).
Your shell can kill background processes correctly, and only exits if these have all been killed (15%).
Your shell has the jobs command (20%).
Submitting
Submit two files: myshell.c containing your updated shell source code with background processing, and makefile which builds an executable named myshell.x. Do this with ~cop4610p/submitscripts/proj2submit.sh.