Processes, Implementation, Signals & Threads
by Vincent
Van, Angie Yen, and Iverin Hui
Process vs. Thread Descriptors
Descriptor Characteristics |
New Process |
New Thread |
pid |
D |
S |
ppid |
D |
S |
accounting* |
C |
S/C |
-name*, priority* |
C |
C |
memory state |
C |
S |
stack* |
C |
D |
register* |
C |
D |
state* |
C |
D |
event handler |
C |
S |
file table |
C |
S |
file structure |
S |
S |
D = different, S = same /
shared, C = copied
* = included in Thread Descriptor
Process Implementation
How does the OS multiplex the machine among application
processes? --->"hardware process" (book lingo)
"Most" of the time, the CPU is running application code
System calls give the OS control; trap instructions (interrupt-like)
|
Interrupts give OS control; timer interrupt goes off periodically
Hardware saves register state on a stack
OS moves register state into the process descriptor
|
|
OS code executes
OS decides next process to run
|
Restore memory state
Restore the registers
|
|
Process runs
| | |
Signals & Asynchronous Events
Signal: notifies a process of an asynchronous event
Asynchronous: an event that happens independently of the process's
code
Examples: killing uncooperative process (infinite loop), sleeping
indefinitely
How should we implement signals?
With files? *NO*
But let's try...
Every process has a file called /proc/pid/signal
Process periodically checks this file for data
If the file is nonempty, exit()
Problem with this
implementation:
==>NOT ROBUST
|
(If the process goes into an infinite loop, it can't check the
file. Since this puts the process in charge of checking for signals :
BAD)
|
We're looking for a way to kill a process
that's out of control. So we can't use anything the process is in charge
of.
Signal syscalls
int sigaction
(int sig, struct sigaction *sa, ...)
==>this function sets up a
handler for a signal in the current process
The struct sigaction
*sa:
struct sigaction
{
void(*sa_handler) (int);
// func
in your code that should be called when the sig
happens
};
Signal Keywords:
sig =
SIGKILL - kill process
SIGSTOP - make process sleep
SIGSEGV - dereferenced a NULL
pointer or other memory error ("segmentation violation")
SIGHUP - output file closed
SIGCHLD - a child process exited
(default is ignored)
int kill (pid_t,
int sig)
==>sends a signal
Why use a signal for a SEGV?
Hardware knows an error occured, but
software does not.
Want to clear up zombie processes?
|
A. Wait for all children B. OR design a SIGCHLD handler |
An example
handler:
void handle_child (int sig)
{
waitpid(0,0,0)
} NO -malloc
-write (because the memory state cannot be changed)
int main()
{
struct sigaction sa;
sa sa_handler
= handle child...;
sigaction(SIGCHLD, &sa,
..)
}
Suppose that process A calls kill(p, SIGHUP); and process B also calls kill(p, SIGHUP);?
|
We need a struct sigaction to
guarantee delivery (even if 2 signals are very close in time) Old way:
race condition, unreliable, success depends on how close signals
are in time |
Threads
Processes are isolated (self-contained)
Threads are like non-isolated processes
Threads decouple processing
- execution - from state -
memory/files
Multiple virtual processors in the same program & memory space
simultaneously
Processes: independent memory state and run
state
Threads: share memory state
Advantages
+ share common data -> less
copying overhead
+ share memory (easier communication, work on shared
data structures)
+ take advantage of multiple CPUs' power
Disadvantages
- synchronization (multiple
execution engines changing the same memory state simultaneously)
-
race conditions!
- locking, makes programming instantly
harder
Generally, threads are used...
to deal with lots of data
because I/O is so slow! (overlap I/O wake)