Processes in Unix

Download Report

Transcript Processes in Unix

operating
systems
Unix Processes
operating
systems
The Process ID
Unix identifies each process with a unique
integer called a process ID.
The process that executes the request for
creating a process is called the parent of
the process.
The created process is called the child
process.
operating
systems
Getting the process ID
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main ( )
{
printf(“Process ID: %ld\n”, (long)getpid());
printf(“Parent process ID: %ld\n”, (long)getppid());
printf(“Process owner ID: %ld\n”, (long)getuid());
}
exit (0);
ex1.c
operating
systems
Process Termination
Normal Termination
return from main calls exit
calling exit cleans up, closes all files
calling _exit leaves files open, does
Abnormal Termination
calling abort
terminated by a signal
not flush buffers
operating
systems
atexit( ) function
ANSI C allows us to register up to 32 functions that are
automatically called by exit( ).
int atexit (void (*func) (void));
_exit
User Process
return
_exit
exit
handler
user
functions
call
call & return
main( )
return
exit( )
exit
handler
call
C start-up
routine
_exit
exec
Kernel
I/O
cleanup
operating
systems
More Process Termination
When a process terminates, the operating system
de-allocates the resources held by the process, and
notifies other processes of its demise.
When a process exits, its orphaned children are adopted
by the init* process. If it’s parent process is not waiting
for it when it terminates, it becomes a zombie process.
* launchd on OS-X (do ps –ax)
operating
systems
Process Creation using fork
makes a copy of the parent’s system image and
begins executing at the current instruction
child process gets a copy of the parent’s data
space, heap and stack. They do not share memory!
The fork() call returns 0 to the child process, and
the pid of the child to the parent process.
Whether the parent or the child executes first,
once the child is created, depends upon the
scheduling algorithm used by the kernel.
operating
systems
Children Also Inherit*
user and group IDs
controlling terminal
current working directory
root directory
file mode creation masks
all open file descriptors
signal mask and dispositions
environment
attached shared memory segments
resource limits
* Keep in mind that the child process does not share
these, but has copies of its own that it inherits from
the parent.
operating
systems
Children Are Different
Because …
They return a different value from fork
They have different process IDs
They have different parent process IDs
The child does not inherit file locks set by the parent
The set of pending signals for the child is set to
the empty set
Try the following code
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
What happened?
x = 0;
fork( );
x = 1;
printf(“I am process %ld and my x value is %d\n”,
(long)getpid( ), x);
return 0;
}
fork.c
Try the following code
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
x = 0; At this point, there is one process.
fork( );
x = 1;
printf(“I am process %ld and my x value is %d\n”,
(long)getpid( ), x);
return 0;
}
Try the following code
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
x = 0;
A child process is created. It has the identical
fork( ); code and execution environment as the parent.
x = 1;
printf(“I am process %ld and my x value is %d\n”,
(long)getpid( ), x);
return 0;
}
fork.c
Try the following code
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
x = 0; Both processes now execute this code, changing its own
value of x, printing it, and exiting. The order depends
fork( ); on the scheduler.
x = 1;
printf(“I am process %ld and my x value is %d\n”,
(long)getpid( ), x);
return 0;
}
Try the following code
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
Could the child process do something different from
x = 0;
fork( ); the parent process?
x = 1;
printf(“I am process %ld and my x value is %d\n”,
(long)getpid( ), x);
return 0;
}
Yes ... test the return value of the fork( )
#include <stdio.h>
#include <unistd.h>
int main ( )
{
int x;
pid_t childpid;
x = 0;
childpid = fork( );
if ( childpid == -1)
{
perror(“failed to fork a new process”);
return 1;
}
fork1.c
if (childpid == 0)
{
x = 1;
printf(“I am the child, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
What happened?
else
{
x = 2;
printf(“I am the parent, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
return 0;
}
Both processes return here from
the fork( ) call. However, the return
value to the parent is the pid of the
child process. The return value to
the child is zero.
if (childpid == 0)
{
x = 1;
printf(“I am the child, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
else
{
x = 2;
printf(“I am the parent, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
return 0;
}
if (childpid == 0) The child process
{
executes this block,
x = 1;
printf(“I am the child, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
And ...
else
the parent process
{
executes this block
x = 2;
printf(“I am the parent, my ID = %ld, my x = &d\n”,
(long)getpid( ), x);
}
return 0;
}
operating
systems
What does the following code do?
int main(int argc, char *argv[ ] )
{
pid_t childpid = 0;
int i, n;
n = atoi ( argv[1] );
for ( i = 1; i < n; i++)
if (childpid = fork( ) )
break;
fprintf(stderr, “i:%d process ID:%ld parent ID: %ld child ID %ld\n”,
i, (long)getpid( ), (long)getppid( ), (long)childpid );
return 0;
operating
systems
Creates a chain of n processes
int main(int argc, char *argv[ ] )
{
pid_t childpid = 0;
int i, n;
process 1
process 2
n = atoi ( argv[1] );
for ( i = 1; i < n; i++)
if (childpid = fork( ) ) // non zero means I’m the parent
process 3
break;
fprintf(stderr, “i:%d process ID:%ld parent ID: %ld child ID %ld\n”,
i, (long)getpid( ), (long)getppid( ), (long)childpid );
the parent breaks
out of the loop …
the child continues
through the loop one return 0;
more time, forks a new
child process and becomes
the parent for the next
iteration.
process 4
a chain
for n = 4
chain.c
operating
systems
Creating a fan of child processes
int main(int argc, char *argv[ ] )
{
pid_t childpid = 0;
int i, n;
parent
process
child 3
child 1
child 2
n = atoi ( argv[1] );
for ( i = 1; i < n; i++)
if (childpid = fork( ) <= 0 )
break;
fprintf(stderr, “i:%d process ID:%ld parent ID: %ld child ID %ld\n”,
i, (long)getpid( ), (long)getppid( ), (long)childpid );
the child breaks
return 0;
out of the loop …
the parent continues
through the loop creating
a new child. This continues
until the loop condition
is satisfied.
Run the example fan1.c a few times.
What do you notice about the output?
The order of the output is not in the order expected.
This is because the parent just exits when it is done,
the children may not have finished at this point. The
order of execution depends upon how the processes
were scheduled to run. Can we make the parent wait
to exit until all of its children have finished?
The wait( ) system call
operating
systems
When a fork ( ) occurs, both the parent and the child proceed
with execution at the point of the fork.
If the parent wants to wait for the child to finish before
continuing, it executes a wait( ). wait() causes the caller to
stop until
the child terminates, or
the caller receives a signal
wait( ) returns immediately if there are no children executing
the return value of wait() is:
the pid of the terminating child process, or
-1 if there is no child to wait for or a signal occurred
errno = ECHILD indicates there was no child
errno = EINTR indicates there was a signal
operating
systems
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *status);
returns -1 or pid of the
terminating child.
the return status of the child is
stored in status. Test with the macros
WIFEXITED
- true if child terminated normally
WIFSIGNALED – true if child terminated abnormally
because it failed to catch a signal
WIFSTOPPED - true if a child is currently stopped
operating
systems
Handling a wait that is interrupted by a signal
#include <errno.h>
#include <sys/wait.h>
pid_t r_wait (int* status_loc)
{
int retval;
if the wait returns because of a signal, stay in this loop
while ( ( (retval = wait(status_loc) ) == -1 && (errno == EINTR) );
return retval;
}
Fixing the fan program . . .
n = atoi(argv[1]);
for (i = 1; i < n; i++)
if ( (childpid = fork( ) ) <= 0)
break;
while (r_wait(NULL) > 0);
fprintf(stderr, ... )
see fan2.c
Fixing the fan program . . .
n = atoi(argv[1]);
for (i = 1; i < n; i++)
if ( (childpid = fork( ) ) <= 0)
break;
while (r_wait(NULL) > 0);
fprintf(stderr, ... )
what happens if you replace the while statement with
r_wait(NULL);
see fan2.c
How many outputs are possible from this program?
int main ( )
{
pid_t childpid;
childpid = fork( );
if (childpid == -1) {
perror(“failed to fork”);
return 1;
}
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
}
int main ( )
{
pid_t childpid;
1. The fork fails, the program will output
childpid = fork( );
“failed to fork”
if (childpid == -1) {
perror(“failed to fork”);
return 1;
}
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
}
int main ( )
{
pid_t childpid;
2. The child prints its message, but then
childpid = fork( );
the parent catches a signal before the
if (childpid == -1) {
child actually returns.
perror(“failed to fork”);
return 1;
I am a child 3427
}
a signal may have interrupted this call
child code
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
parent code
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
}
int main ( )
{
pid_t childpid;
3.
child code
parent code
}
The child prints its message and returns.
The wait returns normally
childpid = fork( );
if (childpid == -1) {
perror(“failed to fork”); I am a child 3427
return 1;
I am a parent 3424
}
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
int main ( )
{
pid_t childpid;
child code
parent code
}
4.
The parent catches a signal and prints
its “signal” message before the child
prints its message.
childpid = fork( );
if (childpid == -1) {
perror(“failed to fork”); A signal may have interrupted the call
I am child 3427
return 1;
}
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
int main ( )
{
pid_t childpid;
child code
parent code
}
5.
The parent catches a signal before the
child prints its message, but it prints
its “signal” message after the child prints.
childpid = fork( );
if (childpid == -1) {
I am child 3427
perror(“failed to fork”);
return 1;
A signal may have interrupted this call
}
if (childpid == 0)
fprintf(stderr, I am a child %ld\n”, (long)getpid( ) );
else if (wait(NULL) != childpid)
fprintf(stderr, “A signal may have interrupted the wait\n”);
else
fprintf(stderr, I am a parent %ld\n”, (long)getpid( ) );
return 0;
Status Values
The status parameter of the wait( ) system call is an int*
If it is not NULL, the return status of the child is stored
in the integer variable pointed to. The child status is
returned by
return n
exit (n)
A zero value indicates that the child process exited normally.
Keep in mind that the wait( ) system call blocks
if there are any child processes executing. What
if we want to see if a child process has terminated,
but not block?
operating
systems
waitpid
pid_t waitpid (pid_t pid, int *stat_loc, int options);
the process to be waited for, or
-1 if you want to wait for any child
process to finish.
returns the pid of the child
or -1 if there is an error,
or 0 if there are children to be waited for,
but none of them are done
the interesting option is
WNOHANG which causes
waitpid to return even if there
is there is no child for which
status is available.
operating
systems
#include <sys/types>
#include <sys/wait.h>
#include <errno.h>
int status;
pid_t waited_for_pid;
…
while (waited_for_pid = waitpid (-1, &status, WNOHANG) )
if ((waited_for_pid == -1) && (errno != EINTR)
break;
this code segment waits for any child, without blocking,
if there are no children with status available. It stays in the
loop if the waitpid is interrupted by a signal.
operating
systems
Polling
A parent can wait for a child to finish by using the wait
or waitpid calls.
However, what does a child do when it wants to wait for
the parent to finish?
while (getppid( ) != 1)
sleep(1);
why?
this type of code is called polling, and burns up cpu time
To avoid race conditions and polling, we use signals
operating
systems
Zombie Process
If a child process completes before its parent does,
some vestige of the process has to hang around so
that the parent can determine its termination status
with a wait( ).
This is called a zombie process.
operating
systems
Orphan Processes
What happens if the parent process exits before its
child process does. The child has to have a parent, so
it is adopted by the init process.
operating
systems
Reminder - forks are used
When a process wants to duplicate itself so that the
parent and the child can execute different parts of the
program simultaneously. This is common for server
software.
When a process wants to execute a different program.
This is common for shells.
operating
systems
exec( )
fork ( ) – creates a copy of the calling process.
exec ( ) – overlays the calling process with a new process.
Traditional approach is for the parent to fork( ) a new
process, which then does an exec to overlay the process
with the new program. The parent then stays around.
The exec copies a new executable into the process image.
The program text, variables, stack, and the heap are
overwritten.
Process A
Process B
program A
fork( );
rhtj,nn vio nlk;jh eahg
jkfsdajhfk.ADFSkfdnkZX
dfjklA:LDhlADjlkd
fakJLc
dsakl;naS
fork( )
{
exec( )
‘
fdsnjhkjsd
}
hjfhksa
}
jkds,n,ds
mkfl
jmhjsd fhjsdf
‘jkfg {
kjl;sdfdsf
fdsjklsdf
program c
program A
exec( )
rhtj,nn vio nlk;jh eahg
jkfsdajhfk.ADFSkfdnkZX
rhtj,nn
vio nlk;jh eahg
dfjklA:LDhlADjlkd
jkfsdajhfk.ADFSkfdnkZX
fakJLc
dfjklA:LDhlADjlkd
dsakl;naS
fakJLc
fork( );
dsakl;naS
{
hlhkasf
exec( );
‘ jghji fdsnjhkjsd
} fhnjkjsdf
fdsnjhkjsd
hjfhksa
fht9
}
hjfhksa
jkds,n,ds
}mkfl
jkds,n,ds
jmhjsd fhjsdf
mkfl
‘jkfg {
jmhjsd
fhjsdf
kjl;sdfdsf
‘jkfg
{
fdsjklsdf
kjl;sdfdsf
fdsjklsdf
execl( )
operating
systems
This family of functions is useful when the number of
command line arguments is known at compile time
#include <unistd.h>
first command
line argument
second command
line argument
int execl(const char *path, const char *arg0, const char *arg1, …
const char *argn, NULL);
path to the
executable
final command
line argument
A NULL
pointer
Example
operating
systems
int main ( )
{
pid_t childpid;
int stat;
}
if ((childpid = fork() == -1)
{
This is child code. It replaces
perror(“Error in fork.”);
the current process with /bin/ls
exit(1);
}
else if (childpid == 0)
{
if (execl(“/bin/ls”, “ls”, “-l”, NULL) < 0)
{
perror(“Error in exec.”);
exit(1);
}
}
else if (childpid != wait(&stat))
{
perror(“A Signal occurred before the child exited.”);
exit(1);
This is parent
}
code.
return (0);
dols.c
operating
systems
Variations of execl
#include <unistd.h>
This variation uses the PATH
variable to locate the executable
int execlp(const char *file, const char *arg0,
const char *arg1, … , const char *argn, NULL);
int execle (const char *path, const char *arg0,
const char *arg1, …, const char argn, NULL,
char *const envp[]);
This variation passes a pointer to
an array of strings that holds a new
environment for the command.
operating
systems
execv( )
This family of functions is used to pass command
line arguments to the new process
#include <unistd.h>
int execv(const char *path, char *const argv[]);
path to the
executable
an argument array, just as
if these had come from the
command line . You have to
build this!
execv also has execvp and execve variations.
create the executable myexec.
Then typing myexec ls –l will
result in this function calling ls -l
operating
systems
execvp constructs the
pathname using the
PATH environment variable.
int main (int argc, char *argv[])
argv[0] is the first argument
{
when this command is executed,
pid_t childpid;
so &argv[1] points to the two
int status;
tokens ls and -l
if ((childpid = fork()) == -1)
{
perror("The fork failed");
exit(1);
}
else if (childpid == 0)
{
if (execvp(argv[1], &argv[1]) <0)
{
perror("The exec failed");
exit(1);
}
}
else
{
the parent waits for the child
while(childpid != wait(&status))
to terminate.
{
if ((childpid == -1) && (errno != EINTR))
break;
}
}
return (0);
}
operating
systems
A Shell
is a command interpreter which prompts for commands,
reads the commands from standard input, forks children
to execute the commands, and waits for the children to
finish.
When you end a command with &, the shell creates the
child process, but does not wait for it to finish. The child
is known as a background process.
operating
systems
A daemon is
a background process that runs indefinitely.
operating
systems
Terminal Log-ins
/etc/ttys contains one line per terminal device
when the system is bootstrapped, the kernel creates
process 1, the init * process
init reads /etc/ttys and for each terminal device in the file,
it forks a new process and execs the getty program
* launchd on OS-X (do ps –ax)
operating
systems
init
fork (once per terminal)
init
exec
getty
init
exec
getty
...
init
exec
getty
operating
systems
getty opens a terminal device and issues a login message
When a user name is entered, getty execs login
login gets the password and checks to see if all is well
If it is, login changes to the user’s home directory,
changes ownership of the terminal, initializes the
environment, and execs the shell specified for the user.
init
operating
systems
fork
init
exec
getty
exec
login
exec
shell
operating
systems
Process Groups
In addition to having a process ID, each process belongs to
a process group. A process group is a collection of one
or more processes.
Session
A session is a collection of one or more process groups.
operating
systems
An Example using
redirection
This program exploits the fact that open file
descriptors are preserved across fork and
exec calls.
upper
operating
systems
#include <stdio.h>
#include <ctype.h>
int main( )
{
int ch;
}
while ((ch = getchar( ) ) != EOF)
{
putchar(toupper(ch));
}
exit(0);
This program is written as a filter. It reads in text and converts
it to upper case.
upper.c
operating
systems
What if we want to invoke this filter
from inside of a program?
#include <unistd.h>
#include <stdio.h>
int main ( int argc, char *argv[])
{
char *filename;
filename = argv[1];
// now call freopen to open the file on standard input
if (!freopen(filename, "r", stdin)) Closes the file descriptor for stdin
{
Then re-opens the file as stdin
printf("Could not re-direct file to stdin\n");
exit(1);
}
// now exec the upper program
execl( "./upper", "upper", 0);
}
// since the exec replaces the current program, the following
// lines are not executed unless the exec fails.
perror("could not exec ./upper");
exit(3);
execupper.c