《30天自制操做系統》12_day_學習筆記

harib09a:
  定時器:(Timer)每隔一段時間,會向CPU發送一箇中斷。這樣CPU不用記住每一條指令的執行時間。沒有定時器不少指令CPU都很難執行。例如HLT指令,這個指令的執行時間不是個固定值,沒有定時器,CPU就不能執行這個指令
  -PIT-: 可編程的間隔型定時器(Programmable Interval Timer )經過設定PIT,可讓定時器每隔必定時間就產生一次中斷。PIT和PIC都被集成在別的芯片裏了,鏈接着IRQ的0號中斷,IRQ0的中斷變動週期是經過寄存器AL的值來設定的。編程

  •   IRQ0的中斷頻率  = CPU主屏/AL(中斷週期;設定的數值)
  •   定時器的時間間隔  = 1秒/IRQ0的中斷頻率

  這裏筆者把中斷頻率設定爲100HZ,(1秒鐘產生100次中斷)根據筆者CPU的主頻,計算獲得AL爲0x2e9c(11932)數組

  一、初始化定時器Timer(  三次調用OUT()  )緩存

//定時器初始化函數init_pit
//timer.c節選
#define PIT_CTRL    0x0043
#define PIT_CNT0    0x0040
void init_pit(void)
{      //中斷週期的變動規則(三次調用OUT()):P222有規則介紹
      //至於規則爲何是這樣連續調用三次OUT();筆者介紹說這個是由芯片設定決定的。咱們就不深究;知道這樣的調用規則就行。
    io_out8(PIT_CTRL, 0x34);//第一步:調用OUT(0x43,AL);AL=0x34 此時AL爲定值PIT_CTRL(0x34)
    io_out8(PIT_CNT0, 0x9c);//第二步:調用OTU(0x40,AL);AL=0x9c 此時AL爲中斷週期的低八位(0x9c)
    io_out8(PIT_CNT0, 0x2e);//第三步:調用OTU(0x40,AL);AL=0x2e 此時AL爲中斷週期的高八位 (0x2e)
    return;
}

  二、在HariMain中調用上面的定時器初始化函數函數

//bootpack.c節選
void HariMain(void) {
    //...............
    fifo8_init(&keyfifo, 32, keybuf);
    fifo8_init(&mousefifo, 128, mousebuf);
    init_pit();         //在這裏調用定時器初始化函數
    io_out8(PIC0_IMR, 0xf8); //PIT和PIC1和鍵盤中斷設置1111-1000
    io_out8(PIC1_IMR, 0xef); //PIC1 設置鼠標的中斷設置1110-1111
    //.............
}

  三、編寫IRQ0發生時的中斷處理程序spa

    這裏咱們先來解釋一下io_out8():io_out,表示芯片的端口。例如這裏的io_out8(PIC0_OCW2,0x60);8表示這個端口或者寄存器是8位的,參數的第一個表哪一個芯片的哪個寄存器,固然這個寄存器位數要和前面的8位數同樣。第二個參數表示要設置的值,位數也要對應。例如:調用io_outM(A_B,N) .表示把芯片A中的B寄存器(這個寄存器的位數爲M)的值設置爲N 。io_out8(PIC0_OCW2,0x60)表示的意思就是把芯片PIC0的OCW2寄存器(8位)的值設置爲0x60。操作系統

void inthandler20(int *esp)
{    //把芯片PIC0的OCW2寄存器(8位)的值設置爲0x60
    io_out8(PIC0_OCW2, 0x60);    /* IRQ-00信號接收完了的信息通知給PIC */
    return;
}
//naskfunc.nas
//函數_asm_inthandler20
//20中斷號,這裏把定時器的中斷號設置爲20

   四、把定時器的中斷程序註冊到IDT中指針

void init_gdtidt(void) {
    //...................
    //20號爲定時器的中斷
    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);
}

