0% found this document useful (0 votes)
11 views

Chapter 7 Process Management Part 2

Uploaded by

owuor bilgates
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

Chapter 7 Process Management Part 2

Uploaded by

owuor bilgates
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 53

Chapter 7

Process Management Part 2


Changing Directories – chdir()
• Every process has a current working directory that is used
when processing a relative pathname.
• A child process inherits its current working directory
from its parent.
– For example, when a utility is executed from a shell, its process
inherits the shell’s current working directory.
• To change a process’ current working directory, use chdir(),
which works as follows:
int chdir( const char* pathname )
• chdir() sets a process’ current working directory to the
directory pathname.
• The process must have execute permission from the
directory to succeed.
2
Changing Directories - Example
• In this program, the process prints its current working
directory before and after executing chdir() by executing
pwd using the system() library routine

$ cat mychdir.c ---> list the source code


#include <stdio.h>
main()
{
system(“pwd”); /* Display current working directory */
chdir(“/”); /* Change working directory to root directory */
system(“pwd”); /* Display new working directory */
chdir(“/home/glass”); /* Change again */
system(“pwd”); /* Display again */
}
$ mychdir ---> execute the program.
/home/glass
/
/home/glass
$_
3
Changing Priorities
• Every process has a priority value between -20 and +19
that affects the amount of CPU time that it’s allocated.
• In general, the smaller the priority value, the faster the
process will run.
• Only super-user and kernel processes can have a
negative priority value, and login shells start with a priority
value of zero.
• A child process inherits its priority value from its parent
and may change it by using nice():
System Call: int nice(int delta)

• nice() adds delta to a process’ current priority value.


• Only a superuser may specify a delta that leads to a
negative priority value. 4
Changing Priorities
• If a delta beyond the limits (-20 and +19) is specified, the
priority value is truncated to the limit.
• If “nice()” succeeds, it returns the new priority value;
otherwise it returns a value of -1.
• This return value causes an ambiguity, since a value of -1
is a legal priority value.

5
Changing Priorities - Example
• In this program, the process executed ps commands before
and after a couple of nice() calls.

$ cat mynice.c ---> list the source code.


#include <stdio.h>
main()
{
printf(“original priority \n”);
system(“ps”); /* Execute a ps */
nice(0); /* Add 0 to my priority */
printf(“running at priority 0 \n”);
system(“ps”); /* Execute another ps */
nice(10); /* Add 10 to my priority */
printf(“running at priority 10 \n”);
system(“ps”); /* Execute the last ps */
}

6
Changing Priorities - Example
• When the process’ priority value became nonzero, it was
flagged with an “N” by ps, together with the sh and ps
commands that it created due to the system() library call.

$ mynice ---> execute the program.


original priority
PID TT STAT TIME COMMAND
15099 p2 S 0:00 -sh(sh)
15206 p2 S 0:00 a.out
15207 p2 S 0:00 sh -c ps
15208 p2 R 0:00 ps
running at priority 0 ---> adding a priority value of zero doesn’t
---> change the priority.
PID TT STAT TIME COMMAND
15099 p2 S 0:00 -sh(sh)
15206 p2 S 0:00 a.out
15209 p2 S 0:00 sh -c ps
15210 p2 R 0:00 ps
running at priority 10 ---> adding a priority value of 10 makes them7
---> run slower.
Changing Priorities - Example
PID TT STAT TIME COMMAND
15099 p2 S 0:00 -sh(sh)
15206 p2 S N 0:00 a.out
15211 p2 S N 0:00 sh -c ps
15212 p2 R N 0:00 ps

$_

8
Accessing User and Group IDs
• The system calls that allow you to read a process’ real and
effective IDs:
uid_t getuid()
uid_t geteuid()
gid_t getgid()
gid_t getegid()

• getuid() and geteuid() return the calling process’ real and


