《30天自制操作系統》筆記(10)——定時器


《30天自制操作系統》筆記(10)——定時器

進度回顧

上一篇上上一篇解決了繪制窗口和窗口刷新的問題。關於窗口的東西就此告一段落。本篇介紹一個相對獨立且十分重要的操作系統部件——定時器的使用方法。

定時器是一個硬件

可編程的間隔型定時器(Programmable Interval Timer)簡稱定時器(PIT),是集成到電腦上的一個硬件部件。之前講過的用於實現中斷機制的PIC也是個硬件部件。有了PIT,我們才能在計算機中計時。

初始化定時器

前面,CPU、PIC都需要設置好才能用,PIT也需要設置。PIT類似C#Winform里的Timer控件,能設置的只有激發Tick事件的時間間隔(Interval)這個屬性。PIT里的Tick事件,對應的是PIC里的0號中斷。也就是說,PIT會根據你設定的Interval,每隔Interval時間就發送一個0號中斷。這里又印證了"事件小名中斷"的說法。

 1 #define PIT_CTRL 0x0043
 2 #define PIT_CNT0 0x0040
 3 void init_pit(void)
 4 {
 5     io_out8(PIT_CTRL, 0x34);/*中斷周期(Interval)即將變更*/
 6     io_out8(PIT_CNT0, 0x9c);/*中斷周期的低8位*/
 7     io_out8(PIT_CNT0, 0x2e);/*中斷周期的高8位*/
 8     return;
 9 }
10 void HariMain(void)
11 {
12     /**/
13     init_gdtidt();
14     init_pic();
15     io_sti(); /* IDT/PIC的初始化已經結束,所以解除CPU的中斷禁止 */
16     fifo8_init(&keyfifo, 32, keybuf);
17     fifo8_init(&mousefifo, 128, mousebuf);
18     init_pit();/* 這里! */
19     io_out8(PIC0_IMR, 0xf8); /* PIT和PIC1和鍵盤設置為許可(11111000) *//* 這里! */
20     io_out8(PIC1_IMR, 0xef); /* 鼠標設置為許可(11101111) */
21     /**/
22 }

 

設置中斷函數

設置Tick時,如果指定中斷周期為0,會被看做指定為65536。如果設定為1000,中斷頻率就是1.19318Hz。如果設定為11932,中斷頻率就是100Hz,即每10ms發生一次中斷。11932寫成十六進制就是0x2e9c

PIT會發送0號中斷,那就得寫一個響應此中斷的函數。

1 void inthandler20(int *esp)
2 {
3     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信號接收完了的信息通知PIC */
4     /* TODO: 暫時什么也不做 */
5     return;
6 }

 

 1 _asm_inthandler20:
 2         PUSH    ES
 3         PUSH    DS
 4         PUSHAD
 5         MOV        EAX,ESP
 6         PUSH    EAX
 7         MOV        AX,SS
 8         MOV        DS,AX
 9         MOV        ES,AX
10         CALL    _inthandler20 ; 這里會調用void inthandler20(int *esp);函數
11         POP        EAX
12         POPAD
13         POP        DS
14         POP        ES
15         IRETD

 

 1 void init_gdtidt(void)
 2 {
 3     /**/
 4     /* IDT的設定 */
 5     set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);/* 這里! */
 6     set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
 7     set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
 8     set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
 9 
10     return;
11 }

這樣就好了。

用PIT做點什么呢?

Hello PIT!

保守起見,先做個PIT的hello world比較好。

 1 struct TIMERCTL {
 2     unsigned int count;
 3 };
 4 struct TIMERCTL timerctl;
 5 void init_pit(void)
 6 {
 7     io_out8(PIT_CTRL, 0x34);
 8     io_out8(PIT_CNT0, 0x9c);
 9     io_out8(PIT_CNT0, 0x2e);
10     timerctl.count = 0;/* 這里! */
11     return;
12 }
13 void inthandler20(int *esp)
14 {
15     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信號接收完了的信息通知PIC */
16     timerctl.count++;/* 這里! */
17     return;
18 }
19 void HariMain(void)
20 {
21     /**/
22     for (;;) {
23         sprintf(s, "%010d", timerctl.count);/* 這里! */
24         boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
25         putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
26         sheet_refresh(sht_win, 40, 28, 120, 44);
27         
28         /**/
29     }
30 }

