 |
Mode: user & kernel mode |
 |
Space
 |
kernel space: system wide & shared
by all processes
 |
kernel segment (kernel code, data
structure, kernel address map) |
|
 |
user space: per process & private
 |
u area, text, data, stack, shared
memory region |
|
|
 |
Kernel accesses kernel space and u area |
 |
User process can only access its own user
space except the u area |
 |
Execution context
 |
process context: kernel executes on
behalf of a process, e.g., system call |
 |
system context: kernel executes
system-wide task, e.g., disk or network interrupt handling |
|
 |
Mode and context are orthogonal |
 |
User address space: text segment, data seg,
stack seg, shared memory regions |
 |
Control information: u area, proc structure,
kernel stack, address translation map(= page & segment table) |
 |
Credentials: UID, GID |
 |
Environment Variables: saved at the bottom of
user stack |
 |
Hardware context: saved in PCB, which is, in
turn, saved at the u area |
 |
User level library package such as C-thread of
Mach or POSIX pthread provides functions such as:
 |
creation, synchronization, scheduling,
and destruction of user threads |
|
 |
User thread can be implemented on top of
process or LWP |
 |
Kernel is unaware of user threads and only
schedules in terms of processes or LWP (Fig 3-5 (a) & (b)) |
 |
User thread becomes possible because
 |
a process can have multiple execution
contexts and switch among those contexts using setjmp, longjmp, and
signal |
 |
the execution context can be saved and
restored at the user level as Condor does |
|
 |
Resource
 |
Private user stack, user-level register
context, and other state info like signal mask |
|
 |
Synchronization at user level eliminates the
thread switch at kernel level
 |
When a thread needs to block on a lock,
thread library puts the thread into the corresponding queue and
schedules another user thread |
|
 |
Blocking system calls
 |
A user thread makes a blocking system
call -> |
 |
Replaced the call to non-blocking calls
-> |
 |
Another thread gets scheduled -> |
 |
The non-blocking system call completes
-> interrupts occurs -> |
 |
The blocked thread gets scheduled again |
|
 |
Pros
 |
Performance (Table 3-1) |
 |
Virtually unlimited number of threads
-> the number of threads = application's concurrency level ->
better programming model |
 |
A system can have several thread
libraries each of which can be used for different purposes |
|
 |
Cons
 |
No true parallelism without support from
kernel thread |
 |
No protection among threads within a
process or a LWP -> threads should be programmed as cooperative |
 |
Information barrier between library and
kernel
 |
A thread (on an LWP) holding lock
can be scheduled out by kernel to schedule in another thread
(on another LWP) waiting for the lock |
 |
A thread (on an LWP) of high
priority can be scheduled out by kernel to schedule in another
thread (on another LWP) of lower priority |
|
|
 |
Combines the advantages of kernel and user
thread |
 |
Basic principle: have close integration
between user threads and kernel |
 |
Clear mission allocation
 |
Kernel: processor allocation |
 |
Library: schedule threads by
multiplexing them on processors given from kernel |
|
 |
Library -> Kernel: information that affects
processor allocation
 |
request for processors |
 |
relinquish of processors |
|
 |
Kernel -> Library: information that affects
thread scheduling
 |
allocation and deallocation of
processors |
 |
status changes of threads, which affects
scheduling |
|
 |
upcall: call from kernel to library |
 |
scheduler activation: virtual processor given
to library by kernel
 |
Logically similar to LWP: has user &
kernel stack |
 |
Main difference from LWP: one-to-one
relationship to processors |
|
 |
Handling of blocking operation of threads
 |
Thread running in kernel blocks -> |
 |
Kernel preempts the
processor(=activation) -> |
 |
Kernel creates a new activation and make
an upcall to library -> |
 |
Library saves the context of the
deactivated thread from the old activation -> |
 |
Library schedules another thread on the
new activation -> |
 |
Blocking operation completes -> |
 |
Kernel makes a new activation (or
preempts an activation from the process) -> |
 |
Kernel notifies the library of the
blocking operation having been completed (and preemption of the
activation) -> |
 |
Library puts the thread having been
blocked (and the thread of preempted activation) into ready list
-> |
 |
Library select a thread from ready list
to run and attaches it to the new activation |
|
 |
Each LWP is bound to a kernel thread during
its life-time |
 |