effective user IDs, respectively.
• getgid() and getegid() return the calling process’ real and
effective group IDs, respectively.
• The ID numbers correspond to the user and group IDs listed
in the following files
– /etc/passwd - user IDs
– /etc/group – group IDs

• These calls always succeed. 9


Accessing User and Group IDs
• The system calls that allow you to set a process’ real and
effective IDs:
int setuid(uid_t id)
int seteuid(uid_t id)
int setgid(gid_t id)
int setegid(gid_t id)

• setuid() and seteuid() set the calling process’ real and


effective user ID, respectively.
• setgid() and setegid() set the calling process’ real and
effective group IDs, respectively, to the specified value.
• These calls succeed only if executed by a super-user, or if ID
is the real or effective user or group ID of the calling process.
• They return a value of 0 if successful; otherwise, they return
a value of -1.
10
Threads
• Multiple processes are expensive to create, either a
new or by copying an existing process with the “fork()”
system call.
• A thread is an abstraction allows multiple “threads of
control” in a single process space.
• Separate tasks are able to share some resources in a
process, such as memory space or an open device.
• The term light-weight processes used interchangeably
with thread.

11
Thread Management
• Four major functions make up the common
thread-management capabilities in most
implementations:
– Create: Create a thread.
– Join: Suspend and wait for a created thread to
terminate.
– Detach: Allow the thread to release its
resources to the system when it finishes and
not to require a join.
– Terminate: Return resources to the process. 12
Redirection
• When a process forks, the child inherits a copy of
its parent’s file descriptors.
• When a process execs, all nonclose-on-exec file
descriptors remain unaffected, including the
standard input, output, and error channels.

13
Redirection
• To perform the redirection, the shell performs the following
series of actions:
– The parent shell forks and then waits for the child shell to terminate.
– The child shell opens the file “ls.out”, creating it or truncating it as
necessary.
ls > ls.out
– The child shell then duplicates the file descriptor of “ls.out” to the
standard-output file descriptor, number 1, and the closes the
original descriptor of “ls.out”.
– The child shell then exec’s the ls utility.
– Since the file descriptors are inherited during an “exec()”, all of
the standard output of ls goes to “ls.out”.
– When the child shell terminates, the parent resumes.
• The parent’s file descriptors are unaffected by the child’s actions, as each process
14
maintains its own private descriptor table.
Redirection – Example
• This program that does approximately the same kind of
redirection as a UNIX shell does.

$ cat redirect.c ---> list the program.


#include <stdio.h>
#include <fcntl.h>
main( argc, argv )
int argc;
char* argv[];
{
int fd;
/* Open file for redirection */
fd = open( argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0600 );
dup2( fd, 1 ); /* Duplicate descriptor to standard output */
close(fd); /* Close original descriptor to save descriptor space
*/
execvp( argv[2], &argv[2] ); /* Invoke program; will inherit stdout
*/
perror(“main”); /* Should never execute */
} 15
Redirection - Example
$ redirect ls.out ls -l ---> redirect “ls -l” to “ls.out”.
$ cat ls.out ---> list the output file.
total 5
-rw-r-xr-x 1 gglass 0 Feb 15 10:35 ls.out
-rw-r-xr-x 1 gglass 449 Feb 15 10:35 redirect.c
-rwxr-xr-x 1 gglass 3697 Feb 15 10:33 redirect
$-

16
Disk Usage
• Disk usage - uses a novel technique for counting the number
of non-directory files in hierarchy.
• When the program is started, its first argument must be
the name of the directory to search.
• It searches through each entry in the directory, spawning
off a new process for each entry.
• Each child process either exits with 1 if its associated file
is a non-directory file, or repeats the process, summing
up the exit codes of its children and exiting with the total
count.
• This creates a large number of processes, which is not
particularly efficient.
17
Disk Usage - Example

$ cat count.c ---> list the program