效果如下圖所示。

定時器的hello world!

 

超時功能

定時器經常被用於這樣一種情形:"hi操作系統老兄!麻煩你10秒鍾后通知我一下,我要執行某函數M"。這樣的功能就叫做超時(timeout)

 1 struct TIMERCTL {
 2     unsigned int count;
 3     unsigned int timeout;
 4     struct FIFO8 *fifo;
 5     unsigned char data;
 6 };
 7 void init_pit(void)
 8 {
 9     io_out8(PIT_CTRL, 0x34);
10     io_out8(PIT_CNT0, 0x9c);
11     io_out8(PIT_CNT0, 0x2e);
12     timerctl.count = 0;
13     timerctl.timeout = 0;
14     return;
15 }
16 void inthandler20(int *esp)
17 {
18     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信號接收完了的信息通知PIC */
19     timerctl.count++;
20     if (timerctl.timeout > 0) { /* 如果已經設定了超時 */
21         timerctl.timeout--;
22         if (timerctl.timeout == 0) {
23             fifo8_put(timerctl.fifo, timerctl.data);
24         }
25     }
26     return;
27 }
28 void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
29 {
30     int eflags;
31     eflags = io_load_eflags();
32     io_cli();
33     timerctl.timeout = timeout;
34     timerctl.fifo = fifo;
35     timerctl.data = data;
36     io_store_eflags(eflags);
37     return;
38 }
39 void HariMain(void)
40 {
41     /**/
42     struct FIFO8 timerfifo;
43     char s[40], keybuf[32], mousebuf[128], timerbuf[8];
44     /**/
45     fifo8_init(&timerfifo, 8, timerbuf);
46     settimer(1000, &timerfifo, 1);
47     /**/
48     for (;;) {
49         /**/
50         io_cli();
51         if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) {
52             io_sti();
53         } else {
54             if (fifo8_status(&keyfifo) != 0) {
55                 /**/
56             } else if (fifo8_status(&mousefifo) != 0) {
57                 /**/
58             } else if (fifo8_status(&timerfifo) != 0) {
59                 i = fifo8_get(&timerfifo); /* 首先讀入(為了設定起始點) */
60                 io_sti();
61                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
62                 sheet_refresh(sht_back, 0, 64, 56, 80);
63             }
64         }
65     }
66 }
超時功能的hello world

程序很簡單,我們在其中設定10秒鍾后向timerinfo寫入"1"(暫時沒什么特別的含義,寫"2"也沒問題),而timerinfo接收到數據時,就會在屏幕上顯示"10[sec]"。

圖就不貼了,沒什么新東西。

設定多個定時器

