回到Main中,看下一个函数Init_Traps()
为中断向量12和13安装了一个中断处理函数GPF_Handler
GPF_Handler函数输出当前寄存器状态,并结束当前线程。
位于/src/geekos/trap.c
再看Main中的下一个函数Init_Timer初始化定时器
首先的Calbrate_Delay()函数用于测试系统时钟。
Calbrate_Delay位于./src/geekos/timer.c
初始化中断函数Timer_Calibrate后,它进入了while()循环,g_numTicks全局变量会在中断函数Timer_Calibrate中增加到CALIBRATE_NUM_TICKS。
看while()后面的Spin(INT_MAX)函数,这是理解的关键。
Spin()先设置eax为INT_MAX也就是4B的整型数最大值,为2147483647,然后循环,直到eax为0为止。
Calibrate_Delay中的while循环只是先让时钟中断运行起来。
看一下这个用于校正的中断函数Timer_Calibrate
位于/src/geekos/timer.c
当增加变量g_numTicks的值到CALIBRATE_NUM_TICKS之后,得到s_spinCountPerTick的值。
可以看到,在时钟中断处理函数中增加了当前的全局时钟g_numTicks和当前进程的时钟current->numTicks计数。
如果当前进程的时钟计数达到g_Quantum则标志需要重新调度。
g_currentThread和g_needReschedule均是全局变量。
可以看到它为键盘中断安装了中断响应函数Keyboard_Interrupt_Handler
按键按下和松开都会产生扫描码,同一个按键按下和松开的扫描码只有最高位的不同。
位于./src/geekos/trap.c
/*
* Initialize handlers for processor traps.
*/
void Init_Traps(void)
{
Install_Interrupt_Handler(12, &GPF_Handler); /* stack exception */
Install_Interrupt_Handler(13, &GPF_Handler); /* general protection fault */
}
为中断向量12和13安装了一个中断处理函数GPF_Handler
GPF_Handler函数输出当前寄存器状态,并结束当前线程。
位于/src/geekos/trap.c
/*
* Handler for general protection faults and other bad errors.
* Kill the current thread (which caused the fault).
*/
static void GPF_Handler(struct Interrupt_State* state)
{
/* Send the thread to the reaper... */
Print("Exception %d received, killing thread %p\n",
state->intNum, g_currentThread);
Dump_Interrupt_State(state);
Exit(-1);
/* We will never get here */
KASSERT(false);
}
再看Main中的下一个函数Init_Timer初始化定时器
位于./src/geekos/timer.c
void Init_Timer(void)
{
/*
* TODO: reprogram the timer to set the frequency.
* In bochs, it defaults to 18Hz, which is actually pretty
* reasonable.
*/
Print("Initializing timer...\n");
/* Calibrate for delay loop */
Calibrate_Delay();
Print("Delay loop: %d iterations per tick\n", s_spinCountPerTick);
/* Install an interrupt handler for the timer IRQ */
Install_IRQ(TIMER_IRQ, &Timer_Interrupt_Handler);
Enable_IRQ(TIMER_IRQ);
}
首先的Calbrate_Delay()函数用于测试系统时钟。
Calbrate_Delay位于./src/geekos/timer.c
/*
* Calibrate the delay loop.
* This will initialize s_spinCountPerTick, which indicates
* how many iterations of the loop are executed per timer tick.
*/
static void Calibrate_Delay(void)
{
Disable_Interrupts();
/* Install temporarily interrupt handler */
Install_IRQ(TIMER_IRQ, &Timer_Calibrate);
Enable_IRQ(TIMER_IRQ);
Enable_Interrupts();
/* Wait a few ticks */
while (g_numTicks < CALIBRATE_NUM_TICKS)
;
/*
* Execute the spin loop.
* The temporary interrupt handler will overwrite the
* loop counter when the next tick occurs.
*/
Spin(INT_MAX);
Disable_Interrupts();
/*
* Mask out the timer IRQ again,
* since we will be installing a real timer interrupt handler.
*/
Disable_IRQ(TIMER_IRQ);
Enable_Interrupts();
}
初始化中断函数Timer_Calibrate后,它进入了while()循环,g_numTicks全局变量会在中断函数Timer_Calibrate中增加到CALIBRATE_NUM_TICKS。
看while()后面的Spin(INT_MAX)函数,这是理解的关键。
位于./src/geekos/timer.c
/*
* Delay loop; spins for given number of iterations.
*/
static void Spin(int count)
{
/*
* The assembly code is the logical equivalent of
* while (count-- > 0) { // waste some time }
* We rely on EAX being used as the counter
* variable.
*/
int result;
__asm__ __volatile__ (
"1: decl %%eax\n\t"
"cmpl $0, %%eax\n\t"
"nop; nop; nop; nop; nop; nop\n\t"
"nop; nop; nop; nop; nop; nop\n\t"
"jg 1b"
: "=a" (result)
: "a" (count)
);
}
Spin()先设置eax为INT_MAX也就是4B的整型数最大值,为2147483647,然后循环,直到eax为0为止。
Calibrate_Delay中的while循环只是先让时钟中断运行起来。
看一下这个用于校正的中断函数Timer_Calibrate
位于/src/geekos/timer.c
/*
* Temporary timer interrupt handler used to calibrate
* the delay loop.
*/
static void Timer_Calibrate(struct Interrupt_State* state)
{
Begin_IRQ(state);
if (g_numTicks < CALIBRATE_NUM_TICKS)
++g_numTicks;
else {
/*
* Now we can look at EAX, which reflects how many times
* the loop has executed
*/
/*Print("Timer_Calibrate: eax==%d\n", state->eax);*/
s_spinCountPerTick = INT_MAX - state->eax;
state->eax = 0; /* make the loop terminate */
}
End_IRQ(state);
}
当增加变量g_numTicks的值到CALIBRATE_NUM_TICKS之后,得到s_spinCountPerTick的值。
流程如下:当g_numTicks==CALIBRATE_NUM_TICKS时,Calibrate_Delay函数中的循环退出,开始Spin()递减eax,过了一个tick之后,时钟中断触发,再时钟中断函数里看减了几次eax,以此来估计一次时钟中断的时间间隔。
随后才安装了真正的时钟中断函数Timer_Interrupt_Handler
时钟中断为0号外部中断,中断向量为32。
位于src/geekos/timer.c
static void Timer_Interrupt_Handler(struct Interrupt_State* state)
{
struct Kernel_Thread* current = g_currentThread;
Begin_IRQ(state);
/* Update global and per-thread number of ticks */
++g_numTicks;
++current->numTicks;
/*
* If thread has been running for an entire quantum,
* inform the interrupt return code that we want
* to choose a new thread.
*/
if (current->numTicks >= g_Quantum) {
g_needReschedule = true;
}
End_IRQ(state);
}
可以看到,在时钟中断处理函数中增加了当前的全局时钟g_numTicks和当前进程的时钟current->numTicks计数。
如果当前进程的时钟计数达到g_Quantum则标志需要重新调度。
g_currentThread和g_needReschedule均是全局变量。
再看Main中最后一个初始化函数!Init_Keyboard
位于./src/geekos/keyboard.c
void Init_Keyboard(void)
{
ushort_t irqMask;
Print("Initializing keyboard...\n");
/* Start out with no shift keys enabled. */
s_shiftState = 0;
/* Buffer is initially empty. */
s_queueHead = s_queueTail = 0;
/* Install interrupt handler */
Install_IRQ(KB_IRQ, Keyboard_Interrupt_Handler);
/* Enable IRQ1 (keyboard) */
irqMask = Get_IRQ_Mask();
irqMask &= ~(1 << KB_IRQ);
Set_IRQ_Mask(irqMask);
}
可以看到它为键盘中断安装了中断响应函数Keyboard_Interrupt_Handler
Keyboard_Interrupt_Handler位于./src/geekos/keyboard.c
/*
* Handler for keyboard interrupts.
*/
static void Keyboard_Interrupt_Handler(struct Interrupt_State* state)
{
uchar_t status, scanCode;
unsigned flag = 0;
bool release = false, shift;
Keycode keycode;//Keycode是两个字节
Begin_IRQ(state);
status = In_Byte(KB_CMD);
IO_Delay();
if ((status & KB_OUTPUT_FULL) != 0) {
/* There is a byte available */
scanCode = In_Byte(KB_DATA);//得到扫描码,按下键和松开键都会产生扫描码
IO_Delay();
/*
* Print("code=%x%s\n", scanCode, (scanCode&0x80) ? " [release]" : "");
*/
if (scanCode & KB_KEY_RELEASE) {//处理键盘松开时产生的扫描码
release = true;
scanCode &= ~(KB_KEY_RELEASE);
}
if (scanCode >= SCAN_TABLE_SIZE) {
Print("Unknown scan code: %x\n", scanCode);
goto done;
}
/* Process the key */
shift = ((s_shiftState & SHIFT_MASK) != 0);//s_shiftState 是全局的变量,用于保存上一个按下的功能键
keycode = shift ? s_scanTableWithShift[scanCode] : s_scanTableNoShift[scanCode];
/* Update shift, control and alt state */
switch (keycode) {
case KEY_LSHIFT:
flag = LEFT_SHIFT;//flag标志用于记录其他功能键
break;
case KEY_RSHIFT:
flag = RIGHT_SHIFT;
break;
case KEY_LCTRL:
flag = LEFT_CTRL;
break;
case KEY_RCTRL:
flag = RIGHT_CTRL;
break;
case KEY_LALT:
flag = LEFT_ALT;
break;
case KEY_RALT:
flag = RIGHT_ALT;
break;
default:
goto noflagchange;
}
//代码运行到这里说明扫描码是属于功能键
if (release)//是松开而产生的扫描码,比如说松开ctrl键
s_shiftState &= ~(flag);
else
s_shiftState |= flag;//保存标志
//功能键松开和按下的扫描码就是flag那一位的不同
/*
* Shift, control and alt keys don't have to be
* queued, flags will be set!
*/
goto done;
noflagchange:
/* Format the new keycode */
if (shift)
keycode |= KEY_SHIFT_FLAG;
if ((s_shiftState & CTRL_MASK) != 0)//处理功能键按下的字符。
keycode |= KEY_CTRL_FLAG;
if ((s_shiftState & ALT_MASK) != 0)
keycode |= KEY_ALT_FLAG;
if (release)
keycode |= KEY_RELEASE_FLAG;
/* Put the keycode in the buffer */
Enqueue_Keycode(keycode);
/* Wake up event consumers */
Wake_Up(&s_waitQueue);//唤醒在队列上等待读取缓冲区的进程
/*
* Pick a new thread upon return from interrupt
* (hopefully the one waiting for the keyboard event)
*/
g_needReschedule = true;//标记全局变量,让系统进行任务调度
}
done:
End_IRQ(state);
}
按键按下和松开都会产生扫描码,同一个按键按下和松开的扫描码只有最高位的不同。
通过一系列的对功能键的处理和判断,最后得到keycode,放入键盘缓冲区中。
至此,Main中的所有初始化函数结束,在接下来的学习中,我们会对系统有一个更深入的理解。。