Download slides.02.pdf

Document related concepts

Plan 9 from Bell Labs wikipedia , lookup

Distributed operating system wikipedia , lookup

Unix security wikipedia , lookup

DNIX wikipedia , lookup

Burroughs MCP wikipedia , lookup

Process management (computing) wikipedia , lookup

Transcript
Operating Systems
Design and Implementation
Chapter 02
(version January 30, 2008)
Melanie Rieback
Vrije Universiteit Amsterdam, Faculty of Sciences
Dept. Computer Science
Room R4.23. Tel: (020) 598 7874
E-mail: [email protected], URL:
www.cs.vu.nl/∼melanie/
01
02
03
04
05
00 – 1
Introduction
Processes
Input/Output
Memory Management
File Systems
/
Processes
• Processes, introduction
• Interprocess communication
• IPC problems
• Scheduling
• Processes in MINIX
• Implementation in MINIX
02 – 1
Processes/
Processes
Primitive model: a process represents a user or application and executes a program on behalf of its owner.
Data involved in this processing is retrieved from and
stored on files. Data in files persist over processes.
A process is an invention by an operating system: it is
used as a placeholder for executing programs so that
we can keep a precise account of how program execution is affecting the use of hardware and software
resources.
process = program in execution
02 – 2
Processes/2.1 Introduction to Processes
Concurrent Processes
Important: processes are, in principle, mutually independent. This implies that the CPU can be allocated
in turn to different processes:
One program counter
Process
A
Four program counters
Process
switch
B
C
A
B
C
D
D
C
B
A
D
(a)
Time
(b)
(c)
• You don’t know when the operating system decides to allocate the CPU to a next process.
• Processes are independent: they need explicit
means to communicate with each other.
• Always remember: a process executes a program;
a process is not the same as a program.
02 – 3
Processes/2.1 Introduction to Processes
Process Hierarchies
Basic problem: How do you actually create processes?
In many systems, the operating system creates just
one initial process.
Solution: offer primitives that allow a process to create another process ⇒ leads to a hierarchy of processes:
A
B
D
E
C
F
Question: What would happen when a parent process dies?
02 – 4
Processes/2.1 Introduction to Processes
Process States
Basic idea: If the operating system is to allocate resources such as the CPU to processes, it will have to
select suitable processes for allocation ⇒ we need to
keep track of the state of a process:
Running
1
Blocked
3
2
4
Ready
1. Process blocks for input
2. Scheduler picks another process
3. Scheduler picks this process
4. Input becomes available
• Running: the process is currently executed by
the CPU
• Blocked: the process is waiting for a resource to
come available
• Ready: the process is ready to be selected
The scheduler takes decisions on (de)allocation of
the CPU (transitions 2 & 3).
Question: How can transitions 1 & 4 take place?
02 – 5
Processes/2.1 Introduction to Processes
Scheduler vs Processes
Note: it is the scheduler who decides to (de)allocate
the CPU. This leads to a simple process organization:
Processes
0
1
n–2 n–1
Scheduler
Question: Does this organization have anything to do
with the hierarchical organization of processes?
02 – 6
Processes/2.1 Introduction to Processes
Process Implementation
To implement processes, the OS keeps track of the
process’ state, values of registers, memory usage,
etc. In MINIX:
Kernel
Process management
File management
Registers
Program counter
Program status word
Stack pointer
Process state
Current scheduling priority
Maximum scheduling priority
Scheduling ticks left
Quantum size
CPU time used
Message queue ptrs
Pending signal bits
Various flag bits
Process name
Ptr to text segment
Ptr to data segment
Ptr to bss segment
Exit status
Signal status
Process ID
Parent process
Process group
Children’s CPU time
Real UID
Effective UID
Real GID
Effective GID
File info for sharing text
Bitmaps for signals
Various flag bits
Process name
UMASK mask
Root directory
Working directory
File descriptors
Real UID
Effective UID
Real GID
Effective GID
Controlling TTY
Save area for read/write
System call parameters
Various flag bits
Question: Why do we see redundancy in maintaining states ?
02 – 7
Processes/2.1 Introduction to Processes
Interrupt Handling
Basic idea: To deallocate the CPU in favor of the
scheduler, we use hardware support – whenever the
hardware generates an interrupt (e.g., on account of
a timer), the scheduler “automagically” gets control.
• Associated with each I/O device is a memory location where execution continues when that device generates an interrupt (interrupt vector).
• The interrupt vector contains the start address of
an operating-system provided procedure (interrupt handler). Execution continues with that procedure.
02 – 8
Processes/2.1 Introduction to Processes
Interrupt Handling & Scheduling
1.
2.
3.
4.
5.
6.
7.
8.
9.
Hardware stacks program counter, etc.
Hardware loads new program counter from interrupt vector
Assembly language procedure saves registers
Assembly language procedure sets up a new stack
C interrupt service constructs and sends message
Message-passing code marks waiting message recipient ready
Scheduler decides which process is to run next
C procedure returns to the assembly code
Assembly language procedure starts up new current process
Note: Every time an interrupt occurs, the scheduler
will eventually get control. It acts as the big mediator.
A process can normally not give the CPU to another
process without going through the scheduler.
02 – 9
Processes/2.1 Introduction to Processes
Threads
Idea: Sometimes you want something as a process in
order to structure your application, but you don’t want
the overhead that goes with it ⇒ make use of threads.
Basic idea: Threads also share resources, but only
within the same process ⇒ resource management is
completely left outside the control of the operating system.
02 – 10
Processes/2.1 Introduction to Processes
Threads: Minimal Support
Important: threads fall under the regime of a single
process, and thus reside in the same address space
⇒ all information exchange is through data shared between the threads.
• Each thread has its own stack, processor context,
and state;
• Threads should synchronize through simple primitives (semaphores, monitors);
• Each thread may call upon any service provided
by the operating system; it does so on behalf of
the process to which it belongs.
Computer
Program
counter
Computer
Thread
(a)
Process
(b)
Question: What’s so light-weight about all this? Where
do processes and threads really differ?
02 – 11
Processes/2.1 Introduction to Processes
Threads – Some Problems
• Does the OS keep an administration of threads
(kernel threads), or not (user threads)? Question: What happens if a user-space thread does
a blocking system call?
• What to do when you clone a process: does the
new process get all the threads as well? What
about threads currently blocking on a system call?
• When the OS sends a signal, how can you relate
that signal to a thread? Should you relate it to a
thread?
Conclusion: Designing threads is maybe just a bit
more tedious than you would expect at first thought.
02 – 12
Processes/2.1 Introduction to Processes
Interprocess Communication
The essence: Processes need to communicate with
each other:
• Data needs to be exchanged between processes
• We have to synchronize processes to avoid they
get in each other’s way
• We need to synchronize processes on account of
dependencies
02 – 13
Processes/2.2 Interprocess Communication
Race Conditions
Spooler
directory
Process A
4
abc
5
prog. c
6
prog. n
7
out = 4
in = 7
Process B
• Process A reads in = 7, and decides to append
its file at that position
• Process A is suspended by the OS (because its
time slot expired)
• Process B also reads in = 7, puts its file at that
position. It then sets in to 8 and eventually gets
suspended
• Process A writes its file to position 7
Problem: reading and updating in should be an atomic
action. If it’s not, processes can race each other and
come to the wrong conclusions.
02 – 14
Processes/2.2 Interprocess Communication
Mutual Exclusion
Critical region: A piece of shared memory (like a
common variable) for which we have:
1: No two process may be simultaneously in their
critical regions
2: No assumptions may be made about speeds or
number of CPUs
3: No process running outside its critical region may
block other processes
4: No process should have to wait forever to enter
its critical region
(Non)solutions:
• Disable interrupts: simply prevent that the CPU
can be re-allocated. Pretty blunt. Works for singleCPU systems only.
• Lock variables: Same as with the spooler variable in: you’re just too late to give the variable its
new value
02 – 15
Processes/2.2 Interprocess Communication
Mutual Exclusion
Strict Alteration
while(TRUE) {
while(turn != 0);
critical_region();
turn = 1;
noncritical_region();
}
while(TRUE) {
while(turn != 1);
critical_region();
turn = 0;
noncritical_region();
}
Question: Which assumption is implicitly being made?
Note: This is again a nonsolution: it violates the condition that processes may not block each other.
02 – 16
Processes/2.2 Interprocess Communication
Mutual Exclusion – Peterson
#define FALSE 0
#define TRUE 1
#define N
2
int turn;
int interested[N];
void enter_region(int process) {
int other;
other = 1 - process;
interested[process] = TRUE;
turn = process;
while(turn == process && interested[other]);
}
void leave_region(int process){
interested[process] = FALSE;
}
Note: This solution can “easily” be extended to cover
N > 2 processes.
02 – 17
Processes/2.2 Interprocess Communication
Mutual Exclusion
The TSL Instruction
Finally: Software solutions are just no good in the
general case. Instead, we can actually implement
software into the hardware, and offer the result as a
separate instruction:
enter region:
tsl register,lock
cmp register,#0
jne enter region
ret
| copy lock to register and set lock to 1
| was lock zero?
| if it was non zero, lock was set, so loop
| return to caller; critical region entered
leave region:
move lock,#0
ret
| store a 0 in lock
| return to caller
02 – 18
Processes/2.2 Interprocess Communication
Avoiding Busy Waiting
Problem: The solutions so far let a process keep the
CPU, busy waiting until it can enter its critical region.
Question: With a single CPU, does the other process
ever get a chance to continue?
Solution: Let a process waiting to enter its critical
region return the CPU to the scheduler voluntarily:
void sleep(){
enter_region();
set own state to BLOCKED;
leave_region();
give CPU to scheduler;
}
void wakeup(process){
enter_region();
set state of process to READY;
leave_region();
give CPU to scheduler;
}
Question: Why do we need enter region() and
leave region() here? And why is it OK to use their
busy-waiting implementations?
Question: Why would you want to give the CPU back
to the scheduler when waking up a process?
02 – 19
Processes/2.2 Interprocess Communication
Producer-Consumer
#define N 100
int count = 0;
void producer(){
int item;
while(TRUE){
item = produce_item();
if (count == N) ();
insert_item(item);
count = count + 1;
if (count == 1) (consumer);
}
}
void consumer(){
int item;
while(TRUE){
if (count == 0) ();
item = remove_item();
count = count - 1;
if (count == N-1) (producer);
consume_item(item);
}
}
Question: There’s something wrong here – what?
02 – 20
Processes/2.2 Interprocess Communication
Semaphores
Basic idea: Introduce a special integer type with two
operations down and up that operate on the semaphore
sema:
• down: if sema ≤ 0 then block the calling process
until it becomes positive again; otherwise decrement sema by one.
• up: if there is a process blocking on sema, wake it
up; otherwise increment sema by one.
Note: We’re talking about atomic actions here.
02 – 21
Processes/2.2 Interprocess Communication
Semaphore – Example
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer(){
int item;
while(TRUE){
item
= produce_item();
(&empty);
(&mutex);
insert_item(item);
(&mutex);
(&full);
}
}
void consumer(){
int item;
while(TRUE){
(&full);
(&mutex);
item = remove_item(item);
(&mutex);
(&empty);
consume_item(item);
}
}
02 – 22
Processes/2.2 Interprocess Communication
Monitors
Problem: Semaphores have been heavily criticized
for the chaos they can introduce in programs. What
is generally needed is a more structured approach toward process synchronization ⇒ monitors.
Essence: Think of a monitor as a protected object
whose methods can be invoked only one at a time
02 – 23
Processes/2.2 Interprocess Communication
Monitors – Example
ProdCons {
condition full, empty;
int
count = 0;
void enter(int item) {
if (count == N) (full);
insert_item(item);
count = count + 1;
if (count == 1)
(empty);
}
}
void remove(int* item)
{
if (count == 0) (empty);
item* = remove_item();
count = count - 1; if (count == N-1)
(full);
}
void producer(){
void consumer(){
int item;
int item;
while(TRUE){
while(TRUE){
item
=
produce_item();
;
;
consume_item(item);
}
}
}
}
02 – 24
Processes/2.2 Interprocess Communication
Message-Passing
Idea: processes communicate by sending and receiving only messages:
send( destination, &message );
receive( source, &message );
receive( ANY, &message );
• What to do when the communication channel is
unreliable?
• How can you verify the identity of the other party
(authentication)?
02 – 25
Processes/2.2 Interprocess Communication
Message-passing: example
#define N 100
void producer(){
int
item;
message msg;
}
while(TRUE){
item = produce_item();
receive(consumer, &msg);
build_message(&msg, item);
send(consumer, &msg);
}
void consumer(){
int
item, i;
message msg;
}
for(i = 0; i < N; i++) send(producer, &msg);
while(TRUE){
receive(producer, &msg);
item = extract_item();
send(producer, &msg);
consume_item(item);
}
Question: What’s happening here? Are there any
assumptions about buffering & blocking?
02 – 26
Processes/2.2 Interprocess Communication
Dining philosophers (1/3)
Problem: There are five philosophers, each seated
at a round table before a bowl of spaghetti. A philosopher needs two forks to eat the spaghetti: the one to
her left, and the one to her right.
#define N 5
void philosopher(int i){
while(TRUE){
think();
take_fork( i );
take_fork( (i+1) % N );
eat();
put_fork( i );
put_fork( (i+1) % N );
}
}
02 – 27
Processes/2.3 Classical IPC Problems
Dining philosophers (2/3)
Main issue: how to avoid deadlock & starvation. The
following solution has no deadlock (why not?):
philosopher(int i){
while( TRUE ){
think();
if( i > 0 ) {
take_fork( i );
take_fork( (i+1) % N );
}
else {
take_fork( (i+1) % N );
take_fork( i );
}
eat();
put_fork( i );
put_fork( (i+1) % N );
}
}
Note: There is great code available at
http://www.cs.utk.edu/∼mbeck/classes/cs560/
02 – 28
Processes/2.3 Classical IPC Problems
Dining philosophers (3/3)
philosopher( int i ) {
while( TRUE ) {
think();
take_forks( i );
eat();
put_forks( i );
}
}
take_forks( int i ) {
down( &mutex );
state[i] = HUNGRY;
test( i );
up( &mutex );
down( &start_eating[i] );
}
test( int i ) {
if( state[i]
==
state[LEFT(i)] !=
state[RIGHT(i)] !=
state[i] = EATING;
up( &start_eating[i]
}
}
put_forks( int i ) {
down( &mutex );
state[i] = THINKING;
test( LEFT(i) );
test( RIGHT(i) );
up( &mutex );
}
HUNGRY &&
EATING &&
EATING ){
);
Question: Can there be deadlock or starvation?
02 – 29
Processes/2.3 Classical IPC Problems
Readers/writers (1/2)
Problem: N processes make use of shared data. It is
permitted to let reading processes simultaneously access the data, but only exactly one process for modifications.
typedef int semaphore;
semaphore mutex = 1;
semaphore dbase = 1;
int rcount = 0;
void reader(){
while(TRUE){
down( &mutex );
rcount = rcount + 1;
if( rcount == 1 )
down( &dbase );
up( &mutex );
read_data_base();
down( &mutex );
rcount = rcount - 1;
if( rcount == 0)
up( &dbase );
up( &mutex );
use_data_read();
}
}
02 – 30
void writer(){
while(TRUE){
think_up_data();
down( &dbase );
write_data_base();
up( &dbase );
}
}
Question: What’s so bad
about this solution?
Processes/2.3 Classical IPC Problems
Readers/writers (2/2)
Simple solution: make a FIFO-queue of readers and
writers and let several readers in at the same time.
Guaranteed free of deadlock and starvation.
batch #2
of
readers
writer
batch #1
of
readers
02 – 31
Processes/2.3 Classical IPC Problems
Process scheduling (1/3)
Problem: There are a number of processes ready to
execute their associated program. However, there is
only one CPU available ⇒ one of the processes has
to be selected.
(a)
Long CPU burst
Waiting for I/O
Short CPU burst
(b)
Time
Scheduler: part of an operating system that is responsible for deciding which process may execute,
and when that process may execute ⇒ scheduling algorithm.
02 – 32
Processes/2.4 Process Scheduling
Process scheduling (2/3)
All systems:
• Fairness: no prejudice with respect to processes.
• Policy enforcement: seeing that stated scheduling policy is carried out.
• Balance: all parts of the system are kept busy.
Batch systems:
• Throughput: do as many jobs as possible.
• Turnaround time: have jobs processed as
quick as possible.
• Utilization: keep the CPU busy doing “real”
work.
02 – 33
Processes/2.4 Process Scheduling
Process scheduling (3/3)
Interactive systems:
• Response time: respond to requests quickly.
• Proportionality: meet user’s expectations.
Real-time systems:
• Meeting deadlines: avoid losing data.
• Predictability: avoid quality degradation in multimedia systems.
Question: Suppose we had a really nifty scheduler
that could optimize on everything, but it would take
some time. Is that useful?
02 – 34
Processes/2.4 Process Scheduling
Batch systems (1/2)
Shortest Job First: Suppose we know what the jobs
are that need to be executed, and how long each job
will take ⇒ optimize the turnaround times per job.
Turnaround: delay between job submission and job
completion.
turnaround time
8
8
4
4
8
4
12
16
20
4
4
4
12
4
20
average
14
11
8
Note: Scheduling the shortest job first leads to minimal average turnaround time per job.
02 – 35
Processes/2.4 Process Scheduling
Batch systems (2/2)
Problems:
1: You have to know all jobs in advance ⇒ cheat a
bit: shortest jobs get queued at the head, but running jobs are never preempted. (there’s a problem here – what is it?) However, you will not get
an optimum.
2: You have to know the completion time per job ⇒
use estimates based on previous runs, e.g.
Tk+1 = a · Tk−1 + (1 − a) · Tk
Question: What happens if a = 0 or a = 1?
02 – 36
Processes/2.4 Process Scheduling
Interactive systems
Problem: we don’t want some process to run to completion if that’s going to take a very long time. Instead,
the process should be interrupted and suspended in
favor of some other process ⇒ preemptive scheduling.
select
preempt
CPU
Note: what we need is some way to switch between
processes ⇒ context switching.
02 – 37
Processes/2.4 Process Scheduling
Context switching
Problem: We have to change from one process to
another. The stuff that is going to be used by another
process should be saved ⇒ CPU registers.
P
scheduler
P*
idle
executing
save registers
select next process
restore registers
save registers
select next process
restore registers
02 – 38
Processes/2.4 Process Scheduling
Round-robin scheduling
CPU
Simple idea: every process is assigned a quantum
– a number of time units that it is allowed to use the
CPU without interruption. If the process blocks, or the
quantum is exceeded, the scheduler assigns the CPU
to the next process in line.
Question: How big should a quantum be?
02 – 39
Processes/2.4 Process Scheduling
Priority scheduling
Idea: Combine round-robin with process priorities: a
process with a higher priority is selected first.
Queue
headers
Runable processes
Priority 4
(Highest priority)
Priority 3
Priority 2
Priority 1
(Lowest priority)
Issues:
• Are priorities assigned statically or dynamically?
• Can priorities be adjusted during execution?
• How are priorities assigned?
02 – 40
Processes/2.4 Process Scheduling
Process management in MINIX
Layer
Init
4
3
Process
manager
2
Disk
driver
1
User
process
File
system
TTY
driver
Kernel
User
process
Info
server
User
process
Network
server
Ethernet
driver
…
…
Server
processes
User
mode
Device
drivers
…
Clock
task
User
processes
System
task
Kernel
Kernel
mode
• All communication at layers 2–4 through messagepassing.
• Layers 1–2, and often 1–3 in other systems are
usually taken together into a single binary to be
executed in kernel mode. The rest executes in
user mode. MINIX 3 has a microkernel.
Question: What’s so good about having the file system (and process manager), as well as all the drivers,
outside the kernel?
02 – 41
Processes/2.5 Processes in MINIX
IPC in MINIX
Essence: All interprocess communication uses blocking message passing:
• When a process sends a message, it is blocked
until the receiver actually reads it.
• A process is always blocked when receiving a message until there is something to read.
Processes can communicate only with processes in
their own layer, and with the layer just below them.
MINIX uses priority scheduling, with one scheduling
queue per priority. User processes have the lowest
priority; device driver tasks the highest.
Important: MINIX runs as several totally independent
programs (minimum: kernel, drivers, process manager, file system). This means that procedures with
the same name, but in different programs, do not conflict.
02 – 42
Processes/2.5 Processes in MINIX
Memory layout
Limit of memory
Memory
available for
user programs
src/servers/init/init
3549K
Init
src/drivers/at_wini/at_wini
Disk driver
src/drivers/log/log
Log driver
src/drivers/memory/memory
Memory driver
src/drivers/tty/tty
Console driver
src/servers/rs/rs
Reincarnation server
src/servers/fs/fs
File system
src/servers/pm/pm
Process manager
3537K
3489K
3416K
3403K
3375K
3236K
(Depends on number
of buffers included
in file system)
1093K
1024K
Read only memory
and I/O adapter
memory
(unavailable to MINIX 3)
[Boot monitor]
640K
590K
Memory
available for
user programs
55K
System task
src/kernel/kernel
Clock task
Kernel
2K Start of kernel
[Used by BIOS]
[Interrupt vectors]
02 – 43
1K
0
Processes/2.5 Processes in MINIX
C Include File Semantics
In C, files can be merged by #include statements:
file A
#ifndef A_H
#define A_H
code in file A
#endif
#ifndef A_H
#define A_H
file B
code in file A
#include A
#endif
code file B
#ifndef A_H
#define A_H
code in file A
This part is
skipped by
the compiler
#endif
file C
#include A
#include B
code file B
code file C
code file C
02 – 44
Processes/2.6 Implementation in MINIX
C Scope Semantics
Modules in C are modeled by source files. Types, variables, etc., can be exported to other files (default);
imported (#extern), or be explicitly restricted to the
current file (#static):
#define PUBLIC
#define EXTERN extern
#define PRIVATE static
EXTERN int k;
PRIVATE int n;
EXTERN int k;
PRIVATE int n;
PUBLIC int k;
PRIVATE int n;
02 – 45
Processes/2.6 Implementation in MINIX
Messages (1/2)
In MINIX, there are seven different message types:
m_source
m_source
m_source
m_source
m_source
m_source
m_source
m_type
m_type
m_type
m_type
m_type
m_type
m_type
m1_i1
m2_i1
m3_i1
m4_l1
m7_i1
m8_i1
m7_i2
m8_i2
m7_i3
m8_p1
m7_i4
m8_p2
m7_p1
m8_p3
m7_p2
m8_p4
m5_c2 m5_c1
m5_i1
m1_i2
m2_i2
m3_i2
m4_l2
m5_i2
m1_i3
m2_i3
m3_p1
m4_l3
m5_l1
m1_p1
m2_l1
m4_l4
m5_l2
m1_p2
m2_l2
m4_l5
m3_ca1
m5_l3
m1_p3
m2_p1
int: i
char*: p long: l
char: c char[]: ca
02 – 46
Processes/2.6 Implementation in MINIX
Messages – Dereferencing
03020
03021
03022
03023
03024
03025
03026
03027
03028
03029
03030
03031
03032
03033
03034
03035
03036
03037
03038
03039
03040
.....
03049
03050
03051
03052
typedef struct {
int m_source;
int m_type;
union {
mess_1 m_m1;
mess_2 m_m2;
mess_3 m_m3;
mess_4 m_m4;
mess_5 m_m5;
mess_7 m_m7;
mess_8 m_m8;
} m_u;
} message;
/* who sent the message */
/* what kind of message is it */
/* The following defines provide names for useful members. */
#define m1_i1 m_u.m_m1.m1i1
#define m1_i2 m_u.m_m1.m1i2
#define m1_i3 m_u.m_m1.m1i3
#define m1_p1 m_u.m_m1.m1p1
#define m1_p2 m_u.m_m1.m1p2
#define m1_p3 m_u.m_m1.m1p3
#define
#define
#define
#define
m3_i1
m3_i2
m3_p1
m3_ca1
m_u.m_m3.m3i1
m_u.m_m3.m3i2
m_u.m_m3.m3p1
m_u.m_m3.m3ca1
m1 i3 ⇒ msg type #1, int-field #3
m3 ca1 ⇒ msg type #3, character-array-field #1
message msg;
msg.m_u.m_m1.m1i3 = 3;
msg.m1_i3 = 3;
02 – 47
Processes/2.6 Implementation in MINIX
System Calls (1/2)
Remember: Whenever a system call is made, the call
is translated into a message to a specific structure.
3670 /*===========================================================================*
03671 *
Messages for BLOCK and CHARACTER device drivers
*
03672 *===========================================================================*/
03673
03674 /* Message types for device drivers. */
03675 #define DEV_RQ_BASE
0x400
/* base for device request types */
03676 #define DEV_RS_BASE
0x500
/* base for device response types */
03677
03678 #define CANCEL
(DEV_RQ_BASE + 0) /* general req to force a task to cancel */
03679 #define DEV_READ
(DEV_RQ_BASE + 3) /* read from minor device */
03680 #define DEV_WRITE
(DEV_RQ_BASE + 4) /* write to minor device */
03681 #define DEV_IOCTL
(DEV_RQ_BASE + 5) /* I/O control code */
03682 #define DEV_OPEN
(DEV_RQ_BASE + 6) /* open a minor device */
03683 #define DEV_CLOSE
(DEV_RQ_BASE + 7) /* close a minor device */
03684 #define DEV_SCATTER
(DEV_RQ_BASE + 8) /* write from a vector */
03685 #define DEV_GATHER
(DEV_RQ_BASE + 9) /* read into a vector */
03686 #define TTY_SETPGRP
(DEV_RQ_BASE + 10) /* set process group */
03687 #define TTY_EXIT
(DEV_RQ_BASE + 11) /* process group leader exited */
03688 #define DEV_SELECT
(DEV_RQ_BASE + 12) /* request select() attention */
03689 #define DEV_STATUS
(DEV_RQ_BASE + 13) /* request driver status */
03690
03691 #define DEV_REPLY
(DEV_RS_BASE + 0) /* general task reply */
03692 #define DEV_CLONED
(DEV_RS_BASE + 1) /* return cloned minor */
03693 #define DEV_REVIVE
(DEV_RS_BASE + 2) /* driver revives process */
03694 #define DEV_IO_READY
(DEV_RS_BASE + 3) /* selected device ready */
03695 #define DEV_NO_STATUS
(DEV_RS_BASE + 4) /* empty status reply */
03696
03697 /* Field names for messages to block and character device drivers. */
03698 #define DEVICE
m2_i1
/* major-minor device */
m2_i2
/* which (proc) wants I/O? */
03699 #define PROC_NR
03700 #define COUNT
m2_i3
/* how many bytes to transfer */
03701 #define REQUEST
m2_i3
/* ioctl request code */
03702 #define POSITION
m2_l1
/* file offset */
03703 #define ADDRESS
m2_p1
/* core buffer address */
11215
11216
02 – 48
/* Transfer bytes from/to the device. */
r = (*dp->dr_transfer)(mp->PROC_NR, mp->m_type, mp->POSITION, iov, nr_req);
Processes/2.6 Implementation in MINIX
System Calls (2/2)
05409
05410
.....
05413
05414
05415
05416
05417
05418
05419
#define SYSCALL_FUNC
#define SYSCALL_FLAGS
07480
07481
07482
07483
07484
07485
07486
07487
07488
07489
07490
07491
.....
07558
07559
07560
07561
07562
07563
07564
07565
07566
07567
07568
07569
07570
07571
07572
.....
07580
07581
07582
07583
07584
07585
07586
PUBLIC int sys_call(call_nr, src_dst, m_ptr)
int call_nr;
/* system call number and flags */
int src_dst;
/* src to receive from or dst to send to */
message *m_ptr;
/* pointer to message in the caller’s space */
{
/* System calls are done by trapping to the kernel with an INT instruction.
* The trap is caught and sys_call() is called to send or receive a message
* (or both). The caller is always given by ’proc_ptr’.
*/
register struct proc *caller_ptr = proc_ptr; /* get pointer to caller */
int function = call_nr & SYSCALL_FUNC;
/* get system call function */
unsigned flags = call_nr & SYSCALL_FLAGS;
/* get flags */
0x0F
0xF0
/* mask for system call function */
/* mask for system call flags */
/* System call numbers that are passed when trapping to the kernel. The
* numbers are carefully defined so that it can easily be seen (based on
* the bits that are on) which checks should be done in sys_call().
*/
#define SEND
1
/* 0 0 0 1 : blocking send */
#define RECEIVE
2
/* 0 0 1 0 : blocking receive */
#define SENDREC
3
/* 0 0 1 1 : SEND + RECEIVE */
switch(function) {
case SENDREC:
/* A flag is set so that notifications cannot interrupt SENDREC. */
priv(caller_ptr)->s_flags |= SENDREC_BUSY;
/* fall through */
case SEND:
result = mini_send(caller_ptr, src_dst, m_ptr, flags);
if (function == SEND || result != OK) {
break;
/* done, or SEND failed */
}
/* fall through for SENDREC */
case RECEIVE:
if (function == RECEIVE)
priv(caller_ptr)->s_flags &= ~SENDREC_BUSY;
result = mini_receive(caller_ptr, src_dst, m_ptr, flags);
break;
default:
result = EBADCALL;
}
/* illegal system call */
/* Now, return the result of the system call to the caller. */
return(result);
}
02 – 49
Processes/2.6 Implementation in MINIX
Process Table
5500 #ifndef PROC_H
05501 #define PROC_H
05502
05503 /* Here is the declaration of the process table. It contains all process
05504 * data, including registers, flags, scheduling priority, memory map,
05505 * accounting, message passing (IPC) information, and so on.
05506 *
.....
05510 */
.....
05516 struct proc {
05517
struct stackframe_s p_reg;
/* process’ registers saved in stack frame */
05518
reg_t p_ldt_sel;
/* selector in gdt with ldt base and limit */
05519
struct segdesc_s p_ldt[2+NR_REMOTE_SEGS]; /* CS, DS and remote segments */
05520
05521
proc_nr_t p_nr;
/* number of this process (for fast access) */
05522
struct priv *p_priv;
/* system privileges structure */
05523
char p_rts_flags;
/* SENDING, RECEIVING, etc. */
05524
05525
char p_priority;
/* current scheduling priority */
05526
char p_max_priority;
/* maximum scheduling priority */
05527
char p_ticks_left;
/* number of scheduling ticks left */
05528
char p_quantum_size;
/* quantum size in ticks */
05529
05530
struct mem_map p_memmap[NR_LOCAL_SEGS];
/* memory map (T, D, S) */
05531
05532
clock_t p_user_time;
/* user time in ticks */
05533
clock_t p_sys_time;
/* sys time in ticks */
05534
05535
struct proc *p_nextready;
/* pointer to next ready process */
05536
struct proc *p_caller_q;
/* head of list of procs wishing to send */
05537
struct proc *p_q_link;
/* link to next proc wishing to send */
05538
message *p_messbuf;
/* pointer to passed message buffer */
05539
proc_nr_t p_getfrom;
/* from whom does process want to receive? */
05540
proc_nr_t p_sendto;
/* to whom does process want to send? */
05541
05542
sigset_t p_pending;
/* bit map for pending kernel signals */
05543
05544
char p_name[P_NAME_LEN];
/* name of the process, including \0 */
05545 };
02 – 50
Processes/2.6 Implementation in MINIX
MINIX v3 processes (1/2)
Note: Not all processes are equal, and we need a
way to distinguish their privileges:
05718 struct priv {
05719
proc_nr_t s_proc_nr;
/* number of associated process */
05720
sys_id_t s_id;
/* index of this system structure */
05721
short s_flags;
/* PREEMTIBLE, BILLABLE, etc. */
05722
05723
short s_trap_mask;
/* allowed system call traps */
05724
sys_map_t s_ipc_from;
/* allowed callers to receive from */
05725
sys_map_t s_ipc_to;
/* allowed destination processes */
05726
long s_call_mask;
/* allowed kernel calls */
05727
05728
sys_map_t s_notify_pending;
/* bit map with pending notifications */
05729
irq_id_t s_int_pending;
/* pending hardware interrupts */
05730
sigset_t s_sig_pending;
/* pending signals */
05731
05732
timer_t s_alarm_timer;
/* synchronous alarm timer */
05733
struct far_mem s_farmem[NR_REMOTE_SEGS]; /* remote memory map */
05734
reg_t *s_stack_guard;
/* stack guard word for kernel tasks */
05735 };
Important: Only low-level user-mode device drivers
are allowed to, for example, request register manipulations.
02 – 51
Processes/2.6 Implementation in MINIX
MINIX v3 processes (2/2)
-nr(-4)
[-3]
[-2]
[-1]
0
1
2
3
...
7
...
15
-id-name---(01) IDLE
(02) CLOCK
(03) SYSTEM
(04) KERNEL
(05) pm
(06) fs
(07) rs
(09) memory
(00) init
-flags- -trapsP-BS-------S--R----S--R----S----P--SESRBN
P--SESRBN
P--SESRBN
P--SESRBN
-ipc_to mask----------------------00000000 00001111 10000000 0000000
00000000 00001111 10000000 0000000
00000000 00001111 10000000 0000000
00000000 00001111 10000000 0000000
11111111 11111111 10000000 0000000
11111111 11111111 10000000 0000000
11111111 11111111 10000000 0000000
00110111 01101111 10000000 0000000
P-B--
E--B-
00000111 00000000 00000000 0000000
(16) printer P--S-
ESRBN
01111111 11111111 11111111 1111111
Note: The debugger provides info on who can do
what. We see that the init process can call only pm,
fs, and rs by means of B = send/receive calls (i.e.,
synchronous communication.
VERY IMPORTANT:
CHECK THIS OUT FOR YOURSELF
WITH THE MINIX CD
02 – 52
Processes/2.6 Implementation in MINIX
Bootstrapping MINIX (1/2)
Boot pr
ogr
am
boot record &
s te r
Ma n 1 bootbloc part
itio
o
k
r t it i
n
Pa
loa
d
n 2 boot
s
rtitio
blo
Pa loads
c
b
ta
ock
tbl s
o
Bo load
le
rogram for pa
rtit
io
rogram
for
ot p
Bo
ot p
Bo
k
n
1
on 2
rtiti
pa
(a)
(b)
Basic idea: A computer has a ROM-installed program that automatically reads the first 512 bytes of
the bootblock into memory. The loaded code is then
executed (program Boot).
02 – 53
Processes/2.6 Implementation in MINIX
Bootstrapping MINIX (2/2)
• Boot reads the second sector of the (1K) bootblock, and inspects the parameters saved there.
• The user is presented with options for loading the
rest of the system, or can even choose to load
other OSs that lie in different partitions.
• Boot normally simply loads the binary image of a
compiled MINIX version into main memory, and
jumps to main().
• main()’s task is to initialize memory and the process table (there are some processes running now!),
and start the shell at the console.
Question: We can use a very similar scheme for diskless workstations. How?
02 – 54
Processes/2.6 Implementation in MINIX
Interrupt Handling (1/2)
Interrupt
INT
CPU
s
y
s
t
e
m
IRQ 0 (clock)
IRQ 1 (keyboard)
INT
Master
interrupt
controller
IRQ 3 (tty 2)
IRQ 4 (tty 1)
IRQ 5 (XT Winchester)
IRQ 6 (floppy)
IRQ 7 (printer)
ACK
INTA
Interrupt
ack
d
a
t
a
b
u
s
INT
Slave
interrupt
controller
ACK
IRQ 8 (real time clock)
IRQ 9 (redirected IRQ 2)
IRQ 10
IRQ 11
IRQ 12
IRQ 13 (FPU exception)
IRQ 14 (AT Winchester)
IRQ 15
During system initialization, main() initializes the controllers and sets the interrupt vector.
02 – 55
Processes/2.6 Implementation in MINIX
Interrupt Handling (2/2)
1: Save registers of interrupted process
2: Handle the interrupt
3: Continue by rescheduling the best process.
Note: The hardware already saves a number of key
registers by pushing them onto a new stack of the interrupted process.
06515
06516
06517
06518
06519
06520
06521
06522
06523
06524
06525
06526
06527
02 – 56
#define hwint_master(irq)
\
call
save
push
(_irq_handlers+4*irq)
call
_intr_handle
pop
ecx
cmp
(_irq_actids+4*irq), 0
jz
0f
inb
INT_CTLMASK
orb
al, [1<<irq]
outb
INT_CTLMASK
0:
movb
al, END_OF_INT
outb
INT_CTL
ret
/* save interrupted process state */;\
/* irq_handlers[irq]
*/;\
/* intr_handle(irq_handlers[irq]) */;\
;\
/* interrupt still active?
*/;\
;\
/* get current mask */
;\
/* mask irq */
;\
/* disable the irq
*/;\
;\
/* reenable master 8259
*/;\
/* restart (another) process
*/
Processes/2.6 Implementation in MINIX
Saving registers
06617
06618
06619
06620
06621
06622
06623
06624
06625
06626
06627
06628
06629
06630
06631
06632
06633
06634
06635
06636
06637
06638
06639
06640
06641
06642
06643
02 – 57
! Save for protected mode.
! This is much simpler than for 8086 mode, because the stack already points
! into the process table, or has already been switched to the kernel stack.
.align
16
cld
pushad
push
push
push
push
mov
mov
mov
mov
incb
jnz
mov
push
xor
jmp
! set direction flag to a known value
! save "general" registers
ds
! save ds
es
! save es
fs
! save fs
gs
! save gs
dx, ss
! ss is kernel data segment
ds, dx
! load rest of kernel segments
es, dx
! kernel does not use fs, gs
eax, esp
! prepare to return (_k_reenter)
! from -1 if not reentering
set_restart1
! stack is already kernel stack
esp, k_stktop
! "!
$#&% '()$%
_restart
! build return address for int handler
ebp, ebp
! for stacktrace
RETADR-P_STACKBASE(eax) ! *
$#! ,+$#-./,0$#1.2
save:
o16
o16
o16
o16
.align
set_restart1:
push
jmp
4
restart1
RETADR-P_STACKBASE(eax) ! *
$#! ,+$#-./,0$#1.2
Processes/2.6 Implementation in MINIX
Restoring registers
06681
06682
06683
06684
06685
06686
06687
06688
06689
06690
06691
06692
06693
06694
06695
06696
06697
06698
06699
06700
06701
06702
02 – 58
_restart:
! Restart the current process or the next process if it is set.
cmp
jz
mov
mov
mov
0:
mov
lldt
lea
mov
restart1:
decb
o16 pop
o16 pop
o16 pop
o16 pop
popad
add
iretd
(_next_ptr), 0
0f
eax, (_next_ptr)
(_proc_ptr), eax
(_next_ptr), 0
esp, (_proc_ptr)
P_LDT_SEL(esp)
eax, P_STACKTOP(esp)
(_tss+TSS3_S_SP0), eax
! see if another process is scheduled
! schedule new process
!
!
!
!
will assume P_STACKBASE == 0
enable process’ segment descriptors
arrange for next interrupt
to save state in process table
(_k_reenter)
gs
fs
es
ds
esp, 4
! skip return adr ! '0/ ! continue process
Processes/2.6 Implementation in MINIX
$#
System Calls
06648
06649
06650
06651
06652
06653
06654
06655
06656
06657
06658
06659
06660
06661
06662
06663
06664
06665
06666
06667
06668
06669
06670
06671
06672
06673
06674
06675
06676
.align
_s_call:
_p_s_call:
cld
sub
push
push
push
o16 push
o16 push
o16 push
o16 push
mov
mov
mov
incb
mov
mov
xor
push
push
push
call
mov
16
esp, 6*4
ebp
esi
edi
ds
es
fs
gs
dx, ss
ds, dx
es, dx
(_k_reenter)
esi, esp
esp, k_stktop
ebp, ebp
! set direction flag to a known value
! skip RETADR, eax, ecx, edx, ebx, est
! stack already points into proc table
!
!
!
!
!
ebx
!
eax
!
ecx
!
_sys_call
!
!
AXREG(esi), eax !
assumes P_STACKBASE == 0
"!
$#&% '()$%
for stacktrace
end of inline save
now set up parameters for sys_call()
pointer to user message
src/dest
SEND/RECEIVE/BOTH
sys_call(function, src_dest, m_ptr)
caller is now explicitly in proc_ptr
sys_call MUST PRESERVE si
! Fall into code to restart proc/task running.
Note: s call is called by a nonkernel process to invoke a system service. This means switching from
user to kernel mode. When call is finished, we continue with restart().
02 – 59
Processes/2.6 Implementation in MINIX
Interprocess communication
Model: assume a process P sends a message msg
to process Q.
1: P calls send(P, Q, msg).
2: If Q is prepared to receive a message from (1) P,
or (2) any process, msg is copied from P to Q’s
buffer space. Both P and Q can continue.
3: If Q cannot receive a message from P, P is blocked
and queued until its message can be delivered.
Assume Q wants to receive a message:
1: First check if there is a process that is blocking
because it wanted to send a message to Q.
2: If there was a message already pending, the message is copied to Q’s buffer space, and sender
and receiver continue.
3: If there was no message pending, Q blocks until
one does.
02 – 60
Processes/2.6 Implementation in MINIX
Example: Doing a System Call
07480
07481
07482
07483
07484
07485
07486
07487
07488
07489
07490
07491
.....
07558
07559
07560
07561
07562
07563
07564
07565
07566
07567
07568
07569
07570
07571
07572
.....
07580
07581
07582
07583
07584
07585
07586
PUBLIC int sys_call(call_nr, src_dst, m_ptr)
int call_nr;
/* system call number and flags */
int src_dst;
/* src to receive from or dst to send to */
message *m_ptr;
/* pointer to message in the caller’s space */
{
/* System calls are done by trapping to the kernel with an INT instruction.
* The trap is caught and sys_call() is called to send or receive a message
* (or both). The caller is always given by ’proc_ptr’.
*/
register struct proc *caller_ptr = proc_ptr; /* get pointer to caller */
int function = call_nr & SYSCALL_FUNC;
/* get system call function */
unsigned flags = call_nr & SYSCALL_FLAGS;
/* get flags */
switch(function) {
case SENDREC:
/* A flag is set so that notifications cannot interrupt SENDREC. */
priv(caller_ptr)->s_flags |= SENDREC_BUSY;
/* fall through */
case SEND:
result = mini_send(caller_ptr, src_dst, m_ptr, flags);
if (function == SEND || result != OK) {
break;
/* done, or SEND failed */
}
/* fall through for SENDREC */
case RECEIVE:
if (function == RECEIVE)
priv(caller_ptr)->s_flags &= ~SENDREC_BUSY;
result = mini_receive(caller_ptr, src_dst, m_ptr, flags);
break;
default:
result = EBADCALL;
}
}
/* illegal system call */
/* Now, return the result of the system call to the caller. */
return(result);
Note: we’re sending (7564) a message to the process
that handles the call, and block until we receive the
answer (7571).
02 – 61
Processes/2.6 Implementation in MINIX
Sending a Message
07591
07592
07593
07594
07595
07596
07597
07598
07599
07600
.....
07605
07606
07607
07608
07609
07610
07611
07612
07613
07614
07615
07616
07617
07618
07620
07621
07622
07623
07624
07625
07626
07627
07628
07629
07630
07631
07632
07633
07634
07635
07636
07637
PRIVATE int mini_send(caller_ptr, dst, m_ptr, flags)
register struct proc *caller_ptr;
/* who is trying to send a message? */
int dst;
/* to whom is message being sent? */
message *m_ptr;
/* pointer to message buffer */
unsigned flags;
/* system call flags */
{
/* Send a message from ’caller_ptr’ to ’dst’. If ’dst’ is blocked waiting
* for this message, copy the message to it and unblock ’dst’. If ’dst’ is
* not waiting at all, or is waiting for another source, queue ’caller_ptr’.
*/
/* Check for deadlock by ’caller_ptr’ and ’dst’ sending to each other. */
xp = dst_ptr;
while (xp->p_rts_flags & SENDING) {
/* check while sending */
xp = proc_addr(xp->p_sendto);
/* get xp’s destination */
if (xp == caller_ptr) return(ELOCKED); /* deadlock if cyclic */
}
/* Check if ’dst’ is blocked waiting for this message. The destination’s
* SENDING flag may be set when its SENDREC call blocked while sending.
*/
if ( (dst_ptr->p_rts_flags & (RECEIVING | SENDING)) == RECEIVING &&
(dst_ptr->p_getfrom == ANY || dst_ptr->p_getfrom == caller_ptr->p_nr)) {
/* Destination is indeed waiting for this message. */
CopyMess(caller_ptr->p_nr, caller_ptr, m_ptr, dst_ptr, dst_ptr->p_messbuf);
if ((dst_ptr->p_rts_flags &= ~RECEIVING) == 0) enqueue(dst_ptr);
} else if ( ! (flags & NON_BLOCKING)) {
/* Destination is not waiting. Block and dequeue caller. */
caller_ptr->p_messbuf = m_ptr;
if (caller_ptr->p_rts_flags == 0) dequeue(caller_ptr);
caller_ptr->p_rts_flags |= SENDING;
caller_ptr->p_sendto = dst;
}
02 – 62
/* Process is now blocked. Put in on the destination’s queue. */ xpp = &dst_ptr->p_caller_q;
/* find end of list */
while (*xpp != NIL_PROC) xpp = &(*xpp)->p_q_link;
*xpp = caller_ptr;
/* add caller to end */
caller_ptr->p_q_link = NIL_PROC;
/* mark new end of list */
} else {
return(ENOTREADY);
}
return(OK); !$ # # ! Processes/2.6 Implementation in MINIX
Receiving a Message
07642
07643
07644
07645
07646
07647
07648
07649
07650
07651
.....
07660
07661
07662
07663
07664
.....
07688
07689
07690
07691
07692
07693
07694
07695
07696
07697
07698
07699
07700
07701
07702
07703
07704
07705
07706
07707
07708
07709
07710
07711
07712
07713
07714
PRIVATE int mini_receive(caller_ptr, src, m_ptr, flags)
register struct proc *caller_ptr;
/* process trying to get message */
int src;
/* which message source is wanted */
message *m_ptr;
/* pointer to message buffer */
unsigned flags;
/* system call flags */
{
/* A process or task wants to get a message. If a message is already queued,
* acquire it and deblock the sender. If no message from the desired source
* is available block the caller, unless the flags don’t allow blocking.
*/
/* Check to see if a message from desired source is already available.
* The caller’s SENDING flag may be set if SENDREC couldn’t send. If it is
* set, the process should be blocked.
*/
if (!(caller_ptr->p_rts_flags & SENDING)) {
}
}
02 – 63
/* Check caller queue. Use pointer pointers to keep code simple. */
xpp = &caller_ptr->p_caller_q;
while (*xpp != NIL_PROC) {
if (src == ANY || src == proc_nr(*xpp)) {
/* Found acceptable message. Copy it and update status. */
CopyMess((*xpp)->p_nr, *xpp, (*xpp)->p_messbuf, caller_ptr, m_ptr);
if (((*xpp)->p_rts_flags &= ~SENDING) == 0) enqueue(*xpp);
*xpp = (*xpp)->p_q_link;
/* remove from queue */
return(OK);
/* report success */
}
xpp = &(*xpp)->p_q_link;
/* proceed to next */
}
/* No suitable message is available or the caller couldn’t send in SENDREC.
* Block the process trying to receive, unless the flags tell otherwise.
*/
if ( ! (flags & NON_BLOCKING)) {
caller_ptr->p_getfrom = src;
caller_ptr->p_messbuf = m_ptr;
if (caller_ptr->p_rts_flags == 0) dequeue(caller_ptr);
caller_ptr->p_rts_flags |= RECEIVING;
return(OK);
} else {
return(ENOTREADY);
}
Processes/2.6 Implementation in MINIX
Scheduling
rdy_head
15
IDLE_Q
rdy_tail
IDLE_Q
IDLE
.
.
.
7
USER_Q
.
.
.
USER_Q
init
.
.
.
fs
3
rs
pm
2
disk
log
1
tty
•
TASK_Q
7
.
.
.
4
0
15
system
4
3
mem
2
1
clock
TASK_Q
0
: select the most suitable task/process
to run, starting with highest priority queue.
• : enter a task/process into the appropriate queue.
• : do the opposite.
• : reshuffle the USER Q when a quantum has
expired: the head is moved to the end.
02 – 64
Processes/2.6 Implementation in MINIX
System Task (1/2)
Problem: because the memory manager, file system,
drivers and other typical OS processes are placed
outside the kernel, they cannot modify kernel data
structures. However, they do control a lot of things
that are administrated in the kernel.
Solution: install a separate system task that (1) communicates with these special processes, (2) operates
in kernel mode, (3) changes kernel data structures on
behalf of these processes.
02 – 65
Processes/2.7 System Task
System Task (2/2)
02 – 66
Processes/2.7 System Task
Clock – Hardware
Essentially just two types:
• Simple ones that generate an interrupt with each
cycle of the power supply (every 20 or 16.7 ms)
• Advanced ones with their own oscillator by which
a counter is decremented. Whenever the counter
hits zero, an interrupt is generated.
The advanced ones are really what we need because
you can program them.
Example: if the oscillator has a frequency of 1 MHz,
and the clock is connected to a 16-bit register, we can
set the timer between 1 and 65536 µs.
Crystal oscillator
Counter is decremented at each pulse
Holding register is used to load the counter
02 – 67
Processes/2.8 Clocks
Clock – Software
Basic functions:
• Maintaining time of day (just count ticks in software).
• Lowering the quantum of a running process (decrement a software counter).
• Accounting for CPU usage (guess how...)
• Handling alarms and watchdogs.
• Profiling, monitoring, and statistics gathering.
Current time
Next signal
4200
3
Clock
header
3
02 – 68
4
6
2
1 X
Processes/2.8 Clocks
The Clock Task
10468
10469
10470
10471
10472
10473
10474
10475
10476
10477
10478
10479
10480
10481
10482
10483
10484
10485
10486
10487
10488
10489
10490
10491
10492
PUBLIC void clock_task()
{
/* Main program of clock task. If the call is not HARD_INT it is an error.
*/
message m;
/* message buffer for both input and output */
int result;
/* result returned by the handler */
init_clock();
/* initialize clock task */
/* Main loop of the clock task.
while (TRUE) {
Get work, process it. Never reply. */
/* Go get a message. */
receive(ANY, &m);
}
02 – 69
}
/* Handle the request. Only clock ticks are expected. */
switch (m.m_type) {
case HARD_INT:
result = do_clocktick(&m);
/* handle clock tick */
break;
default:
/* illegal request type */
kprintf("CLOCK: illegal request %d from %d.\n", m.m_type,m.m_source);
}
Processes/2.8 Clocks
Processing a clock tick
10497
10498
10499
10500
10501
10502
10503
10504
10505
10506
10507
10508
10509
10510
10511
10512
10513
10514
10515
10516
10517
10518
10519
10520
10521
10522
10523
10524
PRIVATE int do_clocktick(m_ptr)
message *m_ptr;
/* pointer to request message */
{
/* Despite its name, this routine is not called on every clock tick. It
* is called on those clock ticks when a lot of work needs to be done.
*/
/* A process used up a full quantum. The interrupt handler stored this
* process in ’prev_ptr’. First make sure that the process is not on the
* scheduling queues. Then announce the process ready again. Since it has
* no more time left, it gets a new quantum and is inserted at the right
* place in the queues. As a side-effect a new process will be scheduled.
*/
if (prev_ptr->p_ticks_left <= 0 && priv(prev_ptr)->s_flags & PREEMPTIBLE) {
lock_dequeue(prev_ptr);
/* take it off the queues */
lock_enqueue(prev_ptr);
/* and reinsert it again */
}
/* Check if a clock timer expired and run its watchdog function. */
if (next_timeout <= realtime) {
tmrs_exptimers(&clock_timers, realtime, NULL);
next_timeout = clock_timers == NULL ?
TMR_NEVER : clock_timers->tmr_exp_time;
}
/* Inhibit sending a reply. */
return(EDONTREPLY);
}
02 – 70
Processes/2.8 Clocks
Clock interrupt handler
10556
10557
10558
10559
10560
10561
.....
10583
10584
10585
10586
10587
10588
10589
10590
10591
10592
10593
10594
10595
10596
10597
10598
10599
10600
10601
10602
10603
10604
10605
10606
10607
10608
10609
10610
10611
10612
10613
10614
10615
PRIVATE int clock_handler(hook)
irq_hook_t *hook;
{
/* This executes on each clock tick (i.e., every time the timer chip generates
* an interrupt). It does a little bit of work so the clock task does not have
* to be called on every tick. */
register unsigned ticks;
/* Acknowledge the PS/2 clock interrupt. */
if (machine.ps_mca) outb(PORT_B, inb(PORT_B) | CLOCK_ACK_BIT);
/* Get number of ticks and update realtime. */
ticks = lost_ticks + 1;
lost_ticks = 0;
realtime += ticks;
/* Update user and system accounting times. Charge the current process for
* user time. If the current process is not billable, that is, if a non-user
* process is running, charge the billable process for system time as well.
* Thus the unbillable process’ user time is the billable user’s system time.
*/
proc_ptr->p_user_time += ticks;
if (priv(proc_ptr)->s_flags & PREEMPTIBLE) {
proc_ptr->p_ticks_left -= ticks;
}
if (! (priv(proc_ptr)->s_flags & BILLABLE)) {
bill_ptr->p_sys_time += ticks;
bill_ptr->p_ticks_left -= ticks;
}
/* Check if do_clocktick() must be called. Done for alarms and scheduling.
* Some processes, such as the kernel tasks, cannot be preempted.
*/
if ((next_timeout <= realtime) || (proc_ptr->p_ticks_left <= 0)) {
prev_ptr = proc_ptr;
/* store running process */
lock_notify(HARDWARE, CLOCK);
/* send notification */
}
return(1);
/* reenable interrupts */
}
02 – 71
Processes/2.8 Clocks