#include <stdio.h>
#include <sys/file.h>
#include <sys/dir.h>
#include <sys/stat.h>
long processFile();
long processDirectory();
main( argc, argv )
int argc;
char* argv[];
{
long count;
count = processFile( argv[1] );
printf(“Total number of non-directory files is %ld \n”, count );
return ( /* EXIT_SUCCESS */ 0 );
}

18
Disk Usage - Example

long processFile( name )


char* name;
{
struct stat statBuf; /* To hold the return data from stat() */
mode_t mode;
int result;

result = stat( name, &statBuf ); /* Stat the specified file */


if ( result == -1 ) return(0); /* Error */

mode = statBuf.st_mode; /* Look at the file’s mode */


if ( S_ISDIR( mode ) ) /* Directory */
return ( processDirectory(name) );
else
return ( 1 ); /* A nondirectory file was processed */
}

19
Disk Usage - Example

long processDirectory( dirName )


char* dirName;
{
int fd, children, i, charsRead, childPid, status;
long count, totalCount;
char fileName[100];
struct dirent dirEntry;

fd = open( dirName, O_RDONLY ); /* Open directory for


reading */
children = 0; /* Initialize child process count */
while(1) /* Scan directory */
{
charsRead = getdents( fd, &dirEntry, sizeof( struct dirent ) );
if ( charsRead == 0 ) break; /* End of directory */
if ( strcmp( dirEntry.d_name, “.” ) != 0 &&
strcmp( dirEntry.d_name, “..” ) != 0 )
{
20
Disk Usage - Example

if ( fork() == 0 ) /* Creates a child to process dir. entry */


{
sprintf( fileName, “%s/%s”, dirName, dirEntry.d_name );
count = processFile( fileName );
exit( count );
}
else ++children; /* Increment count of child processes */
}
lseek( fd, dirEntry.d_off, SEEK_SET ); /* Jump to next dir. entry */
}
close(fd); /* Close directory */
totalCount = 0; /* Initialize file count */
for ( i=1; i<= children; i++ ) /* Wait for children to terminate */
{
childPid = wait( &status ); /* Accept child’s termination code */
totalCount += ( status >> 8 ); /* Update file count */
}
return ( totalCount ); /* Return number of files in directory */
}

21
Disk Usage - Example

$ ls -F ---> list current directory.


a.out* disk.c fork tmp/ zombie*
background myexec.c myfork.c mywait.c
background.c myexit.c orphan.c mywait*
count* myexit* orphan* zombie.c

$ ls tmp ---> list only subdirectory


a.out* disk.c myexit.c orphan.c
background.c myexec.c myfork.c mywait.c
zombie.c

$ count . ---> count regular files from “.”.


Total number of non-directory files is 25

$-

22
Signals
• Program must sometimes deal with unexpected or
unpredictable events, such as :
– a floating point error
– a power failure
– an alarm clock “ring”
– the death of a child process
– a termination request from a user ( i.e., Control-C )
– a suspend request from a user ( i.e., Control-Z )

• These kind of events are sometimes called interrupts, as


they must interrupt the regular flow of a program in
order to be processed.
• When UNIX recognizes that such an event has occurred, it
sends the corresponding process a signal.
23
Signals
• There is a unique, numbered signal for each possible
event.
-
• For example, if a process causes a floating point error, the
kernel sends the offending process signal number 8:

Signal Process
#8

Floating point error signal

24
Signals
• Any process can send any other process a signal, as long
as it has permission to do so.
• A programmer may arrange for a particular signal to be
– ignored or
– processed by a special piece of code called a signal handler.

• The process that receives the signal


1) suspends its current flow of control,
2) executes the signal handler,
3) and then resumes the original flow of control when the signal handler
finishes.

25
Signals
• Signals are defined in
“/usr/include/sys/signal.h”.
• The default handler usually performs one of the
following actions:
– terminate the process and generate a core file (dump)
– terminate the process without generating a core image
file (quit)
– ignore and discard the signal (ignore)
– suspend the process (suspend)
– resume the process