很多應用程序都會使用定時器,所以PIT要能夠變幻出多個定時器。

 1 #define MAX_TIMER        500
 2 struct TIMER {
 3     unsigned int timeout, flags;
 4     struct FIFO8 *fifo;
 5     unsigned char data;
 6 };
 7 struct TIMERCTL {
 8     unsigned int count;
 9     struct TIMER timer[MAX_TIMER];
10 };
11 
12 #define TIMER_FLAGS_ALLOC        1    /* 已配置狀態 */
13 #define TIMER_FLAGS_USING        2    /* 定時器運行中 */
14 void init_pit(void)
15 {
16     int i;
17     io_out8(PIT_CTRL, 0x34);
18     io_out8(PIT_CNT0, 0x9c);
19     io_out8(PIT_CNT0, 0x2e);
20     timerctl.count = 0;
21     for (i = 0; i < MAX_TIMER; i++) {
22         timerctl.timer[i].flags = 0; /* 未使用 */
23     }
24     return;
25 }
26 struct TIMER *timer_alloc(void)
27 {
28     int i;
29     for (i = 0; i < MAX_TIMER; i++) {
30         if (timerctl.timer[i].flags == 0) {
31             timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
32             return &timerctl.timer[i];
33         }
34     }
35     return 0; /* 沒找到 */
36 }
37 void timer_free(struct TIMER *timer)
38 {
39     timer->flags = 0; /* 未使用 */
40     return;
41 }
42 void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
43 {
44     timer->fifo = fifo;
45     timer->data = data;
46     return;
47 }
48 void timer_settime(struct TIMER *timer, unsigned int timeout)
49 {
50     timer->timeout = timeout;
51     timer->flags = TIMER_FLAGS_USING;
52     return;
53 }
54 void inthandler20(int *esp)
55 {
56     int i;
57     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信號接收完了的信息通知PIC */
58     timerctl.count++;
59     for (i = 0; i < MAX_TIMER; i++) {
60         if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
61             timerctl.timer[i].timeout--;
62             if (timerctl.timer[i].timeout == 0) {
63                 timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
64                 fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
65             }
66         }
67     }
68     return;
69 }
設定MAX_TIMER個定時器

 

 1 void HariMain(void)
 2 {
 3     /**/
 4     struct FIFO8 timerfifo, timerfifo2, timerfifo3;
 5     char s[40], keybuf[32], mousebuf[128], timerbuf[8], timerbuf2[8], timerbuf3[8];
 6     struct TIMER *timer, *timer2, *timer3;
 7     /**/
 8     fifo8_init(&timerfifo, 8, timerbuf);
 9     timer = timer_alloc();
10     timer_init(timer, &timerfifo, 1);
11     timer_settime(timer, 1000);
12     fifo8_init(&timerfifo2, 8, timerbuf2);
13     timer2 = timer_alloc();
14     timer_init(timer2, &timerfifo2, 1);
15     timer_settime(timer2, 300);
16     fifo8_init(&timerfifo3, 8, timerbuf3);
17     timer3 = timer_alloc();
18     timer_init(timer3, &timerfifo3, 1);
19     timer_settime(timer3, 50);
20     /**/
21     for (;;) {
22         /**/
23         io_cli();
24         if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo)
25                 + fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) {
26             io_sti();
27         } else {
28             if (fifo8_status(&keyfifo) != 0) {
29                 /**/
30             } else if (fifo8_status(&mousefifo) != 0) {
31                 /**/
32             } else if (fifo8_status(&timerfifo) != 0) {
33                 i = fifo8_get(&timerfifo); /* 首先讀入(為了設定起始點) */
34                 io_sti();
35                 putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
36                 sheet_refresh(sht_back, 0, 64, 56, 80);
37             } else if (fifo8_status(&timerfifo2) != 0) {
38                 i = fifo8_get(&timerfifo2); /* 首先讀入(為了設定起始點) */
39                 io_sti();
40                 putfonts8_asc(buf_back, binfo->scrnx, 0, 80, COL8_FFFFFF, "3[sec]");
41                 sheet_refresh(sht_back, 0, 80, 48, 96);
42             } else if (fifo8_status(&timerfifo3) != 0) {/* 模擬光標閃爍 */
43                 i = fifo8_get(&timerfifo3);
44                 io_sti();
45                 if (i != 0) {
46                     timer_init(timer3, &timerfifo3, 0); /* 然后設置0 */
47                     boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
48                 } else {
49                     timer_init(timer3, &timerfifo3, 1); /* 然后設置1 */
50                     boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
51                 }
52                 timer_settime(timer3, 50);
53                 sheet_refresh(sht_back, 8, 96, 16, 112);
54             }
55         }
56     }
57 }
使用多個定時器

 

定時器優化

前面都算是使用定時器的實驗,以此為基礎進行優化,使其更實用。

原作者的優化進行了好幾步,在此僅羅列一下,並給出最后的程序。

  • 將timeout的含義從"所剩時間"改變為"予定時間",這樣就可以去掉inthandler20(int*)函數里的"timerctl.timer[i].timeout--"。
  • 現在的定時器,每隔42949673秒(497天)后count就是0xFFFFFFFF了,在這之前必須重啟計算機,否則程序就會出錯。因此讓OS每隔一年自動調整一次。
  • timer數組按timeout升序排序,在inthandler20(int*)中每次只檢查第一個timer元素即可。
  • 上一步中,發現超時時,inthandler20(int*)會准備下一個要檢查的timer,這延長了處理時間。為解決這個問題,增加變量using,用於記錄有幾個定時器處於活動中(需要檢查)。(類似於窗口圖層部分的sheet中的top)不過這樣只能緩解問題,不能徹底解決問題。
  • 將靜態數組timers改為鏈表,從而省掉了上一步中可能發生的移位操作。
  • 使用數據結構中的"哨兵"概念簡化上一步的鏈表處理函數。"哨兵"是為了簡化循環的邊界條件而引入的。在timers鏈表最后加上一個timeout為0xFFFFFFFF的定時器(作為哨兵)。由於OS會在1年后將定時器count重置,所以這個哨兵定時器永遠不會到達觸發的時候。這其實就是永恆吊車尾啊。不管你信不信,添上這樣一個吊車尾就可以減少鏈表相關的代碼。

 

