Limited Offer Get 25% off — use code BESTW25
No AI No Plagiarism On-Time Delivery Free Revisions
Claim Now

CS4421 Operating Systems II

This project was adapted from Gary Nutt’s Excellent Book “Kernel Projects for Linux”
published by Addison-Wesley 2001.
You will learn how to write a UNIX shell program. This will give you the opportunity to learn how
child
processes are created
to perform large-grained
work and
how the parent process can follow up on
a child
process’s work.
INTRODUCTION
A shell, or command
line interpreter program, is a mechanism with which each interactive user can
send
command
s to the OS and
by which the OS can respond
to the user. Whenever a user has
successfully logged
in to the computer, the OS causes the user process assigned
to the login port to
execute a specific shell. The OS d
oes not ord
inarily have a built-in wind
ow interface. Instead
, it
assumes a simple character–oriented
interface in which the user types a string of characters (terminated

by pressing the Enter or Return key) and
the OS respond
s by typing lines of characters back to the
screen. If the human-computer interface is to be a graphical wind
ows interface, then the software that
implements the wind
ow manager subsumes the shell tasks that are the focus of this exercise. Thus the
character-oriented
shell assumes a screen d
isplay with a fixed
number of lines (usually 25) and
a fixed

number of characters (usually 80) per line.
Once the shell has initialized
its d
ata structures and
is read
y to start work, it clears the 25-line d
isplay
and
prints a prompt in the first few character positions on the first line. Linux systems are usually
configured
to includ
e the machine name as part of the prompt. For example, my Linux machine is
named
lmojela, so the shell prints, as its prompt string:
lmojela@skylek-Vostro-3550:~$
or
bash:~$
d
epend
ing on which shell I am using. The shell then waits for the user to type a command
line in
response to the prompt. The command
line could
be a string such as:
lmojela@skylek-Vostro-3550:~$ ls –al
terminated
with an or return character (in Linux, this character is represented
internally by
the NEWLINE character, ‘n’). When the user enters a command
line, the shell’s job is to cause the OS
to execute the command
embed
d
ed
in the command
line.
Every shell has its own language syntax and
semantics. In the stand
ard
Linux shell, bash, a command

line has the form:
command [arg1] [arg2] … [argN]
in which the first word
is the command
to be executed
and
the remaining word
s are arguments expected

by that command
. The number of arguments d
epend
s on which command
is being executed
. For
example, the d
irectory listing command
may have no arguments-simply by the user’s typing “ls” or it
may have arguments prefaced
by the negative “-” character, as in “ls –al”, where “a” and
“l” are
arguments. The command
d
etermines the syntax for the arguments, such as which of the arguments
may be grouped
(as for the “a” and
“l” in the “ls” command
), which arguments must be preced
ed
by a
“-” character, and
whether the position of the argument is important.
Other command
s use a d
ifferent argument-passing syntax. For example, a g++ compiler command

might look like:
lmojela@skylek-Vostro-3550:~$ g++ -g -o deviation -S main.cpp inout.cpp –lmath
in which the arguments g, o deviation, S, main.cpp, inout.cpp, and
lmath are all
passed
to the C++ compiler, g++.
The shell relies on an important convention to accomplish its task: The command
for the command
line
is usually the name of a file that contains an executable program, for example, “ls” and
“g++” (files
stored
in /bin on most UNIX-style machines). In a few cases, the command
is not a filename but rather
a command
that is implemented
within the shell. For example, “cd” (change d
irectory) is usually
implemented
within the shell itself rather than in a file in /bin. Because the vast majority of the
command
s are implemented
in files, you can think of the command
as actually being a filename in
some d
irectory on the machine. This means that the shell’s job is to:
1 – find
the file
2 – prepare the list of parameters for the command
,
3 – cause the command
to be executed
using the parameters.
Many shell programs are used
with UNIX variants, includ
ing the original Bourne shell (sh), the C shell
(csh) with its ad
d
itional features over sh, the Korn shell, and
the stand
ard
Linux shell (bash). All have
followed
a similar set of rules for command
line syntax, though each has a superset of features.
Basic UNIX-Style Shell Operation:
The Bourne shell is d
escribed
in Ritchie and
Thompson’s original UNIX paper [Ritchie and
Thompson,
1974]. As d
escribed
in the previous subsection, the shell should
accept a command
line from the user,
parse the command
line, and
then invoke the OS to run the specified
command
with the specified

