560 Notes on Timer
560 NOTES on TIMER and TIMER SERVICES
0. BACKGROUND: NOTES on Interrupts
1. PC's timer:
PC's timer has a base frequency of 1193182 Hz. It can be programmed to
generate a timer interrupt once every 1/60 of a second (commonly known
as one clock tick). It interrupts at IRQ0 (vector 8).
2. The code shown below initializes the timer.
============================================================================
/* Timer parameters. */
#define LATCH_COUNT 0x00 /* cc00xxxx, c = channel, x = any */
#define SQUARE_WAVE 0x36 /* ccaammmb, a = access, m = mode, b = BCD */
/* 11x11, 11 = LSB then MSB, x11 = sq wave */
#define TIMER_FREQ 1193182L /* clock frequency for timer in PC and AT */
#define TIMER_COUNT ((unsigned) (TIMER_FREQ/60)) /*initial value of counter*/
#define TIMER0 0x40
#define TIMER_MODE 0x43
#define TIMER_IRQ 0
#define INT_CNTL 0x20 /* 8259 interrupt control register */
#define INT_MASK 0x21 /* bit i = 0 means enable interrupt i */
/*==========================================================================*
* enable_irq *
*==========================================================================*/
int enable_irq(irq_nr) ushort irq_nr;
{
/* Assume IRQ 0-7 ONLY. Clear the corresponding 8259 interrupt mask bit. */
out_byte(INT_MASK, in_byte(INT_MASK) & ~(1 << irq_nr));
}
int tick,sec,min,hr; /* Wall clock */
/*===========================================================================*
* timer_init *
*===========================================================================*/
int timer_init()
{
/* Initialize channel 0 of the 8253A timer to 60 Hz. */
tick = sec = min = hr = 0; /* initialize wall clock to 0 */
out_byte(TIMER_MODE, SQUARE_WAVE); /* set timer to run continuously */
out_byte(TIMER0, TIMER_COUNT) /* load timer low byte */
out_byte(TIMER0, TIMER_COUNT >> 8); /* load timer high byte */
enable_irq(TIMER_IRQ); /* enable timer interrupts */
}
=============================================================================
3. timer_init() initializes and starts the timer. Once started, it will
interrupt (at IRQ0 or vector 8) once every 1/60 seconds. To handle such
interrupts, a timer interrupt handler MUST be installed BEFORE starting
the timer.
4. Stack for Interrupt Processing:
(1). Timer interrupt may occur anywhere, i.e. either in Umode or in Kmode.
Interrupt processing must be performed in Kernel.
(2). When a timer interrupt occurs, the CPU saves [flag, CS, PC] of the
interrupted point into the CURRENT stack. Then it follows the vector #8
contents to continue execution from
[_tinth, 0x1000]
in the MTX kernel. At this moment, all other CPU registers are still
those of the interrupted point. Similar to _int80h, we may let _tinth
save all CPU registers into the CURRENT stack.
Then a decision must be made in order to set up the correct execution
environment:
(a). If the CPU was in Umode, we MUST change DS,SS,ES to kernel's
segment (0x1000), save the interrupted Umode SS,SP, and establish
a kstack, then call a C handler function (as we did for INT 80) to
continue processing the interrupt.
In this case, the situation is exactly the same as that when the
task enters Kmode via INT 80. So, we may save the interrupted
(SS, SP) into (proc.uss, proc.usp), and use the task's kstack
from scratch (i.e. let sp -> High end of prunning proc's kstack[]).
(b). However, if the CPU was in Kmode, then there is no need to change
DS,SS,ES since they are already at 0x1000, and (more importantly)
we must continue to use the running proc' kstack AS IS since it
already contains the (saved) interrupted point.
How do we know which mode the CPU was in before the interrupt?
Figure this out yourself !!!!
While processing an interrupt, execution is in Kmode so that CS=DS=SS=ES=
0x1000 and stack is the running proc's kstack[ ].
(4). call thandler() in C, which IMPLEMENTS the actual timer interrupt
processing, i.e. it may update time count, display wall clock, start
timer-dependent work, etc, including
switch process !!!! but before switching to another process,
it must re-enable the 8259 by writing 0x20 to port 0x20.
If a process switch occurs, the running proc's resume point is
saved in its kstack, as it should.
(5). When thandler() finishes, _tinth returns to the interrupted point by
_treturn:
cli ! mask out interrupts
if (was in Umode){
restore interrupted (SS,SP); // from proc.uss, proc.usp
}
pop registers in reverse order
iret
NOTE: For nested interrupts, the first interrupt may occur in Umode, but
any subsequent interrupts MUST occur in Kmode (i.e. in the middle of
handling an interrupt). In the latter case, we continue to use the proc's
kstack to save interrupted point and then return to the interrupted point.
Therefore, our scheme can handle nested interrupts (of upto 15 levels).
Of course, each task's kstack[] must be big enough to contain upto 15
layers of saved interrupted points AND their stack frames (i.e. calling
chain and local variables, etc).
5. Implement Timer in MTX:
Simliar to INT80 interrupts, the steps are:
(1). Mask out ALL interrupts (by CLI or a given lock(), which issues CLI).
(2). Initialize MTX kernel as before. Create P0 and let P0 kfork P1 with a
Umode image. In kfork(), set the SAVED flag register to 0x0200 (so that
the proc will run with interrupts masked in even in Kmode).
(4). Set interrupt vectors (int80 for syscall and int8 for timer).
Specifically: vector80 ==> (_int80h, 0x1000)
vector8 ==> (_tinth, 0x1000)
(5). Initialize and ENABLE the timer by timer_init(); ==> Timer interrupts
will occur immediately but they are MASKed out for now.
(6). tswitch(), which will load CPU's flag register with Ibit=1 ==>
CPU begins to accept interrupts.
ASSIGNMENT #5 TIMER for MTX
DUE: next week
****************************** DO THESE *********************************************
1. Modify ts.s file to add ASSEMBLY code for timer interrupts:
.globl _tinth ! entry point of timer interrupt handler
.globl _thandler ! timer interrupt handler in C
! ADD YOUR OWN CODE for _tinth, which calls thandler(), then returns to the
interrupted point
2. Implement these timer functions:
(a). At each second : display current time HH:MM:SS at the lower right corner.
Read my video display driver vid.c code for how.
(b). When a proc is scheduled to run, set its PROC.time to an alloted run time,
e.g. 5 seconds. Decrement running's run time in Umode only! When a proc's
run time expires, switch process.
NOTE: do NOT switch process while it's in Kmode!!!
REASON: our MTX kernel is NOT a multi-processor kernel.
(c). Interval Timer Service
From Umode, implement a command:
set_timer t
which enters Kernel to set a timer request of t seconds. In genral, a
process may continue after setting a timer request. It will be NOTIFIED when
the time interval expires. Since our MTX does not have "notify" mechanism yet,
we shall assume that the process BLOCKs itself (sleep) until the time interval
expires.
NOTE !!! Your implementation MUST conform with the specifications to be shown
in class lecture, else no credits.
Example of interval timer requests:
TQ------------------> TQE1 -----> TQE2 -----> TQE3
----- ----- -----
time: 10 2 5
(Actual time : 10 12 17)
action: WAKEUP NOTIFY EXECUTE
union | PROC *: p1 p2
| func *: func3
----- ----- -----
(1). itimer(t) : insert a request of t seconds into TQ;
(Assignment: Figure out how and IMPLEMENT for action = WAKEUP only).
(2). Timer interrupt handler:
dec first TQE by 1;
loop: if (TQE.time <=0 ) ==> dequeue TQE and perform action
repeat loop;
(3). Protection of TQ: Processes must access TQ as a Critial Region (CR),
and free of interrupt interference.