26
List of Signals
• Here’s a list of the System V predefined signals, along with
their respective macro definitions, numerical values, and
default actions, as well as a brief description of each:

Macro # Default Description

SIGHUP 1 quit hang up


SIGINT 2 quit interrupt
SIGQUIT 3 dump quit
SIGILL 4 dump illegal instruction
SIGTRAP 5 dump trace trap( used by debuggers )
SIGABRT 6 dump abort
SIGEMT 7 dump emulator trap instruction
SIGFPE 8 dump arithmetic execution
SIGKILL 9 quit kill( cannot be caught, blocked, or ignored )
SIGBUS 10 dump bus error( bad format address )

27
List of Signals
Macro # Default Description

SIGSEGV 11 dump segmentation violation(out-of-range address)


SIGSYS 12 dump bad argument to system call
SIGPIPE 13 quit write on a pipe or other socket with no one
to read it.
SIGALRM 14 quit alarm clock
SIGTERM 15 quit software termination signal(default signal
sent by kill )
SIGUSR1 16 quit user signal 1
SIGUSR2 17 quit user signal 2
SIGCHLD 18 ignore child status changed
SIGPWR 19 ignore power fail or restart
SIGWINCH 20 ignore window size change
SIGURG 21 ignore urgent socket condition
SIGPOLL 22 ignore pollable event

28
List of Signals
Macro # Default Description

SIGSTOP 23 quit stopped(signal)


SIGSTP 24 quit stopped(user)
SIGCONT 25 ignore continued
SIGTTIN 26 quit stopped( tty input )
SIGTTOU 27 quit stopped( tty output )
SIGVTALRM 28 quit virtual timer expired
SIGPROF 29 quit profiling timer expired
SIGXCPU 30 dump CPU time limit exceeded
SIGXFSZ 31 dump file size limit exceeded

29
Terminal Signals
• The easiest way to send a signal to a
foreground process is by pressing Control-C or
Control-Z from the keyboard.
• When the terminal driver (the piece of software
that supports the terminal) recognizes that
Control-C was pressed, it sends a SIGINT signal
to all of the processes in the current
foreground job.
• Similarly, Control-Z causes it to send a SIGTSTP
signal to all of the processes in the current
foreground job.
30
Requesting an Alarm Signal: alarm()
• The default handler for the SIGALRM signal displays the
message “Alarm clock” and terminates the process.
• Here’s how alarm() works:
unsigned int alarm( unsigned int count )

• alarm() instructs the kernel to send the SIGALRM


signal to the calling process after count seconds.
• If an alarm had already been scheduled, that alarm is
overwritten.
• If count is 0, any pending alarm requests are
cancelled.
• alarm() returns the number of seconds that remain
until the alarm signal is sent. 31
Alarm Signal: alarm() - Example
• This program that uses alarm() system call

$ cat alarm.c ---> list the program.


#include <stdio.h>
main()
{
alarm(3); /* Schedule an alarm signal in three seconds */
printf(“Looping forever… \n”);
while(1)
printf(“This line should never be executed \n”);
}

$ alarm ---> run the program.


Looping forever…
Alarm clock ---> occurs three seconds later.
$-

32
Handling Signals: signal()
• signal() allows a process to specify the action that
it will take when a particular signal is received.
• It has the following syntax:
void(*signal(int sigCode, void (*func)(int))) (int)

• The parameter sigCode specifies the number of the


signal that is to be reprogrammed.
• func may be one of several values:
– SIG_IGN, which indicates that the specified signal should be
ignored and discarded.
– SIG_DFL, which indicates that the kernel’s default handler should
be used.
– an address of a user-defined function, which indicates that the
33
function should be executed when the specified signal arrives.
Handling Signals: signal()
• The valid signal numbers are stored in
“/usr/include/signal.h”.
• The signals SIGKILL and SIGSTP may not be
reprogrammed.
• A child process inherits the signal settings from
its parent during a “fork()”.
• signal() returns the previous func value
associated with sigCode if successful; otherwise, it
returns a value of -1.
• The signal() system call may be used to override
the default action. 34
Handling Signals: signal()
• pause() suspends the calling process and
returns when the calling process receives a
signal.
• It has the following syntax:
int pause(void)