arguments. This command
line is a request to execute the program in any file that contains a program,
includ
ing programs that the user wrote. Thus a programmer can write an ord
inary C program, compile
it, and
have the shell execute it just like it was a UNIX command
.
For example, suppose that you write a C++ program in a file named
main.cpp and
then compile and

execute it with shell command
s such as:
lmojela@skylek:~$ g++ -c –I. –o main.o main.cpp
lmojela@skylek:~$ g++ -o main.o main
lmojela@skylek:~$ ./main
For the first command
line, the shell will find
the g++ command
(the C++ compiler) in the /bin
d
irectory and
then, when the g++ command
is executed
, pass it the string main.cpp. The C++
compiler will translate the C++ program that is stored
in main.cpp and
write the resulting object file
named
main.o in the current d
irectory. The next command
links the object file into an executable.
The third
command
is simply the name of the file to be executed
, main, without any parameters. The
shell find
s the main file in the current d
irectory and
then executes it.
Consid
er the following steps that a shell must take to accomplish its job.

  1. Print a prompt.
    A d
    efault prompt string is available, sometimes hard
    cod
    ed
    into the shell, for example the
    single character string %, $, #, or >. When the shell is started
    , it can look up the name
    of the machine on which it is running and
    prepend
    this string to the stand
    ard
    prompt
    character, for example a prompt string such as lmojela@skylek:~$. The shell also can
    be d
    esigned
    to print the current d
    irectory as part of the prompt, meaning that each time that
    the user types cd
    to change to a d
    ifferent d
    irectory, the prompt string is red
    efined
    . Once the
    prompt string is d
    etermined
    , the shell prints it to stdout whenever it is read
    y to accept a
    command
    line.
  2. Get the command
    line.
    To get a command
    line, the shell performs a blocking keyboard
    input operation so that the
    process that executes the shell will be asleep until the user types a command
    line in response
    to the prompt. Once the user types the command
    line (and
    terminates it with a NEWLINE
    (‘n’) character), the command
    line string is returned
    to the shell.
  3. Parse the command
    .
    The syntax for the command
    line is trivial. The parser begins at the left sid
    e of the command

