操作系統實踐之中斷程序分析

%macro hwint_master 1
        call save
        in al, INT_M_CTLMASK
        or al, (1 << %1)
        out INT_M_CTLMASK, al
        mov al, 20h
        out 20h, al
        sti
        push %1
        call [irq_table+4*%1] ;調用真正的處理函數
        add esp, 4
        cli
        in al, INT_M_CTLMASK
        and al, ~(1 << %1)
        out INT_M_CTLMASK, al
        ret
%endmacro


save:
        pushad
        push ds
        push es
        push fs
        push gs
        mov dxss
        mov dsdx
        mov esdx
        mov esiesp
        inc dword [k_reenter]
        cmp dword [k_reenter], 0
        jne .1
        mov esp, StackTop
        push restart
        jmp [esi + RETADR - P_STACKBASE]    
.1:
        push restart_reenter
        jmp [esi + RETADR - P_STACKBASE]

restart:
 mov esp, [p_proc_ready]
 lldt [esp + P_LDT_SEL] 
 lea eax, [esp + P_STACKTOP]
 mov dword [tss + TSS3_S_SP0], eax


restart_reenter:
 dec dword [k_reenter]
 pop gs
 pop fs
 pop es
 pop ds
 popad
 add esp, 4
 iretd

 

//時間中斷的真正處理函數

PUBLIC void clock_handler(int irq)
{
    //disp_str("#");
    ticks++;
    p_proc_ready->ticks--;
    if(k_reenter!=0)
    {
     //disp_str("!");
     return;
    }
    if (p_proc_ready->ticks > 0) {
 return;
   }
    schedule();
}

分析:Tinix操作系統目前的運行狀態分爲內核態(ring0)和任務態(ring1),ring1是進程代碼,然後這些進程的內核對象即進程表是在ring0下訪問的,進程表就是一個結構體數組,每個結構體保存了進程上次運行時各個寄存器的狀態,包括gs,fs,es,ds,edi,esi,ebp,ebx,edx,ecx,eax,eip,cs,eflags,esp,ss,這些寄存器在tinix_main(運行在ring0下)中進行了初始化,其中eip指向進程的入口函數,即TestA,TestB,TestC,esp指向各自不同的棧空間,並且p_proc_ready = proc_table,p_proc_ready 即下一個將要運行的進程的內核對象,tinix_main最後會調用restart,restart會將下一個將要運行的進程的內核對象中的regs地址賦給tss的sp0,這樣進行運行發生中斷時,堆棧要切換到ring0,棧指針sp從tss的sp0讀入,這樣中斷處理程序就可以剛好在內核對象中保存上述寄存器了(嚴格來說eip,cs,eflags,esp,ss是CPU自動壓棧的),restart最後執行iretd指令就恢復進程的運行了。

   下面看一下中斷處理的流程,進入中斷處理程序後,中斷屏蔽位是開着的,這是不會發生硬件中斷,進入save後通過pushad、push es,push fs,push gs把之前的進入中斷之間的寄存器狀態保存起來,不過不是中斷重入的話保存的位置就是進程內核對象,保存完以後需要將sp指向內核堆棧,以免以後的堆棧操作破壞進程結構;如果是中斷重入,保存的位置就是內核棧的某個位置(中斷重入的情況不會發生堆棧切換),從call save返回後,中斷處理程序首先關閉掉了同類的中斷,也就是說在時鐘處理過程中是不會發生時鐘中斷的中斷重入的,同理鍵盤中斷的處理過程中葉不會發生鍵盤中斷重入。然後激活EOI,開中斷,這時候別的中斷可以發生了。接下來調用  call [irq_table+4*%1]來進行實際的中斷處理,接下來要關閉中斷,通過操作EOI重新打開同類中斷,ret指令後,如果是中斷重入,程序執行到restart_reenter,直接重中斷返回;如果不是中斷重入,執行到restart,此時需要將堆棧切換到進程內核對象處,因爲進入中斷前的寄存器狀態保存在內核對象處。通過iretd指令從中斷返回後中斷又重新打開。

  上述流程保證了中斷處理函數在執行時同類的中斷不會發生,我們來看看時鐘中斷處理函數clock_handler,只有不是中斷重入的時候纔會切換進程,否則什麼都不做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章