harib09b:
  上面咱們已經對定時器作了一些準備工做,下面咱們來試一試讓他運行起來,咱們定義了一個struct TIMERCTL結構體,在結構體中定義了一個計數變量count,在PIT初始化的時候,把count初始化爲0,而後在定時器中斷程序中,不斷對count進行自加,所以定時器每發生一次中斷,count都會增長1,這樣用來記錄定時器中斷髮生的次數。最後把count顯示在咱們前面實現的窗口中。code

/* timer.c */
//計數結構體定義
struct TIMERCTL {    unsigned int count;    };
struct TIMERCTL timerctl;
void init_pit(void)
{
    //..初始化count爲 0 .............
    timerctl.count = 0;
    return;
}

void inthandler20(int *esp)
{
    //定時器每次發生中斷會調用這個函數
    //這是計數變量count會 +1 。
    timerctl.count++;
    return;
}
  //在HariMain中把數值顯示出來:HariMain節選
void HariMain(void)
{
  for (;;) {
    //先把值寫到字符串s中
    sprintf(s, "%010d", timerctl.count);
    //初始化窗口圖層的緩存buf_win
    boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
    //把字符串寫到窗口圖層緩存buf_win中
    putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
    //刷新並顯示窗口圖層sht_win
    sheet_refresh(sht_win, 40, 28, 120, 44);
    //.......
    }
}

harib09c:
  超  時:筆者舉了一個很形象的例子來講明什麼是超時:「喂,OS小弟,10秒鐘以後,通知我一聲,我要幹什麼幹什麼」咱們就把這樣的功能叫作超時(定時吧)。下面咱們來實現這個功能。對象

   一、擴展struct TIMERCTL記錄超時有關的信息blog

/* timer.c */
struct TIMERCTL {
    unsigned int count;//定時器中斷計數器
    unsigned int timeout; //記錄離超時還有多長時間
    struct FIFO8 *fifo;//使用FIFO緩衝區來通知
    unsigned char data;
};

   二、修改PIT初始化和中斷處理函數

void init_pit(void)
{    //.........修改pit初始化函數
    //將count和timeout都初始化爲0
    timerctl.count = 0;
    timerctl.timeout = 0;
    return;
}
void inthandler20(int *esp)
{    //.........修改第20號中斷
    if (timerctl.timeout > 0) { /* 若是已經設定了超時 */
        timerctl.timeout--;//沒發生一次中斷,記錄離超時的時間timeout減1
        if (timerctl.timeout == 0) { //若是記錄的時間已經沒有了,說明已經到了定時的時間
            fifo8_put(timerctl.fifo, timerctl.data);//時間已經到了,經過FIFO緩衝區通知CPU
        }
    }
    return;
}
//定時函數:進行超時設定
void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
    int eflags;
    //先禁止中斷,避免IRQ0中斷沒有結束前進來中斷髮生混亂
    eflags = io_load_eflags();
    io_cli();
    //接着進行超時設置。
    timerctl.timeout = timeout;
    timerctl.fifo = fifo;
    timerctl.data = data;
    //最後恢復中斷狀態
    io_store_eflags(eflags);
    return;
}

   三、在HariMain中調用定時函數settimer()

    //在定時器的中斷函數發生1000次中斷後,向timerFIFO中寫入」1「,而timerFIFO接收到數據,就會砸屏幕上顯示」10[sec]「
    settimer(1000, &timerfifo, 1);

harib09d:
  上一步咱們實現了超時的功能。在操做系統中,超時功能的使用很是方便也很是多。下面咱們來根據超時設定多個定時器
  一、修改結構體struct TIMERCTL

#define MAX_TIMER        500
   //定時器結構體
struct TIMER {
    //timeout:定時器的中斷次數
    //flag:記錄各個定時器的狀態
    unsigned int timeout, flags;
    struct FIFO8 *fifo;
    unsigned char data;
};
struct TIMERCTL {
    unsigned int count;
    struct TIMER timer[MAX_TIMER];
};

  二、修改timer.c中的相關函數

