You are on page 1of 39

Lecture- 12

Some File System call and Process


Related System Call
Processes In UNIX
• The only active entities in a UNIX system are the processes.
• Each process runs a single program and initially has a single
thread of control. In other words, it has one program
counter, which keeps track of the next instruction to be
executed.
• UNIX is a multiprogramming system, so multiple,
independent processes may be running at the same time.
• Each user may have several active processes at once, so on
a large system, there may be hundreds or even thousands
of processes running.
Processes
• Processes are created in UNIX in an especially simple manner.
• The fork system call creates an exact copy of the original
process.
• The forking process is called the parent process. The new
process is called the child process.
• The parent and child each have their own, private memory
images.
• If the parent subsequently changes any of its variables, the
changes are not visible to the child, and vice versa.
• Open files are shared between parent and child.
• if a certain file was open in the parent before the fork, it will
continue to be open in both the parent and the child
afterward.
Init Process
• Every process except process 0 is created when another process
executes the fork system call.
• Every process has one parent process, but a process can have
many child processes.
• The kernel identifies each process by its process number, called
the process ID (PID).
• Process 0 is a special process that is created “by hand” when the
system boots; after forking the child process a child process
(process 1), process 0 becomes the swapper process.
• Process 1, known as init, is the ancestor of every other child
process in the system and enjoys a special relationship with them.
Process Creation
• The only way for user to create a new process in the
UNIX operating system is to invoke the fork system
call.
• The syntax of fork system call is:
pid=fork();
• On return from the fork system call, the two processes
have identical copies of their user-level context except
from the return value pid.
• In the parent process, pid is the child process pid; in
the child process, pid is 0.
Kernel does the following sequence of opeartion for
fork:
1. It allocates a slot in the process table for the new
process.
2. It assigns a unique ID number to the child process.
3. It makes a logical copy of the context of the parent
process.
4. It increment file and inode table counters for files
associated with the process.
5. It assigns the child process to a ready to run state
6. It returns the ID number of the child to the parent
process, and a 0 value
fork() system call (Cont’d)
• The new process inherits several characteristics of the
old process. Among the characteristics inherited are:
– The environment.
– All signal settings.
– The set user ID and set group ID status.
– The time left until an alarm clock signal.
– The current working directory and the root directory.
– The file creation mask as established with umask().
• fork() returns zero in the child process and non-zero
(the child's process ID) in the parent process.
Example 1 using fork
using namespace std;
int globalVariable = 2;   
main()
{
   string sIdentifier;
   int    iStackVariable = 20;
   pID = fork();
   if (pID == 0)                // child
   {
      // Code only executed by child process
      sIdentifier = "Child Process: ";
      globalVariable++;
      iStackVariable++;
    }
else if (pID < 0)            // failed to fork
   {
        cerr << "Failed to fork" << endl;
        exit(1);
   }
    else                                   // parent
    {
      // Code only executed by parent process
        sIdentifier = "Parent Process:";
    }
     // Code executed by both parent and child.     
    cout << sIdentifier;
    cout << " Global variable: " << globalVariable;
    cout << " Stack variable: "  << iStackVariable << endl;
}
Example using fork()
#include <fcntl.h>
int fdrd, fdwt;
Char c;
Main( argc, argv)
int argc;
char *argv[];
{
if (argc!=3)
exit(1);
if ((fdrd= open(argv[1], o_RDONLY))==-1)
exit(1);
if ((fdwt= creat(argv[2], 0666))==-1)
exit(1);
fork();
/* both process execute same process*/
rdwrt();
exit(0);
}
Rdwrt()
{
for(;;)
{
if (read(fdrd, &c,1)!=1)
return;
write(fdwt, &c, 1);
}
}
Explanation of Example
• A user should invoke the program with two parameters, the name of an
existing file and the name of a new file to be created.
• The process opens the existing file, creats the new file and assuming it
encounters no errors, forks and creates a child process.
• Each process can access private copies of the global variables fdrt, fdwt, and
c and private copies of the stack variables argc and argv, but neither process
can access the variables of the other process.
• The parent and child processes call the function rdwrt, independently and
execute a loop, reading one byte from source file and writing it to the target
file.
• The file descriptor fdrd and fdwt both refer to the same file descriptor table
respectively.
• Therefore they never read or write same file offset values, as the kernel
increment them after each read and write call.
Process Termination
• Processes on a UNIX system terminate by executing the exit system call.
• The syntax of the call is
exit(status);
• Where the value of status is returned to the parent process for its
examination.
• Processes may call exit explicitly or implicitly at the end of a program.
• The startup routine linked with all C programs calls exit when the
program returns from the main function, the entry point of all programs.
• Alternatively, the kernel may invoke exit internally for a process on
receipt of uncaught signals. If so, the value of the status is the signal
number.
• The process 0 (the SWAPPER) and 1 (init) exist throughout the lifetime of
a system.
Example of exit system call
main()
{
int child;
if((child=fork())==0)
{
printf(“child PID %d\n”, getpid());
pause(); /* suspend execution until signal */
}
/*parent */
printf(“child PID %d\n”, child);
exit(child);
}
Explanation of example
• A process creates a child process, which prints
its PID and execute the pause system call,
suspending itself until it receives a signal.
• The parent prints the child’s PID and exits,
returning the child’s PID as its status code.
• If the exit call were not present, the startup
routine calls exit when the process returns
from main.
Awaiting Process Termination
• A process can synchronize its execution with the termination of a child
process by executing the wait system call.
• The syntax for the system call is
pid= wait (stat_addr);
• Where pid is the process ID of the zombie child, and stat_addr is the
address in user space of an integer that will contain the exit status code
of the child.
• wait(): Blocks calling process until the child process terminates. If child
process has already teminated, the wait() call returns immediately. if
the calling process has multiple child processes, the function returns
when one returns.
• waitpid(): Options available to block calling process for a particular
child process not the first one.
Zombie Process
• When a process finishes execution, it will have an exit status to report
to its parent process.
• Because of this last little bit of information, the process will remain in
the operating system’s process table as a zombie process, indicating
that it is not to be scheduled for further execution, but that it cannot be
completely removed (and its process ID cannot be reused) until it has
been determined that the exit status is no longer needed.
• When a child exits, the parent process will receive a SIGCHLD signal to
indicate that one of its children has finished executing.
• The parent process will typically call the wait() system call at this point.
• That call will provide the parent with the child’s exit status, and will
cause the child to be reaped, or removed from the process table.
Example of Wait and Ignoring Death Of Child
Signal
#include <signal.h>
main (argc, argv)
Int argc;
char *argv[];
{
int i, ret_val, ret_code;
if(argc>1)
signal(SIGCLD, SIG_IGN); /*ignore death of children*/
for (i=0; i<15; i++)
{
if (fork()==0)
{ /*child proc here */
printf(“child proc %x\n”, getpid());
exit(i);
}
}
ret_val = wait(&ret_code);
printf(“wait ret_val %x ret_code %x\n”, ret_val, ret_code);
}
Explanation of Example
• User will different results when invoking the program with
or without parameters.
• When user invokes the program without parameters (argc
is 1, the program name).
• The parent process creates 15 child processes that
eventually exit with return code i, the value of the loop
variable when the child was created.
• The kernel executing wait for the parent , finds a zombie
child process and returns its process ID and exit code.
• If the user invoke the above program with a parameter
(argc >1), the parent process calls signal to ignore “death
of child” signals.
Explanation cont….
• When the child process exits, it sends a “death of child” signal to the parent
process; the parent process wakes up because its sleep in wait is at an
interruptible priority.
• When the parent process eventually runs, it finds that the outstanding signal was
for “death of child”, but because it ignores “death of child” signals, the kernel
removes the entry of the child from the process table and continuous executing
wait as if no signal had happened.
• The kernel does the above procedure each time the parent receives a “death of
child” signal, until it finally goes through the wait loop and finds the parent has no
children.
• The difference between the two invocations of the program is that the parent
process waits for the termination of any child process in the first case but waits
for the termination of all child processes in the second case.
• If the process creates many children but never executes wait, the process table
will become cluttered with zombie children when the children exit.
Summary

• Fork
- Creates a duplicate of the calling process
- The result is two processes: parent and child
- Both continue executing from the same point on
• Exit
- Orderly program termination
- Unblocks waiting parent
• Wait
- Used by parent
- Waits for child to finish execution
Invoking Other Programs
• The exec system call invokes another program, overlaying the memory
space of a process with a copy of an executable file.
• The contents of the user-level context that existed before the exec call
are no longer accessible afterward except for exec’s parameters, which
kernel copies from the old address space to the new address space.
• The syntax for the system call is
execve(filename, argv, envp)
• Where filename is the name of the executable file being invoked, argv is
a pointer to an array of character pointers that are parameter to the
executable program and envp is a pointer to an array of character
pointers that are the environment of the executed program.
• There are several library functions that call the exec system call such as
execl, execv, execle, and so on. All call execve eventually
Understand exec in Detail
• When a program uses command line parameters as in
main (argc, argv)
• The array argv is a copy of the argv parameter of exec.
• The character strings is the environment are of the
form “name=value” and may contain useful
information for programs, such as the user’s home
directory and a path of directories to search for
executable programs.
• Processes can access their environment via the global
variable environ, initialized by the C startup routine.
Algorithm for exec
Algorithm exec
Input: filename
parameter list
environment variable list
Output:none
{
get file inode (namei);
verify file executable, user has permission to execute;
read file headers, check that it is a load module;
copy exec parameters from old address space to system space;
for (every region attached to process)
detach all old region (algorithm detach);
for (every region specified in load module)
{
allocates new region (algorithm allocreg);
attach region (algorithm attachreg);
load region into memory if appropriate (algorithm loadreg);
}
copy exec parameters into new user stack region;
special processing for setuid programs, tracing;
initialize user register save area for return to user mode;
release inode of file (algorithm iput);
}
Explanation of Algorithm
• Exec first access the file by namei to determine if it is an executable, regular
(nondirectory) file, and to determine if the user has permission to execute the
program.
• The kernel then reads the file header to determine the layout of the executable file.
• The kernel first copies the arguments from the old memory space to a temporary
buffer until it attaches the regions for the new memory space.
• After copying the exec parameters to a holding place in the kernel, the kernel
detaches the old regions of the process using algorithm detachreg.
• The kernel allocates and attaches regions for text and data, loading the content of
the executable file into main memory (algorithm allocreg, attachreg, and loadreg).
• Finally, it allocates a region for the process stack, attaches it to the process, and
allocates memory to store the exec parameters.
• Then the kernel set the saved register context for user mode, specially setting the
intial user stack pointer and program counter.
• The kernel takes special action for setuid program and for process tracing.
• Finally it invokes algorithm iput, releasing inode.
Example of exec system call
main()
{
int status;
if (fork()==0)
execl(“/bin/date”, “date”, 0);
wait(&status);
}
Explanation of Exec
• The program creates a child process that invokes the exec system call.
• When the child process is about to invoke the exec call, its test region consists of the
instructions for the programs, its data region consists of the string “/bin/date” and
“date”, and its stack contains the stack frame the process pushed to get to the exec
call.
• The kernel finds the file “/bin/date” in the file system, finds that all user can execute
it, determines that it is an executable load module.
• The kernel then copies the strings “/bin/date” and “date” to an internal holding area
and frees the text ,data and stack regions occupied by the process.
• It allocates new text, data and stack region for the process, copies the instruction
section of the file “/bin/date” into text region, and copies the data section of the file
into the data region.
• The kernel reconstructs the original parameter list (here, the character string “date”)
and puts it in the stack region.
• After the exec call, the child process no longer executes the old program but executes
the program “date”.
• When the “date” program completes, the parent process receives its exit status from
the wait call.
Example: A Simple Shell

• Shell is the parent process


- E.g., bash
• Parses command line
– E.g., “ls –l”
• Invokes child process
– Fork, execvp
• Waits for child
– Wait
execv Example
#include <stdio.h>
#include <unistd.h>
char * argv[] = {“/bin/ls”, “-l”, 0};
int main()
{
int pid, status;
if ( (pid = fork() ) < 0 )
{
printf(“Fork error \n”);
exit(1);
}
if(pid == 0)
{ /* Child executes here */
execv(argv[0], argv);
printf(“Exec error \n”);
exit(1);
}
else /* Parent executes here */
wait(&status);
printf("Hello there! \n”);
return 0;
}
Combined fork/exec/wait
• Common combination of operations
– Fork to create a new child process
– Exec to invoke new program in child process
– Wait in the parent process for the child to complete
• Single call that combines all three
– int system(const char cmd);
• Example
int main()
{
system(“echo Hello world”);
}
Variations of execv
• execv
– Program arguments passed as an array of strings
• execvp
– Extension of execv
– Searches for the program name in the PATH environment
• execl
– Program arguments passed directly as a list
• execlp
– Extension of execv
– Searches for the program name in the PATH environment
Summary
• exec(v,vp,l,lp)
– Does NOT create a new process
– Loads a new program in the image of the calling process
– The first argument is the program name (or full path)
– Program arguments are passed as a vector (v,vp) or list
(l,lp)
– Commonly called by a forked child
• system:
– combines fork, wait, and exec all in one
Signals
• Signals offer another way to transition between Kernel and User Space.
• While system call are synchronous calls originating from User Space,
signals are asynchronous messages coming from Kernel space.
• Signals are always delivered by the Kernel but they can be initiated by:
• other processes on the system (using the kill command/system call)
• the process itself. This includes hardware exceptions triggered by a
process: such as dividing a number by zero or attempting to access a
memory zone that has not been allocated yet, the hardware detects it
and a signal is sent to the faulty program.
• the Kernel. The Kernel also use signals to notify a process of some
system events, the Kernel sends a signal to the process every time a
timer expires (e.g. every 10 seconds).
What to do with signal ?
• So a signal is an asynchronous message, but what happens exactly when
a process receives it?
• Well… it depends. For each signal, a process can instruct the Kernel to
either:
• Ignore this signal: In which case this signal has absolutely no effect on
the process. Ignoring the signal must be explicitly requested before the
signal is delivered.
• Catch this signal: In which case the Kernel will call a custom routine, as
defined by this process when delivering the signal. The process must
explicitly register this custom routine before the signal is delivered. The
signal-catching function is traditionally called a custom signal handler.
• Let the default action apply: For each signal the system defines a default
action that will be called if the process did not explicitly request to
ignore or catch this signal. The default signal handler typically but not
always terminates the process .
Signal Delivery
• The overall dynamic for signal delivery is quite simple:
• When a process receives a signal that is not ignored, the
program immediately interrupts its current execution flow .
• Control is then transferred to a dedicated signal handler, a
custom one defined by the process or the system default.
• Once the signal handler completes, the program resumes
where it was originally interrupted.
• The mechanics used by the Kernel to send a signal are
more involved and consist of two distinct steps:
generating and delivering the signal.
How Kernel Delivers the Signals
• The Kernel generates a signal for a process simply
by setting a flag that indicates the type of the
signal that was received.
• Generating a signal is just a matter of updating
the bit corresponding to the signal type in this bit
field structure. At this stage, the signal is s
• Before transferring control back to a process in
user mode, the Kernel always checks the pending
signals for this process. aid to be pending.
When a pending signal is detected by the Kernel,
the system will deliver the signal by performing one of the
following actions:
• if the signal is SIGKILL the system does not switch back to user
mode. It processes the signal in Kernel mode and terminates
the process.
• if the signal is SIGSTOP the system also stays in Kernel mode. Its
suspends the process and puts it to sleep.
• if the process did not register any custom handler for this
signal, the default system action is taken.
• if the process registered a custom handler for the signal, the
Kernel transfers control to the process and the custom signal
handler is executed in user mode. At this point, the program is
the one responsible for handling the signal properly
Handling Signals
• The kernel handles signals in the context of the process that
receives them so a process must run to handle signals.
• The syntax for the signal system call is
oldfunction= signal (signum, function);
• Where signum is the signal number the process is specifying the
action for, function is the address of the (user) function the
process wants to invoke on receipt of signal, and the return value
oldfunction was the value of function in the most recently
specified call to signal for signum.
• The process can pass the value 1 or 0 instead of a function
address: the process will ignore future occurrence of the signal if
the parameter value is 1 and exit in the kernel on reciept of the
signal if its value is 0.
Sending Signals from Process
• Processes use the kill system call to send
signals.
• The syntax for the kill system call is
Kill(pid, signum);
• Where pid identifies the set of processes to
receive the signal, and signum is the signal
number being sent.
Example of kill system call
#include <signal.h>
main()
{
register int i;
setpgrp ();
for(i=0;i<10;i++)
{
if(fork()==0)
{
/child proc */
if (i & 1)
setpgrp();
printf(“pid =%d pgrp= %d\n”, getpid(), getpgrp());
pause();
}
kill(0, SIGINT);
}
Explanation of Example
• The process reset its process its process group number and
creates 10 child processes.
• When created, each child process has the same process group
number as the parent process, but processes created during odd
iterations of the loop reset their process group number.
• The system calls getpid and getpgrp return the process ID and the
group ID of the executing process, and pause system call
suspends execution of the process until it recieves a signal.
• Finally, the parent execute the kill system call and sends an
interrupt signal to all processes in its process group.
• The kernel sends the signal to the 5 “even” processes that did not
reset their process group, but the 5 “odd” processes continue to
loop.

You might also like