line and
scans until it sees a whitespace character (such as space, tab, or NEWLINE). The
first word
is the command
name, and
subsequent word
s are the parameters.

  1. Find
    the file.
    The shell provid
    es a set of environment variables for each user. These variables are first
    d
    efined
    in the user’s login file (for the bash shell this is /home//.bashrc) but
    they can be mod
    ified
    at any time by using the set command
    . The PATH environment
    variable (whose value can be viewed
    by typing “echo $PATH” at the bash shell) is an
    ord
    ered
    list of absolute pathnames specifying where the shell should
    search for command

files. If the login file has a line such as:
set path=(.:/bin:/usr/bin)
then the shell will first look in the current d
irectory (since the first full pathname is “:.”),
then in /bin, and
finally in /usr/bin. If no file with the same name as the command
can be
found
in any of the specified
d
irectories, then the shell notifies the user that it is unable to
find
the command
.

  1. Prepare the parameters.
    The shell simply passes the parameters to the command
    as the argv array of pointers to
    strings.
  2. Execute the command
    .
    The shell must execute the executable program in the specified
    file. UNIX shells have
    always been d
    esigned
    to protect the original process from crashing when it executes a
    program. That is, since a command
    can be any executable file, then the process that is
    executing the shell must protect itself in case the executable file contains a fatal error.
    Somehow, the shell wants to launch the executable so that even if the executable contains a
    fatal error (which d
    estroys the process executing it), then the shell will remain unharmed
    .
    The Bourne shell uses multiple processes to accomplish this by using the UNIX-style system calls
    fork(), execvp(), and
    wait().
    fork()
    The fork() system call creates a new process that is a copy of the calling process, except that it has
    its own copy of the memory, its own process ID (with the correct relationships to other processes), and

its own pointers to shared
kernel entities such as file d
escriptors. After fork() has been called
, two
processes will execute the next statement after the fork() in their own ad
d
ress spaces: the parent and
the
child
. If the call succeed
s, then in the parent process fork() returns the process ID of the newly created

child
process and
in the child
process, fork() returns a zero value.
execvp()
The execvp() system call changes the program that a process is currently executing. It has the form:
execvp(char* path, char* argv[]);
The path argument is the pathname of a file that contains the new program to be executed
. The
argv[] array is a list of parameter strings. When a process encounters the execvp() system call,
the next instruction it executes will be the one at the entry point of the new executable file. Thus the
kernel performs a consid
erable amount of work in this system call. It must:
• find
the new executable file,
• load
the file into the ad
d
ress space currently being used
by the calling process (overwriting and

d
iscard
ing the previous program),
• set the argv array and
environment variables for the new program execution, and
start the
process executing at the new program’s entry point.
Various versions of execvp() are available at the system call interface, d
iffering in the way that
parameters are specified
(for example, some use a full pathname for the executable file and
others d
o
not).
wait()
The wait() system call is used
by a process to block itself until the kernel signals the process to
execute again, for example because one of its child
processes has terminated
. When the wait() call
returns as a result of a child
process’s terminating, the status of the terminated
child
is returned
as a
parameter to the calling process.
When these three system calls are used
, here is the cod
e skeleton that a shell might use to execute a
command
:
// Child
if (fork() == 0)
execvp(fullpathname, argv);

// Parent
else
int status=0;
wait(&status);
cout << “Child exited with status of ” << status << endl; Putting a Process in the Background In the normal parad igm for executing a command , the parent process creates a child process, starts it executing the command , and then waits until the child process terminates. If the “and ” (“&”) operator is used to terminate the command line, then the shell is expected to create the child process and start it executing on the d esignated command but not have the parent wait for the child to terminate. That is, the parent and the child will execute concurrently. While the child executes the command , the parent prints another prompt to stdout and waits for the user to enter another command line. If the user starts several command s, each terminated by an “&”, and each takes a relatively long time to execute, then many processes can be running at the same time. When a child process is created and started executing on its own program, both the child and the parent expect their stdin stream to come from the user via the keyboard and for their stdout stream to be written to the character terminal d isplay. Notice that if multiple child processes are running concurrently and all expect the keyboard to d efine their stdin stream, then the user will not know which child process will receive d ata on its stdin if d ata is typed to the keyboard . Similarly, if any of the concurrent processes write characters to stdout, those characters will be written to the terminal d isplay wherever the cursor happens to be positioned . The kernel makes no provision for giving each child process its own keyboard or terminal (unlike a wind ows system, which controls the multiplexing and d emultiplexing through explicit user actions). I/O Redirection A process, when created , has three d efault file id entifiers: stdin, stdout, and stderr. These three file id entifiers correspond to the C++ objects cin, cout, and cerr. If the process read s from stdin (using cin) then the d ata that it receives will be d irected from the keyboard to the stdin file d escriptor. Similarly, d ata received from stdout (using cout) and stderr (using cerr) are mapped to the terminal d isplay. The user can red efine stdin or stdout whenever a command is entered . If the user provid es a filename argument to the command and preced es the filename with a “less than” character “<” then the shell will substitute the d esignated file for stdin; this is called red irecting the input from the d esignated file. The user can red irect the output (for the execution of a single command ) by preced ing a filename with the right angular brace character, “>”. For example, a command
such as
lmojela@skylek:~$ we < main.cpp > program.stats
will create a child
process to execute the “we” command
. Before it launches the command
, however, it
will red
irect stdin so that it read
s the input stream from the file main.cpp and
red
irect stdout so
that it writes the output stream to the file program.stats.
The shell can red
irect I/O by manipulating the child
process’s file d
escriptors. A newly created
child

process inherits the open file d
escriptors of its parent, specifically the same keyboard
for stdin and

the terminal d
isplay for stdout and
stderr. (This expand
s on why concurrent processes read
and

write the same keyboard
and
d
isplay.) The shell can change the child
‘s file d
escriptors so that it read
s
and
writes to files rather than to the keyboard
and
d
isplay.
Each process has its own file d
escriptor table in the kernel. When the process is created
, the first entry
in this table, by convention, refers to the keyboard
(stdin) and
the second
two refer to the terminal
d
isplay.
Next, the C++ runtime environment and
the kernel manage stdin, stdout, and
stderr so that:
C++ Object Name Alternative Linux
Name
File Descriptor Table
Index
Device Referred To
cin std
in 0 Keyboard

cout std
out 1 Terminal Display
cerr std
err 2 Terminal Display
If you want to perform I/O red
irection, you need
to connect stdin to a file which can be read
for
input instead
of the keyboard
, or stdout to a file which can accept output instead
of the terminal
d
isplay.
Let’s consid
er stdin specifically. The key to getting I/O red
irection to work for stdin is to replace
the contents of the file d
escriptor entry for stdin, currently the keyboard
, with the file d
escriptor
entry for the file you want to use for input.
To accomplish this, you need
to access the file d
escriptors for stdin and
the file you want to read

from. You know how to get the file d
escriptor for stdin, that’s just the number “0”. But how d
o you
get the file d
escriptor for the file you want to use for input? Unfortunately, you can’t get the file
d
escriptor d
irectly from a C++ ifstream object. Instead
you have to issue the following Linux
system call:
int newstdin = open(“main.cpp”,O_RDONLY);
The second
argument to the open() call is an “open this file for read
ing only” flag. Now newstdin
has a file d
escriptor for the file “main.cpp”.
Next you have to replace the contents of the stdin file d
escriptor with the “main.cpp” file
d
escriptor. Use the following sequence of Linux system calls:
close(0);
dup(newstdin);
close(newstdin);
The close(0) call wipes out the contents of file d
escriptor table entry 0, which is the table entry for
stdin. The d
up(newstd
in) call copies the contents of the newstd
in file d
escriptor table entry to the
first empty table entry in the file d
escriptor table. In this case, that will be entry 0. Now stdin will use
the file “main.cpp” instead
of the keyboard
for input. There are now two file d
escriptors which are
linked
to “main.cpp”. The final close(newstd
in) cleans things up so that only the stdin file d
escriptor
is linked
to main.cpp.
A similar set of calls is used
if you want to red
irect std
out to an output file:
int newstdout = open(“program.stats”,O_WRONLY|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
close(1);
dup(newstdout);
close(newstdout);
Make sure that when you use this open call you includ
e ALL of the flags I’ve listed
here. Otherwise the
red
irection won’t work.
Shell Pipes
The pipe is a common IPC (interprocess communication) mechanism in Linux and
other versions of
UNIX. By d
efault, a pipe employs asynchronous send
and
blocking receive operations. Optionally, the
blocking receive operation may be changed
to be a nonblocking receive. Pipes are FIFO (first-in/first
out) buffers d
esigned
with an API that resembles as closely as possible a low level file I/O interface. A
pipe may contain a system-d
efined
maximum number of bytes at any given time, usually 4KB. As
ind
icated
in Figure 2.2, a process can send
d
ata by writing it into one end
of the pipe and
another can
receive the d
ata by read
ing the other end
of the pipe.
Information Flow Through a Pipe
A pipe is represented
in the kernel by a file d
escriptor, just like a file. A process that wants to create a
pipe calls the kernel with a call of the following form:
int thePipe[2];
pipe(thePipe);
The kernel creates the pipe as a kernel FIFO d
ata structure with two file id
entifiers. In this example
cod
e, thePipe[0] is a file pointer (an ind
ex into the process’s open file table) to the read end
of the
pipe and
thePipe[1] is file pointer to the write end
of the pipe.
For two or more processes to use pipes for IPC, a common ancestor of the processes must create the
pipe prior to creating the processes. Because the fork() command
creates a child
that contains a
copy of the open file table (that is, the child
has access to all of the files that the parent has alread
y
opened
), the child
inherits the pipes that the parent created
. To use a pipe, it need
s only to read
and

The post CS4421 Operating Systems II appeared first on My Assignment Online.

Plagiarism Free Assignment Help

Expert Help With This Assignment — On Your Terms

Native UK, USA & Australia writers Deadline from 3 hours 100% Plagiarism-Free — Turnitin included Unlimited free revisions Free to submit — compare quotes
Scroll to Top