• It is most often used to wait efficiently for an


alarm signal.
• pause() doesn’t return anything useful.

35
Handling Signals: signal() - Example
• Some changes are made to the previous program so that it catches and
processes the SIGALRM signal efficiently
• The program uses a user-defined signal handler, “alarmHandler()” which
uses signal()

$ cat handler.c ---> list the program.


#include <stdio.h>
#include <signal.h>

int alarmFlag = 0; /* Global alarm flag */


void alarmHandler(); /* Forward declaration of alarm handler */
/***************************************************/
main()
{
signal( SIGALRM, alarmHandler ); /* Install signal handler */
alarm(3); /* Schedule an alarm signal in three seconds */
printf(“Looping…\n”);
while( !alarmFlag ) /* Loop until flag set */
{
pause(); /* Wait for a signal */
}
printf(“Loop ends due to alarm signal \n”); 36
}
Handling Signals: signal() - Example
/*************************************************/
void alarmHandler()
{
printf(“An alarm clock signal was received \n”);
alarmFlag=1;
}

$ handler ---> run the program.


Looping…
An alarm clock signal was received ---> occurs three seconds later.
Loop ends due to alarm signal
$_

37
Protecting Critical Code
• Critical pieces of code can be protected against
Control-C attacks and other such signals.
• It can be restored after the critical code has
executed.

38
Protecting Critical Code - Example
• Here’s the source code of a program that protects itself
against SIGINT signals:

$ cat critical.c ---> list the program.


#include <stdio.h>
#include <signal.h>
main()
{
void (*oldHandler) () /* To hold old handler value */
printf(“I can be Control-C’ed \n”);
sleep(3);
oldHandler = signal(SIGINT, SIG_IGN); /* Ignore Control-C */
printf(“I’m protected from Control-C now\n”);
sleep(3);
signal(SIGINT, oldHandler); /* Restore old handler */
printf(“I can be Control-C’ed again \n”);
sleep(3);
printf(“Bye! \n”);
} 39
Protecting Critical Code - Example
$ critical ---> run the program.
I can be Control-C’ed
^C ---> Control-C works here.

$ critical ---> run the program again.


I can be Control-C’ed
I’m protected from Control-C now
^C
I can be Control-C’ed again
Bye!

$_

40
Sending Signals: kill()
• A process may send a signal to another process by using
the kill() system call.
• kill() is a misnomer, since many of the signals that it can
send to do not terminate a process.
• It’s called kill() because of historical reasons;
• the main use of signals when UNIX was first designed was
to terminate processes.

41
Sending Signals: kill()
• Syntax:
– int kill( pid_t pid, int sigCode )

• kill() sends the signal with value sigCode to the


process with PID pid.

• kill() succeeds and the signal is sent as long as at


least one of the following conditions is satisfied:
– The sending process and the receiving process have
the same owner.

– The sending process is owned by a super-user.

42
Sending Signals: kill()
• There are a few variations on the way that kill()
works:
– If pid is zero, the signal is sent to all processes in the
sender’s process group.

– If pid is -1 and the sender is owned by a super-user,


the signal is sent to all processes, including the sender.

– If pid is -1 and the sender is not owned by a super-


user, the signal is sent to all the processes owned by
the same owner as that of the sender, excluding the
sending process.

– If the pid is negative, but not -1, the signal is sent to all
the processes in the process group. 43
Death of Children
• When a child terminates, the child process sends
its parent a SIGCHLD signal.

• A parent process often installs a handler to deal


with this signal;
– this handler typically executes a wait() system call to accept
the child’s termination code and let the child dezombify.

