COMP3080: Operating Systems Summer Semester 2023 Programming Project 1
Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit
COMP3080: Operating Systems
Summer Semester 2023
Programming Project 1
Instructions for this assignment:
You may work on this assignment either solo, or in a group of up to four students (including yourself). Submit the assignment by following the steps listed under “Submission Procedure” before the due date/time; late submissions will receive a grade of zero, but on-time submissions will receive partial credit even if incomplete. If working in a group, each member of the group should submit the assignment, and enter the names of all group members in the Comments field in Blackboard.
This project is intended to introduce you to the elements of concurrent programming using the POSIX standard API. Most Unix and Linux systems adhere closely to this API, and these or similar systems are heavily used in backend applications and cloud computing. If you’re already familiar with Linux, some of the instructions will seem rather simplistic!
For this project, you may use an Ubuntu Linux Virtual Machine (provided by the authors of our required textbook) running on the VirtualBox application, or an Ubuntu or other Linux-compatible system such as the CS department servers (cs.uml.edu).
Problem Statement
In this part of the project, you are going to redirect Linux input within a C program after forking a child process and using the execlp system call. I'll first provide some background.
The Linux/UNIX shell command line environment provides a large number of simple commands that process text files. Suppose that you have a text file, Names.txt, that contains a series of names (one per line of the file), and that you want to know how many names are in the file. You can type:
wc -l < Names.txt
which will display the number of lines in Names.txt, which is the number of names.
However, if you examine Names.txt, you'll see that it contains a lot of duplicate names. If you want to know the number of unique names in Names.txt, you can use the Linux shell's pipe redirector to use several commands in a pipeline, as below:
sort < Names.txt | uniq | wc -l
The sort command is given the list of names and sorts them. The uniq command removes duplicate lines in a file, but only if the duplicate lines are adjacent. By sorting Names.txt and piping the sorted list to uniq (using the " |" symbol), the duplicate lines are adjacent and uniq can remove them. Finally, the sorted list with duplicates removed is piped to the wc command, which counts the number of lines of the sorted list with the duplicates removed to display the number of unique names.
You will write a small C program which does what the Linux shell does, without doing the command line processing. That is, you'll write a program that creates three child processes, one for each command. Each child process will tie an end of a pipe to the standard input, standard output, or both, and then call the execlp system call to run the command. The three child processes will be connected by two Linux pipes, one pipe between the first two processes and the other pipe between the second two processes. The overall structure of the program is shown in Figure 1. Figures 2 and 3 show examples discussed in class which show the use of execlpto run a Linux command and the creation of pipes. Your program will use the techniques from both of these programs.
However, the code in Figure 3, which demonstrates pipes, doesn't handle the redirection needed. The problem is that once the execlp finishes, each child process is running a brand-new program image which knows nothing of the pipes that were created by the parent process. So, before calling execlp to start the new program, each child must first tie the appropriate ends of the pipes to its standard input and/or output using the dup2 system call.
Your program will need to use the following include statements:
#include
#include
You will also need the following POSIX API/Linux system calls and C library calls:
• printf(
fprintf(
printf and fprintf allow you to display formatted output. The only difference between them is that printf always displays its output on the standard output (stdout), while fprintf allows you to choose the output file (for this assignment, the only need for fprintf is to send output to the standard error output, stderr). Replace
The call:
printf("This process id is %d\n", getpid());
displays the string:
This process id is 19334
on a separate line, if the function getpid() returns 19334 as the process ID for the current process.
• getpid()
This system call returns the process ID (pid) of the current process.
• fork()
This system call creates a new process that is an exact duplicate of the current process. The only difference between the two processes are the return values from fork(): The child process sees a return value of zero, while the parent process sees the PID of the child process.
• close(
This system call closes the file descriptor
• pipe(
This system call places two file descriptors into the two-element integer array
• dup2(
This system call ties the existing file descriptor
• execlp(
This system call replaces the current program image (code, data, and stack) with the image found at the path name
◦ sort
▪
▪
▪ no
◦ uniq
▪
▪
▪ no
◦ wc
▪
▪
▪
▪ NULL
• waitpid (
This system call waits for the child process with the process id
We’re now ready to begin work on this step of the project.
1. To start this step of the project, open a terminal window and type:
mkdir SortUniqWc
and then:
cd SortUniqWc
2. You will now be in the directory SortUniqWc. Type:
gedit sortuniqwc.c &
to open the gedit editor (or open your favorite editor).1 Use the output provided in Figure 1 and the examples shown in Figures 2 and 3 to guide you.
It’s a good idea to add printf statements to each of the children to display their process IDs:
printf("The child process running
where
Also add a printf after each execlp:
printf("Should not be here after execlp to
If execlp fails, the printf will display a message; if the execlp succeeds, the printf will not execute. This is a simple way to verify that execlp is working. Typical reasons for execlp failing are a bad command path name, or a bad argument sequence (most commonly, a missing NULL at the end of the command line arguments).
Also, add a printfto the main process code that informs you when the last child is finished (just after the call to wait).
3. From the Blackboard page for this assignment, download the file, Names.txt, that contains the list of names.2
4. Type the command:
sort < Names.txt | uniq | wc -l
in your terminal. You should get a display of 23, indicating that there are 23 unique names in the Names.txt file. (If you get a value other than 23, use an editor to check whether there is a superfluous line at the end of your copy of the Names.txt file.)
5. To run your program, you must first compile it. To do so, type
gcc -o sortuniqwc sortuniqwc.c
This command will compile the program in sortuniqwc.c and put the binary executable in the file sortuniqwc.
6. To run your program, type:
./sortuniqwc < Names.txt
The " ./" tells the shell to execute the program in the file sortuniqwc by looking in the current directory (SortUniqWc) rather than searching through the standard system paths. You should see the printed messages you added to the code. There is no certain order to these messages; running the program several times may result in slightly different orders. This is normal.
Submission Procedure
Submit your source file sortuniqwc.c using the Blackboard page for this assignment.
Remember to indicate in the Comments field for the submission whether you worked solo or in a group, and (if you worked in a group) list all of your group members including yourself.
Outline for your program
/* insert #include directives for all needed header files here */
int main(int argc, char *arv[]) {
//create first pipe fd1
// fork first child
pid = fork(); // create first child for sort
if (pid < 0) {
// fork error
}
// tie write end of pipe fd1 to standard output (file descriptor 1)
// close read end of pipe fd1
// start the sort command using execlp
// should not get here
}
//create second pipe fd2
// fork second child
pid = fork(); // create second child for uniq
if (pid < 0) {
// fork error
}
if (pid == 0) { // second child process, run uniq
// tie write end of fd2 to standard output (file descriptor 1)
// close write end of pipe fd1
// close read end of pipe fd2
// start the uniq command using execlp
// should not get here
}
// fork third child
pid = fork() // create third child for wc -l
if (pid < 0) {
// fork error
}
if (pid == 0) { // third child process, run wc -l
// tie read end of fd2 to standard input (file descriptor 0)
// close write end of pipe fd2
// close read end of pipe fd1
// close write end of pipe fd1
// start the wc -l command using execlp
// should not get here
}
// parent process code
// close both ends of pipes fd1 and fd2
// wait for third process to end.
}
Figure 1: Outline ofthe sortuniqwc.c program
Example of using execlp
/**
* This program forks a separate process using the fork()/exec() system calls. *
* Figure 3.09 *
* @author Silberschatz, Galvin, and Gagne
* Operating System Concepts - Ninth Edition
* Copyright John Wiley & Sons - 2013
*/
#include
#include
#include
int main()
{
pid_t pid;
/* fork a child process */
pid = fork();
if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed\n");
return 1;
}
else if (pid == 0) { /* child process */
printf("I am the child %d\n",pid);
execlp("/bin/ls","ls",NULL);
}
else { /* parent process */
/* parent will wait for the child to complete */
printf("I am the parent %d\n",pid);
wait(NULL);
printf("Child Complete\n");
}
return 0;
}
Figure 2: Figure 3.9 from OSC textbook showing use of execlp to run the Linux command
Example using the pipe system call
/**
* Example program demonstrating UNIX pipes . *
* Figures 3.25 & 3.26 *
* @author Silberschatz, Galvin, and Gagne
* Operating System Concepts - Ninth Edition
* Copyright John Wiley & Sons - 2013
*/
#include
#include
#include
#include
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main(void)
{
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
pid_t pid;
int fd[2];
/* create the pipe */
if (pipe(fd) == -1) {
fprintf(stderr,"Pipe failed");
return 1;
}
/* now fork a child process */
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
}
if (pid > 0) { /* parent process */
/* close the unused end of the pipe */
close(fd[READ_END]);
/* write to the pipe */
write(fd[WRITE_END], write_msg, strlen(write_msg)+1);
/* close the write end of the pipe */
close(fd[WRITE_END]);
}
else { /* child process */
/* close the unused end of the pipe */
close(fd[WRITE_END]);
/* read from the pipe */
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("child read %s\n",read_msg);
/* close the write end of the pipe */
close (fd[READ_END]);
}
return 0;
}
Figure 3: Figures 3.25 and 3.26 from OSC textbook showing
use of Linux pipes
2023-06-16