位于./src/geekos/keyboard.c
/* * Wait for a keycode to arrive. * Uses the keyboard wait queue to sleep until * a keycode arrives. */ Keycode Wait_For_Key(void) { bool gotKey, iflag; Keycode keycode = KEY_UNKNOWN; iflag = Begin_Int_Atomic(); do { gotKey = !Is_Queue_Empty(); if (gotKey) keycode = Dequeue_Keycode(); else Wait(&s_waitQueue); } while (!gotKey); End_Int_Atomic(iflag); return keycode; }
总结,Wait_For_Queue完成:从缓冲区中取数据,每次取一个,缓冲区中若无数据则等待,直到有数据为止。
好了,接下来只要创建一个内核线程并调用Wait_For_Queue就行。
参照Init_Scheduler函数中的Start_Kernel_Thread(Reaper, 0, PRIORITY_NORMAL, true)
来使用Start_Kernel_Thread(print_out, 0, PRIORITY_NORMAL, true)试试看!!
在Main函数上面加入print_out函数
static void print_out(ulong_t arg) { while(1) { int key = Wait_For_Key(); int finish = ('d' | KEY_CTRL_FLAG); if( key == finish ){ Print("finish input \n"); Exit(0); }else Print("%c", key); } }
运行时发现按一个键会打印2个同样的字符,这是因为按下和松开都会产生扫描码,中断处理程序都识别并压入了缓冲区。
好了,project0完成了,^_^!!
让我们来玩一些简单而又更酷的。
试试启动两个进程,一个进程打印A,另一个进程打印B。
添加代码
Start_Kernel_Thread(print_A, 0, PRIORITY_NORMAL, true);
Start_Kernel_Thread(print_B, 0, PRIORITY_NORMAL, true);
在Main函数上方添加函数
static void print_A(ulong_t arg) { while(1) { Print("A"); } } static void print_B(ulong_t arg) { while(1) { Print("B"); } }
^_^,果然,屏幕一会儿输出A,一会儿输出B。如果你完成了project0的话,你也可以输入字符,这不就是活生生的多任务么。
包括内核线程Idle和Reaper。系统里就运行着5个任务。。
下面再来细致的分析一下GeekOS中的多任务机制。
1.时钟中断产生线程切换
任务print_A---->时钟中断---->从通用Handle_Interrupt进入特定中断函数Timer_Interrupt_Handler---->加时钟滴答---->滴答未到期---->返回Handle_Interrupt恢复堆栈回来运行print_A
任务print_A---->时钟中断---->从通用Handle_Interrupt进入特定中断函数Timer_Interrupt_Handler---->加时钟滴答---->滴答到期,置全局标志g_needReschedule表示需要重新调度---->返回Handle_Interrupt调用Get_Next_Runnable将新要运行的线程结构体赋给g_currentThread,并恢复新线程的堆栈---->最后iret返回,运行新进程。
2.键盘中断产生线程切换
任务print_A---->按了一下键位,产生键盘中断---->从通用Handle_Interrupt进入特定中断函数Keyboard_Interrupt_Handler---->处理扫描码得到显示字符keycode,并将keycode放入队列,唤醒等待键盘缓冲区的进程之后,直接置全局标志g_needReschedule表示需要重新调度---->返回Handle_Interrupt调用Make_Runnable将新要运行的线程结构体赋给g_currentThread并恢复新线程的堆栈---->最后iret返回,运行新进程。