#define TIMER_FLAGS_ALLOC        1    /* 表示定時器已配置 */
#define TIMER_FLAGS_USING        2    /* 定時器運行中 */
void init_pit(void)
{    //....................
    for (i = 0; i < MAX_TIMER; i++) {
      //初始化TIMERCTL結構體將全部定時器標誌位置0,表示未使用
        timerctl.timer[i].flags = 0;
    }//...................
}
struct TIMER *timer_alloc(void)
{    //...
        if (timerctl.timer[i].flags == 0) {
              //定時器已分配,將FLAG由未使用改成已配置
            timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
            return &timerctl.timer[i];
        }//...
}
void timer_free(struct TIMER *timer)
{
    timer->flags = 0;          /* 定時器釋放後,將flag置0 */
}


void timer_settime(struct TIMER *timer, unsigned int timeout)
{    //...
    timer->flags = TIMER_FLAGS_USING;//設置後,flag置運行中
}

void inthandler20(int *esp)
{    //...
        if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
            timerctl.timer[i].timeout--;                    //定時器運行中,每次20號中斷一次,timeout-1
            if (timerctl.timer[i].timeout == 0) {               //timeout沒有了
                timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;        //解除運行狀態,
                fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);//通知CPU想FIFO中寫入data
            }
        }
}

  三、HariMain中設置3s和10s兩個定時器

  if (fifo8_status(&timerfifo) != 0) {     //設置10s定時器
    i = fifo8_get(&timerfifo);         /* 首先得到第一個FIFO緩衝區的地址 */
    io_sti();                  //IDT/PIC開始向CPU發送中斷信號
    //顯示出來
    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) {//設置3s定時器
     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);         //得到第三個FIFO緩衝區的地址
    io_sti();
    if (i != 0) {                 //i!=0 表示FIFO地址獲取成功。
      timer_init(timer3, &timerfifo3, 0);   /* 初始化定時器timer3,flag=0,表示沒使用 */
      boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111);
    } else {
      timer_init(timer3, &timerfifo3, 1);   /* 初始化timer3,flag=1表示該定時器一杯分配 */
      boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111);
    }
    timer_settime(timer3, 50);          //設置定時的時間,50箇中斷一次閃爍
    sheet_refresh(sht_back, 8, 96, 16, 112);
  }

harib09e:
  加快中斷處理(01):咱們知道,中斷在CPU中基本是時刻都在發生的,若是按照上面的中斷速度,CPU基本什麼都作不了。接下來咱們加快定時器中斷處理的速度。在上面程序中,爲了計時,咱們在每一次中斷髮生時,讓定時器的timeout減1。這樣會增長中斷函數執行的時間。修改timeout的含義,表示予定時間,接下來用count和timeout比較就知道是否達到定時的時間了。

void inthandler20(int *esp)
{        //...
        if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
            //當予定時間小於計數count的時間時,就把數據寫到FIFO中。
            //避免了timeout--的操做
            if (timerctl.timer[i].timeout <= timerctl.count) {
                timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
                fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
            }
        }//....
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{    //由於定時器啓動時,不能肯定count的值,因此予定時間設定爲count+定時時間
    timer->timeout = timeout + timerctl.count;
    //此時定時器啓動了,FLAG設置定時器正在使用
    timer->flags = TIMER_FLAGS_USING;
    return;
}
//時刻調整:cout從系統啓動,到0xffff_ffff的時間爲497天。
//每一年多一點的時間啓動一次。須要從新將count的值置爲0

harib09f:
  加快中斷處理(02):咱們發現中斷執行if(i<MAX_TIMER)的次數太多了。並且大多數是沒必要要的。追加一個變量timerctl.next記住下一個時刻的值,這樣不用每次都作無用的判斷。