• The parent can choose to ignore SIGCHLD


signals, in which case the child dezombifies
automatically.

44
Death of Children - Example
• The program works by performing the following
steps:
1. The parent process installs a SIGCHLD handler that is
executed when its child process terminates.

2. The parent process forks a child process to execute the


command.

3. The parent process sleeps for the specified number of


seconds. When it wakes up, it sends its child process a
SIGINT signal to kill it.

4. If the child terminates before its parent finishes


sleeping, the parent’s SIGCHLD handler is executed,
causing the parent to terminate immediately. 45
Death of Children - Example
• Here’s the source code for and sample output from the program.

$ cat limit.c ---> list the program.


#include <stdio.h>
#include <signal.h>
int delay;
void childHandler();
/************************************************/
main( argc, argv )
int argc;
char* argv[];
{
int pid;
signal( SIGCHLD, childHandler ); /* Install death-of-child handler */
pid = fork(); /* Duplicate */
if ( pid == 0 ) /* Child */
{
execvp( argv[2], &argv[2] ); /* Execute command */
perror(“limit”); /* Should never execute */
} 46
Death of Children - Example
else /* Parent */
{
sscanf( argv[1], “%d”, &delay ); /* Read delay from command-line */
sleep(delay); /* Sleep for the specified number of seconds */
printf(“Child %d exceeded limit and is being killed \n”, pid );
kill( pid, SIGINT ); /* Kill the child */
}
}
/***************************************************/
void childHandler() /* Executed if the child dies before the parent */
{
int childPid, childStatus;
childPid = wait(&childStatus); /* Accept child’s termination code */
printf(“Child %d terminated within %d seconds \n”, childPid, delay);
exit(/* EXIT SUCCESS */ 0);
}

47
Death of Children - Example
$ limit 5 ls ---> run the program; command finishes OK.
a.out alarm critical handler limit
alarm.c critical.c handler.c limit.c
Child 4030 terminated within 5 seconds.

$ limit 4 sleep 100 ---> run it again; command takes too long
Child 4032 exceeded limit and is being killed.

$-

48
Suspending and Resuming Processes
• The SIGSTOP and SIGCONT signals suspend and
resume a process, respectively.

• They are used by the UNIX shells that support


job control to implement built-in commands like
stop, fg, and bg.

49
Suspending and Resuming Processes
– Example
• The main program created two children that both
entered an infinite loop and displayed a message
every second.

• The main program waited for three seconds and


then suspended the first child.

• After another three seconds, the parent restarted


the first child, waited a little while longer, and then
terminated both children.

50
Suspending and Resuming Processes
– Example
$ cat pulse.c ---> list the program.
#include <signal.h>
#include <stdio.h>
main()
{ int pid1;
int pid2;

pid1 = fork();

if (pid1== 0) /* First child */


{
while(1) /* Infinite loop */
{
printf(“pid1 is alive \n”);
sleep(1);
}
}
pid2 = fork(); /* Second child */
51
Suspending and Resuming Processes
– Example
if ( pid2 == 0 )
{
while(1) /* Infinite loop */
{
printf(“pid2 is alive \n”);
sleep(1);
}
}
sleep(3);
kill( pid1, SIGSTOP ); /* Suspend first child */
sleep(3);
kill( pid1, SIGCONT ); /* Resume first child */
sleep(3);
kill( pid1, SIGINT ); /* Kill first child */
kill( pid2, SIGINT ); /* Kill second child */
}

52
Suspending and Resuming Processes
– Example
$ pulse ---> run the program.
pid1 is alive ---> both run in first three seconds.
pid2 is alive
pid1 is alive
pid2 is alive
pid1 is alive
pid2 is alive
pid2 is alive ---> just the second child runs now.
pid2 is alive
pid2 is alive
pid1 is alive ---> the first child is resumed.
pid2 is alive
pid1 is alive
pid2 is alive
pid1 is alive
pid2 is alive

53

You might also like