經過若干次優化后的代碼如下。

 1 void init_pit(void)
 2 {
 3     int i;
 4     struct TIMER *t;
 5     io_out8(PIT_CTRL, 0x34);
 6     io_out8(PIT_CNT0, 0x9c);
 7     io_out8(PIT_CNT0, 0x2e);
 8     timerctl.count = 0;
 9     for (i = 0; i < MAX_TIMER; i++) {
10         timerctl.timers0[i].flags = 0; /* 沒有使用 */
11     }
12     t = timer_alloc(); /* 取得一個 */
13     t->timeout = 0xffffffff;
14     t->flags = TIMER_FLAGS_USING;
15     t->next = 0; /* 末尾 */
16     timerctl.t0 = t; /* 因為現在只有哨兵,所以他就在最前面 */
17     timerctl.next = 0xffffffff; /* 因為只有哨兵,所以下一個超時時刻就是哨兵的時刻 */
18     return;
19 }

 

 1 void timer_settime(struct TIMER *timer, unsigned int timeout)
 2 {
 3     int e;
 4     struct TIMER *t, *s;
 5     timer->timeout = timeout + timerctl.count;
 6     timer->flags = TIMER_FLAGS_USING;
 7     e = io_load_eflags();
 8     io_cli();
 9     t = timerctl.t0;
10     if (timer->timeout <= t->timeout) {
11         /* 插入最前面的情況 */
12         timerctl.t0 = timer;
13         timer->next = t; /* 下面是設定t */
14         timerctl.next = timer->timeout;
15         io_store_eflags(e);
16         return;
17     }
18     /* 搜尋插入位置 */
19     for (;;) {
20         s = t;
21         t = t->next;
22         if (timer->timeout <= t->timeout) {
23             /* 插入s和t之間的情況 */
24             s->next = timer; /* s下一個是timer */
25             timer->next = t; /* timer的下一個是t */
26             io_store_eflags(e);
27             return;
28         }
29     }
30 }

 

 1 void inthandler20(int *esp)
 2 {
 3     struct TIMER *timer;
 4     io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00接收信號結束的信息通知給PIC */
 5     timerctl.count++;
 6     if (timerctl.next > timerctl.count) {
 7         return;
 8     }
 9     timer = timerctl.t0; /* 首先把最前面的地址賦給timer */
10     for (;;) {
11         /* 因為timers的定時器都處於運行狀態,所以不確認flags */
12         if (timer->timeout > timerctl.count) {
13             break;
14         }
15         /* 超時 */
16         timer->flags = TIMER_FLAGS_ALLOC;
17         fifo32_put(timer->fifo, timer->data);
18         timer = timer->next; /* 將下一個定時器的地址代入timer */
19     }
20     /* 新移位 */
21     timerctl.t0 = timer;
22     /* timerctl.next的設定 *//* 這里 */
23     timerctl.next = timer->timeout;
24     return;
25 }

曾經引入的using變量現在又被去掉了。

總結

定時器是如此重要,以至於我一時想不出它有多重要。定時器使用起來並不復雜,只不過為了盡可能優化提高效率,原作者講了很多鏈表之類的數據結構和算法的東西。

到現在,終於看完了《30天自制操作系統》的三分之一。收獲么,可以說是堅定了我之前對軟件工程的理念,也可以說是加強了自我封閉和頑固的理由。數字電路構成了硬件,但從軟件工程師的角度看,硬件也是一種軟件,它為上層軟件(操作系統)提供了API。操作系統則為應用程序提供了API。如果應用程序做成插件式的,那這個應用程序也可以被稱為一個"操作系統",或者叫做"平台"(例如chrome OS、Visual Studio、Eclipse)。這就像計算機網絡體系結構一樣,分為多個層,每個下層都為上層提供API,上層不必知道下層的實現原理,直接使用就行了。

 

很快就要進入"多任務"的設計實現了!

請查看下一篇《《30天自制操作系統》筆記(11)——高分辨率》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM