460 Notes on Timer
460 NOTES on TIMER and TIME-SHARING
0. BACKGROUND: NOTES on Interrupts
1. PC's timer:
The 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.
============================================================================
/* Clock 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 CLOCK_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 */
/*===========================================================================*
* clock_init *
*===========================================================================*/
int clock_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(CLOCK_IRQ); /* enable timer interrupts */
}
=============================================================================
3. clock_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:
Fiirst, our MTX depends on BIOS for I/O (e.g. getc/putc/diskio). So, when a
timer interrupt occurs, (the running task of) our MTX may be executing in
ANY one of 3 possible places:
Kmode : in this case, CS=DS=SS=0x1000
Umode : in this case, CS=DS=SS=running task's Umode segment
BIOS : CS=0xF000, (DS,SS)=(DS,SS) of Kmode OR Umode
Note that BIOS does NOT have its own stack segment. Therefore, while a task
executes in BIOS, the stack is still the task's stack, which is either its
kstack or ustack, depending on which mode it is executing in.
Therefore, when an interrupt occurs, the interrupted stack MUST BE either
kstack (if the task is running in Kmode) or ustack (if it is running in
Umode).
(1). Timer interrupt may occur anywhere, but interrupt processing must be
performed in Kernel. When a timer interrupt occurs, the CPU saves
[flag, CS, PC] of the interrupted point onto CURRENT stack.
(2). 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 task 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 task's kstack[]).
(b). If the task 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 task's kstack AS IS since it already contains
the (saved) interrupted point.
How do we know which mode the CPU was in before the interrupt?
One way is to compare the interrupted segment value with 0x1000.
Here is another way:
(d). Define a global variable int in_kmode; to keep track of the
number of times of entering Kmode. Initialize in_kmode to 1
(since MTX starts from Kmode). Whenever a task enters Kmode
(via INT 80 or due to an interrupt), it increments in_kmode by 1.
Whenever a task exits Kmode (via goUmode() or return from interrupt
handler), it decrements in_kmode by 1. Then we may test the value
of in_kmode to determine which mode the CPU was in when interrupt
occurred.
While processing an interrupt, execution is in Kmode so that CS=DS=SS=ES=
0x1000 and stack is the running task'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 actions, etc, including
switch task!!!! but before switching to another task,
it must re-enable the 8259 by writing 0x20 to port 0x20.
If a task switch occurs, the running task'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.iss, proc.isp
}
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 cases, we continue to use the task'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 lock(), which issues CLI).
(2). Initialize MTX kernel as before. Let running-> mainProc, myfork()
task1 with a Umode image. In myfork(), set the SAVED flag to 0x0200
(so that the task will run with interrupts enabled).
(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 clock_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.
==================================================================
HELP files
Download and READ the files ts.s and clock.c in samples/TIMER/
==================================================================
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
LAB ASSIGNMENT #7 : TIMER FOR MTX
DUE: Friday, Nov 2, 2007 in class
Implement timer for the MTX kernel such that:
1. At each second : display time HH:MM:SS at the lower right corner.
! The PC's screen has 25 rows (0-24), and 80 columns (0-79)
! The origin (row,col) = (0,0) is at the UPPER-LEFT corner.
!
! When calling BIOS to diaplay a char, (refer to getc()), the char is
! displayed at the current CURSOR position. The following parameters
! manipulate the CURSOR position:
!
! ah = 2: set curosr position to (row, col) in (dh,dl)
! ah = 3; read cursor position in (dh,dl)
!
_setCursor:
movb dh, _row
movb dl, _col
mov ax, #0x0200
xor bx, bx
int 0x10
ret
_getCursor:
mov ax, #0x0300
xor bx, bx
int 0x10
movb _row, dh
movb _col, dl
ret
2. When a task 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 task's run time expires in Umode, switch task.
NOTE: do NOT switch task while it's in Kmode!!!
REASON: our MTX kernel is NOT a multi-processor kernel. But switch task
in Umode is perferctly OK.
3. Every 5 seconds: turn off FD if it is on; (write 0x0C to 0x3F2)
turn on FD if it is off; (write 0x1C to 0x3F2)
SAMPLE DISK: samples/timer.bin.gz
download, gunzip, dd to a disk, boot and run.
HELP files:
In samples/TIMER: ts.s is the assembly code file, use it as is
clock.c file : complete the thandler() C function
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
5. TIMER SERVICE:
With a timer in Kernel, tasks may request timer services as follows:
From Umode, implement a command:
Umode : set_timer t
which enters Kernel to set a timer request of t seconds. In genral, a task
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 task BLOCKs itself (sleep) until the time interval
expires.
LISTEN TO THE LECTURE for more details.
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
Sample Timer Service Programming
You may implement timer service in the mtx system.
SAMPLE DISK: samples/timer2.bin.gz
download, gunzip, then dd to a disk
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
6. Additional Experiments:
The (Master) 8259 interrupt controller has an
INT_CNTL register at 0x20
INT_MASK register at 0x21
(1). To enable IRQn (n = 0 to 7) interrupts,
bit n of INT_MASK register must be 0;
(bit n == 1 means IRQn is disabled)
(2). After each interrupt (of ANY kind), the interrupt handler
MUST explicitly re-enable the 8259 again by writing
0x20 to the INT_CNTL register.
Otherwise, no further interrupts can occur.
====================================================================
Put all these into action:
(0). Usually, BIOS initializes the 8259 in such a way that all IRQn are
enabled, so you don't have to enable them again. To be safe, you
should enable any specific IRQn that you plan to use. But you MUST
re-enable the 8259 after each interrupt in your interrupt handler.
(1). Try this: In your mtx's initialize() function, do
out_byte(0x21, 0xFF);
This will disable ALL interrupts, including KBD. ==> nothing
will work anymore, not even BIOS.
(2). In your clock.c function, try
out_byte(0x21, 0x01);
This will disable IRQ0 so that YOUR clock will no longer work.
(3). In your clock interrupt handler fucntion, do NOT
out_byte(INT_CNTL, 0x20);
at the end. Then try to type as hard as you can on the KND
to see what happens. WHY?
======================================================================