Thursday, November 21, 2013

interrupt handling in Xinu

Further investigation revealed that a source of the CPU usage was likely the timed interrupt wakeups found in clkint.S. The code looked like this:
clkint:
pushal
cli
movb $EOI,%al
outb %al,$OCW1_2
incl ctr1000
subw $1,count1000
ja cl1
incl clktime
movw $1000,count1000
cl1:
cmpl $0,slnonempty
je clpreem
movl sltop,%eax
decl (%eax)
jg clpreem
call wakeup
clpreem: decl preempt
jg clret
call resched
clret:
sti
popal
iret
Apparently, this code was being called by an interrupt and was thereby providing the system with a sort of timer that it was using to do scheduling, or preemption, or both. However, despite the counting code being relatively easy to decipher, there was still quite a bit of mystery to me here.

Not knowing very much about interrupts and their methods of operation, I decided to do some research into interrupts before proceeding. I first discovered that Xinu has an interrupt table initialization routine in evec.c called initevec(), which is called inside sysinit() in initialize.c. This appears to set NULL interrupt handlers for all interrupts, then to initialize the Intel 8259 Programmable Interrupt Controller (PIC), and then to mask all maskable interrupts (IRQ0-IRQ15) using setirmask, which writes a bitwise interrupt mask to the PIC.

So with no interrupts being actively handled by the operating system after initialization, it was time to find out who was enabling the interrupt that was causing my increased CPU usage. I eventually traced the call to the function clkinit() in clkinit.c, called from the very end of the sysinit() function in initialize.c:

void clkinit(void)
{
uint16 intv;

/* Set interrupt vector for clock to invoke clkint */
set_evec(IRQBASE, (uint32)clkint);

intv = 1193; /* clock rate in KHz */

sleepq = newqueue();
preempt = QUANTUM;

slnonempty = FALSE;

clktime = 0;

/*  set to: timer 0, 16-bit counter, rate generator mode,
counter is binary */
outb(CLKCNTL, 0x34);

/* must write LSB first, then MSB */
outb(CLOCK0, (char) (0xff & intv) );
outb(CLOCK0, (char) (0xff & (intv>>8)));
return;
}

This code, it seemed, was responsible for activating an interrupt handler for and unmasking IRQ0, and then setting up some sort of periodic interrupt with an unknown timer found at the bus address stored in CLKCNTL. Though this part of the source code gave no clues as to what type of device this was, I soon discovered that the Intel 8253 is a Programmable Interval Timer (PIT) that appears in all x86 systems.

My first attempt at tinkering with the PIT was to simply change the frequency, but my initial experiments were confusing. It seemed that lowering the value of intv (apparently the timer rate) caused my timer to speed up, while increasing it caused the timer to slow down. I therefore tracked down the Intel manual for the 8254-2, the specific superset more commonly in use today, and discovered that the rate is actually a divisor instead of a frequency. The internal counting element is set to the number provided, and then, at a rate of ~1.193181.81 MHz, decrements the counter. Upon reaching, 0, the counter fires an interrupt and, if set as a rate-generating counter, resets itself to the original value and begins decrementing again. 

Further documentation revealed that the 8253 includes not one but three timers. After verifying that, by changing the divisor of Timer 0 (and thereby breaking the accuracy of the Xinu system clock), I could lower the CPU usage of the Xinu system to as low as 8%, I decided that the best thing to do would be to somehow use the second and third timers in the 8253 to provide different interrupt rates under different system loads, while preserving the system's ability to keep accurate time.

Unfortunately, after writing the necessary code to set up and activate the timer interrupts from Timer 1 and Timer 2, I was disappointed to find that no interrupts were arriving from those timers. A long investigation culminated in the discovery that those timers are often not hooked to anything in modern architectures, and that Timer 0 is definitely the only timer hooked up to IRQ0. Thus, in order to preserve the core system functionality of millisecond sleeps (found in sleep.c) alongside accurate system timekeeping, it would be necessary to find another source of periodic interrupts.

No comments:

Post a Comment