# Timer 要補足 timer 這樣才可以往任務切換前進,中間跳過一些 有關畫面和緩衝區的一些優化詳細可能要看書 今天要補充的就是,能不能透過 系統的 PIT "Programmable Interval Timer",讓我們透過設定 PIT 去控制中斷比如說每幾秒呼叫中斷一次IRQ0、這個功能用來計時或者固定時間用來呼叫 系統process 調度 很有用處(每幾秒切換到其他process 等等 IRQ0的中断周期变更: * AL=0x34:OUT(0x43,AL); * AL=中断周期的低8位; OUT(0x40,AL); * AL=中断周期的高8位; OUT(0x40,AL); 如果指定中断周期为0,会被看作是指定为65536。实际的中断产生的频率是单位时间 时钟周期数(即主频)/设定的数值。比如设定值如果是1000,那么中断产生的频率就 是1.19318KHz。设定值是10000的话,中断产生频率就是119.318Hz。再比如设定值是 11932的话,中断产生的频率大约就是100Hz了,即每10ms发生一次中断。 我们不清楚其中的详细原理,只知道只要执行3次OUT指令设定就完成了。将中断周期设定 为11932的话,中断频率好像就是100Hz,也就是说1秒钟会发生100次中断。那么我们就设定成这 个值吧。把11932换算成十六进制数就是0x2e9c,下面是我们编写的函数init_pit ## 設置PIT ### timer.c ```c= #define PIT_CTRL 0x0043 #define PIT_CNT0 0x0040 void init_pit(void) { io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); return; } ``` ### bootback.c 這邊會去呼叫 init_pit 這樣 IRQ0 就會在一秒內發生 100 次中斷了 ```c= void HariMain(void) { (中略) init_gdtidt(); init_pic(); io_sti(); /* IDT/PIC的初始化已经结束,所以解除CPU的中断禁止*/ fifo8_init(&keyfifo, 32, keybuf); fifo8_init(&mousefifo, 128, mousebuf); init_pit(); /* 这里! */ io_out8(PIC0_IMR, 0xf8); /* PIT和PIC1和键盘设置为许可(11111000) */ /* 这里! */ io_out8(PIC1_IMR, 0xef); /* 鼠标设置为许可(11101111) */ (中略) } ``` ### timer.c ```c= void inthandler20(int *esp) { io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */ /* 暂时什么也不做 */ return; } ``` ### naskfunc.nas ```asm _asm_inthandler20: PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler20 POP EAX POPAD POP DS POP ES IRETD ``` ### dsctbl.c 跟鍵盤一樣註冊到idt init_gdtidt ```c= void init_gdtidt(void) { (中略) /* IDT的设定 */ set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); /* 这里! */ set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); return; } ``` ## 開始計時 ### bootback.h ```c= struct TIMERCTL { unsigned int count; }; ``` ### timer.c ```c= struct TIMERCTL timerctl; void init_pit(void) { io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; /* 这里! */ return; } void inthandler20(int *esp) { io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */ timerctl.count++; /* 这里! */ return; } ``` ### bootback.c ```c= void HariMain(void) { (中略) for (;;) { sprintf(s, "%010d", timerctl.count); /* 这里! */ boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); sheet_refresh(sht_win, 40, 28, 120, 44); (中略) } } ``` ![](https://i.imgur.com/tIazP5h.png) ## timeout 結合上一節,有了計時功能,在後續實作方面在改善優化完的程式架構我們就可以結合計時器來做我們的 benchmark 也就是去計算優化完和優化前的時間去做對比,可以看到我們的鍵盤透過我們按下按鍵發送數據到緩衝區,滑鼠因為一次發送大量數據給緩衝區,緩衝區的作用可以用來解決連續數據判斷問題(左邊的 ctl 兩個數據),或者滑鼠要等到 三個數據才進行移動滑鼠或者 按下按鍵的判斷等等,作者想把我們的 timer 做成有緩衝區的樣子這樣我們就可以套用 fifo的樣子進行緩衝區的讀寫。 ## bootback.h ```c= struct TIMERCTL { unsigned int count; unsigned int timeout; struct FIFO8 *fifo; unsigned char data; }; ``` ## timer.c ```c= void init_pit(void) { io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; timerctl.timeout = 0; return; } void inthandler20(int *esp) { io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; if (timerctl.timeout > 0) { /* 如果已经设定了超时 */ timerctl.timeout--; if (timerctl.timeout == 0) { fifo8_put(timerctl.fifo, timerctl.data); } } return; } void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data) { int eflags; eflags = io_load_eflags(); io_cli(); timerctl.timeout = timeout; timerctl.fifo = fifo; timerctl.data = data; io_store_eflags(eflags); return; } ``` ## bootback.c CLI和STI。所谓CLI,是将中断标志(interrupt flag)置为0的指令(clear interrupt flag)。 STI是要将这个中断标志置为1的指令(set interrupt flag)。 CLI汇编指令全称为Clear Interupt,该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用。 STI汇编指令全称为Set Interupt,该指令的作用是允许中断发生,在STI起效之后,所有外部中断都被恢复,这样可以打破被保护代码的运行,允许硬件中断转而处理中断的作用。 可能作者想要過濾在某些時候只允許某些時候這個中斷避免被干擾 cli 禁止中断发生 sti 允许中断发生 在对 ss 和sp操作的时候, 如果有中断发生,中断的保存现场的操作是将相关寄存器值保存到ss:sp指向的地址. 如果ss 或者sp没有完成赋值操作, 这时候ss:sp指向的地址则是不期望的地方. 如果将系统或者其他应用的数据覆盖,会导致系统/应用崩溃. 下面是两条规则: 1)在改变ss:sp之前,必须用cli指令屏蔽中断,然后等操作执行完立即用sti指令恢复 2)ss:sp需要设置在空闲的内存地址,不要建立在其他的程序代码区 正确的写法: cli mov ax,0b900h mov ss,ax mov sp,100h sti ```c= void HariMain(void) { (中略) struct FIFO8 timerfifo; char s[40], keybuf[32], mousebuf[128], timerbuf[8]; (中略) fifo8_init(&timerfifo, 8, timerbuf); settimer(1000, &timerfifo, 1); (中略) for (;;) { (中略) io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) { io_sti(); } else { if (fifo8_status(&keyfifo) != 0) { io_sti(); (中略) } else if (fifo8_status(&mousefifo) != 0) { io_sti(); (中略) } else if (fifo8_status(&timerfifo) != 0) { i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */ io_sti(); putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]"); sheet_refresh(sht_back, 0, 64, 56, 80); } } } } ``` ## Mutiple timer 這一節調整了 timer 的 struct 可以動態的設置多個 timer 陣列 這樣我就可以比較快速模組化的設置timer 各自的timer 也有各自的陣列存到相對應的 timer 還實現了一個閃爍的 光標 ### timer.c ```c= #define TIMER_FLAGS_ALLOC 1 /* 已配置状态 */ #define TIMER_FLAGS_USING 2 /* 定时器运行中 */ void init_pit(void) { int i; io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timer[i].flags = 0; /* 未使用 */ } return; } struct TIMER *timer_alloc(void) { int i; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == 0) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; return &timerctl.timer[i]; } } return 0; /* 没找到 */ } void timer_free(struct TIMER * timer) { timer->flags = 0; /* 未使用 */ return; } void timer_init(struct TIMER * timer, struct FIFO8 * fifo, unsigned char data) { timer->fifo = fifo; timer->data = data; return; } void timer_settime(struct TIMER * timer, unsigned int timeout) { timer->timeout = timeout; timer->flags = TIMER_FLAGS_USING; return; } void inthandler20(int *esp) { int i; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC*/ timerctl.count++; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { timerctl.timer[i].timeout--; if (timerctl.timer[i].timeout == 0) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } } } return; } ``` ### bootback.c ```c= void HariMain(void) { (中略) struct FIFO8 timerfifo, timerfifo2, timerfifo3; char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8]; struct TIMER *timer, *timer2, *timer3; (中略) fifo8_init(&timerfifo, 8, timerbuf); timer = timer_alloc(); timer_init(timer, &timerfifo, 1); timer_settime(timer, 1000); fifo8_init(&timerfifo2, 8, timerbuf2); timer2 = timer_alloc(); timer_init(timer2, &timerfifo2, 1); timer_settime(timer2, 300); fifo8_init(&timerfifo3, 8, timerbuf3); timer3 = timer_alloc(); timer_init(timer3, &timerfifo3, 1); timer_settime(timer3, 50); (中略) for (;;) { (中略) io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) + fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) { io_sti(); } else { if (fifo8_status(&keyfifo) != 0) { (中略) } else if (fifo8_status(&mousefifo) != 0) { (中略) } else if (fifo8_status(&timerfifo) != 0) { i = fifo8_get(&timerfifo); /* 首先读入(为了设定起始点) */ io_sti(); putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]"); sheet_refresh(sht_back, 0, 64, 56, 80); } else if (fifo8_status(&timerfifo2) != 0) { i = fifo8_get(&timerfifo2); /* 首先读入(为了设定起始点) */ io_sti(); putfonts8_asc(buf_back, binfo->scrnx, 0, 80, COL8_FFFFFF, "3[sec]"); sheet_refresh(sht_back, 0, 80, 48, 96); } else if (fifo8_status(&timerfifo3) != 0) { /* 模拟光标 */ i = fifo8_get(&timerfifo3); io_sti(); if (i != 0) { timer_init(timer3, &timerfifo3, 0); /* 然后设置0 */ boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111); } else { timer_init(timer3, &timerfifo3, 1); /* 然后设置1 */ boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111); } timer_settime(timer3, 50); sheet_refresh(sht_back, 8, 96, 16, 112); } } } } ``` ## Optimization Interrupt 既然要實作 timer 實作我們最高可能一次要對 500個timer 發生中斷這邊我快總結一下,他的優化過程 第一個部分就是 timerctl.timer[i].timeout--; ```c= void inthandler20(int *esp) { int i; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC*/ timerctl.count++; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { timerctl.timer[i].timeout--; if (timerctl.timer[i].timeout == 0) { timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } } } return; } ``` 改為好處我們一開始不是有啟動時間嗎,他現在不改用 每次--歸零,改用創建時間+預設時間我們就過得知那些timer timeout,因為 timer 是每一秒觸發中斷一百次的也就是timerctl.count,也就意味著一秒 timerctl.count = 100 則假設有一個timer timerout 設為 1000 也就是 10秒 那就是目前中斷時間 timerctl.count +上 timer timerout 也就是 1000 也就是這個 timer 預計在 1100會觸發 也就是 11 秒 ,那這些意思就是 有個 timer 在系統 1 秒的時候創建 預估在10秒內發生timeout 。 ```c= void inthandler20(int *esp) { int i; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { if (timerctl.timer[i].timeout <= timerctl.count) { /* 这里! */ timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } } } return; } void timer_settime(struct TIMER *timer, unsigned int timeout) { timer->timeout = timeout + timerctl.count; /* 这里! */ timer->flags = TIMER_FLAGS_USING; return; } ``` ## timer 校正回歸 上面程式碼改動後,另一個問題就是假設發生 同时也正是因为变成了这种方式,在我们这个纸娃娃操作系统中,启动以后经过42 949 673 秒后,count就是0xffffffff了,比这个值再大就不能设定了。这么多秒是几天呢?……嗯,请稍等 (用计算器算一下)……大约是497天。也就是大约一年就要重新启动一次操作系统,让count归0。 假設理解沒錯誤的話,大概一年調一次,就是所有timer 去做歸零,這邊可以看到 為了在調整的時候其他timer 發生問題,所以前後加上 cli 和 sti 去做更新,也就是當下假設發生中斷都不理會直到我把timer 全部更新完 在接收中斷 ```c= int t0 = timerctl.count; /* 所有时刻都要减去这个值 */ io_cli(); /* 在时刻调整时禁止定时器中断 */ timerctl.count -= t0; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { timerctl.timer[i].timeout -= t0; } } io_sti(); ``` ## 重新調正timer 這邊大概就是繼續加速中斷流程,主要要解決每次要判斷全部的timer 太耗時 他舉個例子三個timer 0.3 50 20 這些timer 最先要處理的是誰 當然是0.3 那麼下面的程式碼一開始設為最大每次遇到比小的就把 timerctl.next 設為最小 ```c= timerctl.next = 0xffffffff; if (timerctl.next > timerctl.timer[i].timeout){ timerctl.next = timerctl.timer[i].timeout; } ``` 那麼迴圈結束後下次中斷假設還沒來到最小的timeout 值就直接return ```c= if (timerctl.next > timerctl.count) { return; /* 还不到下一个时刻,所以结束*/ } ``` ### ootpack.h ```c= struct TIMERCTL { unsigned int count, next; /* 这里! */ struct TIMER timer[MAX_TIMER]; }; ``` ### timer.c ```c= void inthandler20(int *esp) { int i; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; if (timerctl.next > timerctl.count) { return; /* 还不到下一个时刻,所以结束*/ } timerctl.next = 0xffffffff; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timer[i].flags == TIMER_FLAGS_USING) { if (timerctl.timer[i].timeout <= timerctl.count) { /* 超时 */ timerctl.timer[i].flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data); } else { /* 还没有超时 */ if (timerctl.next > timerctl.timer[i].timeout) { timerctl.next = timerctl.timer[i].timeout; } } } } return; } ``` ## timer 位移 大致程式碼就是,之前那個做法,我們全部跑一次 timer 去抓最小值,現在這個做法就是 使用一個 timerctl.using 去看現在有幾個 timer 在運行,也就是在  settime 的時候進行timer 陣列的調整 ,插入 新的 timer (最小往前排 那麼他就假設會進到inthandler20 發生中斷時候, TIMER_FLAGS_USING拿掉(因為一定就是有一個 timer 進行活動則 接下來一定會發生中斷然後找到最前面的 timer )然後 break 這個時候timer 一定是最小發生中斷完後, timer陣列往前移 (最小) 這樣也不用再 去找最大值 所以整個概念從,每次中斷都要找最小值變成 在settime 的時候就進行排序。 ### timer.c ```c= void inthandler20(int *esp) { int i, j; io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收结束的信息通知给PIC */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } for (i = 0; i < timerctl.using; i++) { /* timers的定时器都处于动作中,所以不确认flags */ if (timerctl.timers[i]->timeout > timerctl.count) { break; } /* 超时*/ timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data); } /* 正好有i个定时器超时了。其余的进行移位。 */ timerctl.using -= i; for (j = 0; j < timerctl.using; j++) { timerctl.timers[j] = timerctl.timers[i + j]; } if (timerctl.using > 0) { timerctl.next = timerctl.timers[0]->timeout; } else { timerctl.next = 0xffffffff; } return; } ``` ```c= void timer_settime(struct TIMER * timer, unsigned int timeout) { int e, i, j; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); /* 搜索注册位置 */ for (i = 0; i < timerctl.using; i++) { if (timerctl.timers[i]->timeout >= timer->timeout) { break; } } /* i号之后全部后移一位 */ for (j = timerctl.using; j > i; j--) { timerctl.timers[j] = timerctl.timers[j - 1]; } timerctl.using ++; /* 插入到空位上 */ timerctl.timers[i] = timer; timerctl.next = timerctl.timers[0]->timeout; io_store_eflags(e); return; } ``` ```c= void init_pit(void) { int i; io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; timerctl.next = 0xffffffff; /* 因为最初没有正在运行的定时器 */ timerctl.using = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timers0[i].flags = 0; /* 未使用 */ } return; } struct TIMER *timer_alloc(void) { int i; for (i = 0; i < MAX_TIMER; i++) { if (timerctl.timers0[i].flags == 0) { timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC; return &timerctl.timers0[i]; } } return 0; /* 没找到 */ } ``` ![](https://i.imgur.com/BZm6NNe.png)