struct TIMERCTL {            //next記錄下一個時刻的值
    unsigned int count, next;
    struct TIMER timer[MAX_TIMER];
};
                     //把中斷函數修改一下,把使用到了next的地方都修改一下
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) {
                            /* 定時時間已經到了,寫FIFO */
                timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
                fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
            } else {
                            /* 定時時間尚未到,修改NEXT的值日後 */
                if (timerctl.next > timerctl.timer[i].timeout) {
                    timerctl.next = timerctl.timer[i].timeout;
                }
            }
        }
    }
    return;
}
void init_pit(void)
{    //...
    timerctl.next = 0xffffffff;    /* 剛初始化,沒有在運行的定時器 */
    for (i = 0; i < MAX_TIMER; i++) {
        timerctl.timer[i].flags = 0; /* 沒有使用 */
    }//...
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{    //...
    if (timerctl.next > timer->timeout) {
       /* 此時剛啓動定時器,初始化next在timeout的下一個時刻 */
        timerctl.next = timer->timeout;
    }//...
}

harib09g:
  加速中斷處理(03):到達next和沒有到達next時刻的定時器處理的時間差異很大。咱們進一步改進:定義timers[]用來存放按照順序排列的定時器的地址(和以前描繪圖層的處理順序很類似)

    struct TIMERCTL {
    unsigned int count, next, using;//using記錄出處於活動中的定時器數量
    struct TIMER *timers[MAX_TIMER];//定時器指針數組:存放排好序的定時器地址
    struct TIMER timers0[MAX_TIMER];//定時器結構體數組:存放定時器
};

  接下來修改中斷,初始化,定時器初始化,定時器分配函數(每次修改了一點點,要貼這麼多代碼。還要重複寫這麼多註釋!!)

void inthandler20(int *esp)
{
    int i, j;
    io_out8(PIC0_OCW2, 0x60);    /* IRQ-00通知PIC接收結束信息 */
    timerctl.count++;//每一次中斷,計數器count++
    if (timerctl.next > timerctl.count) {
        return;//還不到下一個時刻,結束
    }
    for (i = 0; i < timerctl.using; i++) {
        /* 這裏直接用using。只對使用中的定時器進行定時器地址排序 */
        //timeout>count,沒有超時
        if (timerctl.timers[i]->timeout > timerctl.count) {
            break;
        }
        /* 超時了,先設置標誌位已分配 */
        timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
        //寫data到FIFO中
        fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
    }
    /* 上面循環超時執行後到這裏來了
       這是i保存的是超時寄存器的個數 */
    timerctl.using -= i; //超時了i個定時器,活動中的定時器減小i個
    for (j = 0; j < timerctl.using; j++) {     //對於每個活動中的定時器
        //這是活動中的定時器已經減小了i個,須要對活動中的定時器從新排序,地址從新賦值給timers[]
        timerctl.timers[j] = timerctl.timers[i + j];
    }
    if (timerctl.using > 0) {            //活動中的定時器不爲0
        //將next指向下一個時刻
        timerctl.next = timerctl.timers[0]->timeout;
    } else {
        //不然,沒有活動中的定時器
        timerctl.next = 0xffffffff;
    }
    return;
}

void init_pit(void)
{    //...定時器初始化函數,此時剛剛初始化
    timerctl.next = 0xffffffff;           /* 沒有活動中的定時器 */
    timerctl.using = 0;
    for (i = 0; i < MAX_TIMER; i++) {
        timerctl.timers0[i].flags = 0;       /* 剛初始化,flag都爲0  */
    }
    return;
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{    //...設置定時器,(至關於建立了一個對象的一個實例)
    for (i = 0; i < timerctl.using; i++) {
        //這裏從排序好的定時器的地址開始找找到最後一個定時器地址的位置i
        if (timerctl.timers[i]->timeout >= timer->timeout) {
            break;
        }
    }
    for (j = timerctl.using; j > i; j--) {
        //在地址i的後面,把新的(實例)定時器的地址放到i的後面
        timerctl.timers[j] = timerctl.timers[j - 1];
    }
    timerctl.using++;                //有了新的定時器,活動定時器數量增長一個
    timerctl.timers[i] = timer;          //賦值,定時的時間
    timerctl.next = timerctl.timers[0]->timeout;//新的活動定時器來了,next指向下一個時刻
    io_store_eflags(e);               //恢復中斷。在前面部分是先禁止了全部的中斷的,io_load_eflags()
    return;
}
相關文章
相關標籤/搜索