Traditional proc and u area of process model
should be changed
 |
proc: hold all per-process data,
including the process-specific part of traditional u area |
 |
lwp data: per-LWP data
 |
user level register context:
meaningful only when the LWP is not running |
 |
system call arguments, results,
and error code |
 |
signal handling information |
 |
resource usage and profiling data,
virtual time alarms, user time and CPU usage |
 |
pointer to the kernel thread attached |
 |
pointer to the proc structure |
|
|
 |
All LWP share signal handlers but each may
have individual sigmask |
 |
Traps--synchronous signals like segfault--are
delivered to the causing LWP |
 |
Interrupt--asynchronous signals can be
delivered to any LWP that has not masked the signal |
 |
Semaphores
are rather low level and error prone: need careful program, otherwise race
condition or deadlock |
 |
Monitor
 |
Can
be thought as an object wrapped by mutex.down() & mutex.down() |
 |
Condition
variable instead of integer semaphore |
|
 |
Diff
between condition variable & (integer) semaphore
 |
up()
Vs. signal()
 |
up:
increase the semaphore value -> excess up operations get
remembered |
 |
signal:
wake up a process waiting for the condition variable, no
effect if no process is waiting |
|
 |
down()
Vs. wait()
 |
down:
block the caller only if value == 0 |
 |
wait:
always block the caller until next signal(), also release
mutex |
|
|
 |
Bounded
Buffer - in Java syntax
 |
synchronized
insert(object item) {
 |
if
(buffer is full)
 |
nonfull.wait(); |
|
 |
addElement(item); |
 |
nonempty.signal(); |
|
 |
} |
 |
synchronized
delete() {
 |
if
(buffer is empty)
 |
nonempty.wait(); |
|
 |
deleteElement(); |
 |
nonfull.signal(); |
|
 |
} |
|
 |
Notes
on signal
 |
When
a waiter is awaken, the condition it was waiting for is guaranteed
-> |
 |
No
process should sneak between signal() and return from wait() ->
 |
Signaling
mechanism should be absolutely reliable & Only one waiter
should be awaken & The signaler should block immediately
-> |
 |
Upon
waiter leaving the monitor, signaler should be awaken or be
able to contend to grab mutex released by the waiter |
|
 |
Inefficient:
unnecessary context switches signaler->waiter->signaler |
|
 |
Notify
 |
Wake
up a waiter, but signaler does NOT block -> |
 |
Other
processes can sneak into between signal and the return from wait
call -> |
 |
Waiter
should check whether the condition is still true after returning
from wait call
 |
'if'
block of the above code snippet should be replaced by 'while' |
|
|
 |
Monitor
in Java
 |
No
monitor modifier for the entire object. Instead each method can be
tagged with 'synchronized' |
 |
Only
one implicit condition variable per object -> notifyAll instead
of notify |
|
 |
Metrics
 |
Waiting time - the amount of time the
job is runnable and not running |
 |
Penalty ratio - elapsed-time /
ideal-time |
|
 |
FCFS
 |
Favors long burst, i.e., CPU-bound jobs
 |
Short job's penalty ratio is too
big |
|
 |
Favoring long job leads convoy effect |
 |
Convoy effect
 |
short jobs are waiting behind a
long job |
 |
CPU busy & I/O devices idle
<-> CPU idel & I/O busy |
|
|
 |
Shortest Job First
 |
Optimal with respect to average waiting
time |
 |
Don't know how long the job will run
-> Approx. algorithm needed |
 |
The length of burst is guessed from
history
 |
guess = ( guess + previousBurst )
/ 2 |
|
|
 |
Shortest Remaining Job First
 |
Preemptive version of SJF |
 |
Min ( shortest job in the ready queue,
remaining time for the job currently running to finish ) |
|
 |
Round-robin & Process Sharing
 |
Round-robin becomes process sharing when
quantum becomes zero |
|
 |
Priority scheduling
 |
Both preemptive and nonpreemptive
scheduling are possible |
 |
Priority can be given externally or
calculated internally |
 |
Multi-Level Feedback Queue
 |
Short jobs are favored |
 |
Long jobs get longer quantum and
run with less context switch |
|
|
 |
Analysis
 |
Queuing theory: arrival rate vs service
rate |
|