天天看點

中斷和動态時鐘顯示

中斷

  中斷就是打斷處理器目前的執行流程,去執行另外一些和目前工作不相幹的指令,執行完之後,還可以傳回到原來的程式流程繼續執行。

外部硬體中斷

  顧名思義,外部硬體中斷,就是從處理器外面來的中斷信号。當外部裝置發生錯誤,或者有資料要傳送(比如,從網絡中接收到一個針對目前主機的資料包),或者處理器交給它的事情處理完了(比如,列印已經完成),它們都會拍一下處理器的肩膀,告訴它應當先把手頭上的事情放一放,來臨時處理一下。

  外部硬體中斷是通過兩個信号線引入處理器内部的。這兩根線的名字叫NMI和INTR.

1. 非屏蔽中斷

  中斷不會被阻斷和屏蔽的,稱為非屏蔽中斷(Non Maskable Interrupt,NMI),産生中斷的裝置,稱為中斷源。在傳統的相容模式下,NMI的中斷源通過一個與非門連接配接到處理器。處理器的NMI引腳是高電平有效的,而中斷信号是低電平有效的。當不存在中斷信号的時候,與非門的所有輸入都是為高,因為處理器的NMI引腳為低電平,這意味着沒有中斷發生。

  當有任何一個非屏蔽中斷産生時,與非門的輸出都為高,Intel處理器規定,NMI中斷信号由0跳變到1後,至少要維持4個以上的時鐘周期才算有效,才能被識别。在實模式下,NMI被賦予了統一的中斷号2,不會再細分。

2. 可屏蔽中斷

  可屏蔽中斷是通過 INTR 引腳進入處理器内部的,像 NMI 一樣,不可能為每一個中斷源都提供一個引腳。而且,處理器每次隻能處理一個中斷。在這種情況下,需要一個代理,來接受外部裝置發出的中斷信号。還有,多個裝置同時發出中斷請求的機率也是很高的,是以該代理的任務還包括對它們進行仲裁,以決定讓它們中的哪一個優先向處理器提出服務請求。其中用的最多的就是中斷代理就是8259晶片又叫可程式設計中斷控制器(Programming Interrupt Controller,PIC)。

  Intel處理器允許256個中斷,中斷信号的範圍是0~255,8259負責提供其中的15個,但中斷号并不固定。該中斷控制晶片有自己的端口号,可以像通路其他外部裝置一樣用in和out指令來改變它的狀态,包括引腳中的中斷号。

中斷和動态時鐘顯示

  8259晶片是 級聯(Cascade) 關系,每片中隻有8個中斷輸入引腳,主片的代理輸出INT直接送到處理器的INTR引腳,從片的INT輸出送到主片的引腳2上。

  在 8259 晶片内部,有中斷屏蔽寄存器(Interrupt Mask Register,IMR),這是個 8 位寄存器,對應着該晶片的 8 個中斷輸入引腳,對應的位是 0 還是 1,決定了從該引腳來的中斷信号是否能夠通過 8259 送往處理器(0 表示允許,1 表示阻斷)。當外部裝置通過某個引腳送來一個中斷請求信号時,如果它沒有被 IMR 阻斷,那麼,它可以被送往處理器。注意,8259晶片是可程式設計的,主片的端口号是 0x20 和 0x21,從片的端口号是 0xa0 和 0xa1,可以通過這些端口通路 8259 晶片,設定它的工作方式,包括 IMR 的内容。

  中斷能否被處理要看處理器,處理器内部有一個标志位IF,這就是中斷标志(InterruptFlag)。當 IF 為 0 時,所有從處理器 INTR 引腳來的中斷信号都被忽略掉;當其為 1 時,處理器可以接受和響應中斷。IF 标志位可以通過兩條指令 cli 和 sti 來改變。這兩條指令都沒有操作數,cli(CLear Interrupt flag) 用于清除 IF 标志位,sti(SeT Interrupt flag) 用于置位 IF 标志。

  當中斷發生頻發時,8259 晶片會記住它們,并按一定的政策決定先為誰服務。總體上來說,中斷的優先級和引腳是相關的,主片的 IR0 引腳優先級最高,IR7引腳最低,從片也是如此。當然,還要考慮到從片是級聯在主片的 IR2 引腳上。

  最後,當一個中斷事件正在處理時,如果來了一個優先級更高的中斷事件時,允許暫時中止目前的中斷處理,先為優先級較高的中斷事件服務,這稱為中斷嵌套。

實模式下的中斷向量表

中斷和動态時鐘顯示

  在實模式下,處理器要求将它們的入口點集中存放到記憶體中從實體位址0x00000開始,到0x003ff結束,共1KB的空間内,這就是所謂的中斷向量表(Interrupt Vector Table,IVT)。

  • 保護斷點的現場。首先要将标志寄存器 FLAGS 壓棧,然後清除它的 IF 位和 TF 位。接着,再将目前的代碼段寄存器 CS 和指令指針寄存器 IP 壓棧。注意,由于 IF 标志被清除,在中斷處理過程中,處理器将不再響應硬體中斷。如果希望更高優先級的中斷嵌套,可以在編寫中斷處理程式時,适時用 sti 指令開放中斷。
  • 執行中斷處理程式。由于處理器已經拿到了中斷号,它将該号碼乘以 4(畢竟每個中斷在中斷向量表中占 4 位元組),就得到了該中斷入口點在中斷向量表中的偏移位址。接着,從表中依次取出中斷程式的偏移位址和段位址,并分别傳送到 IP 和 CS。
  • 傳回到斷點接着執行。所有中斷處理程式的最後一條指令必須是中斷傳回指令 iret。這将導緻處理器依次從堆棧中彈出(恢複)IP、CS 和 FLAGS 的原始内容,于是轉到主程式接着執行。

  和可屏蔽中斷不同,NMI 發生時,處理器不會從外部獲得中斷号,它自動生成中斷号碼 2,其他處理過程和可屏蔽中斷相同。

實時時鐘,CMOS RAM 和 BDC編碼

  在外圍裝置控制晶片ICH内部,內建了實時時鐘電路(Real Time Clock,RTC)和兩小塊由互補金屬氧化物(CMOS)材料組成的靜态存儲器(CMOS RAM)。實時時鐘電路負責計時,而日期和時間的數值則存儲在這塊存儲器中。實時時鐘是全天候跳動的,即使是在你關閉了計算機的電源之後,原因在于它由主機闆上的一個小電池提供能量。它為整台計算機提供一個基準時間,為所有需要時間的軟體和硬體服務。

  日期和時間資訊是儲存在 CMOS RAM 中的,通常有 128 位元組,而日期和時間資訊隻占了一小部分容量,其他空間則用于儲存整機的配置資訊,比如各種硬體的類型和工作參數、開機密碼和輔助儲存設備的啟動順序等。這些參數的修改通常在 BIOS SETUP 開機程式中進行。

  RTC 晶片由一個振蕩頻率為 32.768kHz 的石英晶體振蕩器(晶振)驅動,經分頻後,用于對CMOS RAM 進行每秒一次的時間重新整理。正常的日期和時間資訊占據了 CMOS RAM 開始部分的 10 位元組,有年、月、日和時、分、秒,報警的時、分、秒用于産生到時間報警中斷,如果它們的内容為 0xC0~0xFF,則表示不使用報警功能。

中斷和動态時鐘顯示

  CMOS RAM的通路,需要通過兩個端口來進行。0x70或者0x74是索引端口,用來指定CMOS RAM 内的單元。0x71或者0x75是資料端口,用來讀寫響應單元裡的内容。舉個例子,以下代碼用于讀取今天是星期幾:

mov al,0x06
out 0x70,al
in al,0x71
           

  端口0x70的最高位(bit 7)是控制NMI中斷的開關。當它為0時,允許NMI中斷到達處理器,為1時,則阻斷所有的NMI信号,其他7個bit,即0~6位,則實際上用于指定CMOS RAM單元的索引号。

  單元0x0a~0x0d不是普通的儲存單元,而是4個索引寄存器(8位寄存器)的索引号,也是通過0x70和0x71通路的,這4個寄存器用于設定實時時鐘電路的參數和工作狀态。

  寄存器 A 和 B 用于對 RTC 的功能進行整體性的設定,它們都是 8 位的寄存器,可讀可寫,其各位的用途如表:

中斷和動态時鐘顯示
中斷和動态時鐘顯示
中斷和動态時鐘顯示
中斷和動态時鐘顯示

  寄存器 C 和 D 是标志寄存器,這些标志反映了 RTC 的工作狀态,寄存器 C 是隻讀的,寄存器D 則可讀可寫,它們也都是 8 位寄存器,其各位的含義如表所示。特别是寄存器 C,因為 RTC 可以産生中斷,當中斷産生時,可以通過該寄存器來識别中斷的原因。

中斷和動态時鐘顯示

内部中斷内部

  内部中斷發生在處理器,是由執行的指令引起的。内部中斷不受标志寄存器IF位的影響,也不需要中斷識别總線周期,它們的中斷類型是固定的,可以立即轉入相應的處理過程。

軟中斷

  軟中斷是由int指令引起的中斷處理。這類中斷也不需要中斷識别總線周期,中斷号在指令中給出。

  最有名的軟中斷是BIOS中斷,之是以稱為BIOS中斷,是因為這些中斷功能是在計算機加電之後,BIOS程式執行期間建立起來的,這些中斷功能在加載和執行主引導扇區代碼之前就可以使用了。

中斷和動态時鐘顯示
中斷和動态時鐘顯示
中斷和動态時鐘顯示
中斷和動态時鐘顯示

  BIOS 可能會為一些簡單的外圍裝置提供初始化代碼和功能調用代碼,并填寫中斷向量表,但也有一些 BIOS 中斷是由外部裝置接口自己建立的。

  首先,每個外部裝置接口,包括各種闆卡,如網卡、顯示卡、鍵盤接口電路、硬體控制器等,都有自己的隻讀存儲器(Read Only Memory,ROM),類似于 BIOS 晶片,這些 ROM 中提供了它自己的功能調用例程,以及本裝置的初始化代碼。按照規範,前兩個單元的内容是 0x55 和 0xAA,第三個單元是本 ROM 中以 512 位元組為機關的代碼長度;從第四個單元開始,就是實際的 ROM 代碼。

  在計算機啟動期間,BIOS 程式會以 2KB 為機關搜尋記憶體位址 C0000~E0000 之間的區域。當它發現某個區域的頭兩個位元組是 0x55 和 0xAA 時,那意味着該區域有 ROM 代碼存在,是有效的。接着,它對該區域做累加和檢查,看結果是否和第三個單元相符。如果相符,就從第四個單元進入。這時,處理器執行的是硬體自帶的程式指令,這些指令初始化外部裝置的相關寄存器和工作狀态,最後,填寫相關的中斷向量表,使它們指向自帶的中斷處理過程。

代碼清單

;代碼清單9-1
         ;檔案名:c09_1.asm
         ;檔案說明:使用者程式     

;===============================================================================

SECTION header vstart=0                     ;定義使用者程式頭部段 

    program_length  dd program_end          ;程式總長度[0x00]

    ;使用者程式入口點

    code_entry      dw start                ;偏移位址[0x04]

                    dd section.code.start   ;段位址[0x06]   

    realloc_tbl_len dw (header_end-realloc_begin)/4

                                            ;段重定位表項個數[0x0a]    

    realloc_begin:

    ;段重定位表           

    code_segment    dd section.code.start   ;[0x0c]

    data_segment    dd section.data.start   ;[0x14]

    stack_segment   dd section.stack.start  ;[0x1c]    

header_end:                

;===============================================================================

SECTION code align=16 vstart=0           ;定義代碼段(16位元組對齊) 

new_int_0x70:

      push ax

      push bx

      push cx

      push dx

      push es
      

  .w0:                                    

      mov al,0x0a                        ;阻斷NMI。當然,通常是不必要的

      or al,0x80                          

      out 0x70,al

      in al,0x71                         ;讀寄存器A

      test al,0x80                       ;測試第7位UIP 

      jnz .w0                            ;以上代碼對于更新周期結束中斷來說 

                                         ;是不必要的 

      xor al,al

      or al,0x80

      out 0x70,al

      in al,0x71                         ;讀RTC目前時間(秒)

      push ax

      mov al,2

      or al,0x80

      out 0x70,al

      in al,0x71                         ;讀RTC目前時間(分)

      push ax

      mov al,4

      or al,0x80

      out 0x70,al

      in al,0x71                         ;讀RTC目前時間(時)

      push ax

      mov al,0x0c                        ;寄存器C的索引。且開放NMI 

      out 0x70,al

      in al,0x71                         ;讀一下RTC的寄存器C,否則隻發生一次中斷
                                         ;此處不考慮鬧鐘和周期性中斷的情況 

      mov ax,0xb800

      mov es,ax

      pop ax

      call bcd_to_ascii

      mov bx,12*160 + 36*2               ;從螢幕上的12行36列開始顯示

      mov [es:bx],ah

      mov [es:bx+2],al                   ;顯示兩位小時數字

      mov al,':'

      mov [es:bx+4],al                   ;顯示分隔符':'

      not byte [es:bx+5]                 ;反轉顯示屬性 

      pop ax

      call bcd_to_ascii

      mov [es:bx+6],ah

      mov [es:bx+8],al                   ;顯示兩位分鐘數字

      mov al,':'

      mov [es:bx+10],al                  ;顯示分隔符':'

      not byte [es:bx+11]                ;反轉顯示屬性

      pop ax

      call bcd_to_ascii

      mov [es:bx+12],ah

      mov [es:bx+14],al                  ;顯示兩位小時數字

      
      mov al,0x20                        ;中斷結束指令EOI 

      out 0xa0,al                        ;向從片發送 

      out 0x20,al                        ;向主片發送 



      pop es

      pop dx

      pop cx

      pop bx

      pop ax

      iret

;-------------------------------------------------------------------------------

bcd_to_ascii:                            ;BCD碼轉ASCII
                                         ;輸入:AL=bcd碼
                                         ;輸出:AX=ascii
      mov ah,al                          ;分拆成兩個數字 

      and al,0x0f                        ;僅保留低4位 

      add al,0x30                        ;轉換成ASCII 


      shr ah,4                           ;邏輯右移4位 

      and ah,0x0f                        

      add ah,0x30

      ret

;-------------------------------------------------------------------------------

start:

      mov ax,[stack_segment]

      mov ss,ax

      mov sp,ss_pointer

      mov ax,[data_segment]

      mov ds,ax

     
      mov bx,init_msg                    ;顯示初始資訊 

      call put_string


      mov bx,inst_msg                    ;顯示安裝資訊 

      call put_string

     
      mov al,0x70

      mov bl,4

      mul bl                             ;計算0x70号中斷在IVT中的偏移

      mov bx,ax                          
      
      cli                                ;防止改動期間發生新的0x70号中斷

      push es

      mov ax,0x0000

      mov es,ax

      mov word [es:bx],new_int_0x70      ;偏移位址。                                          

      mov word [es:bx+2],cs              ;段位址

      pop es

      mov al,0x0b                        ;RTC寄存器B

      or al,0x80                         ;阻斷NMI 

      out 0x70,al

      mov al,0x12                        ;設定寄存器B,禁止周期性中斷,開放更 

      out 0x71,al                        ;新結束後中斷,BCD碼,24小時制 

      mov al,0x0c

      out 0x70,al

      in al,0x71                         ;讀RTC寄存器C,複位未決的中斷狀态

      in al,0xa1                         ;讀8259從片的IMR寄存器 

      and al,0xfe                        ;清除bit 0(此位連接配接RTC)

      out 0xa1,al                        ;寫回此寄存器 

      sti                                ;重新開放中斷 

      mov bx,done_msg                    ;顯示安裝完成資訊 

      call put_string


      mov bx,tips_msg                    ;顯示提示資訊

      call put_string      

      mov cx,0xb800

      mov ds,cx

      mov byte [12*160 + 33*2],'@'       ;螢幕第12行,35列
      
 .idle:

      hlt                                ;使CPU進入低功耗狀态,直到用中斷喚醒

      not byte [12*160 + 33*2+1]         ;反轉顯示屬性 

      jmp .idle

;-------------------------------------------------------------------------------

put_string:                              ;顯示串(0結尾)。
                                         ;輸入:DS:BX=串位址

         mov cl,[bx]

         or cl,cl                        ;cl=0 ?

         jz .exit                        ;是的,傳回主程式 

         call put_char

         inc bx                          ;下一個字元 

         jmp put_string

   .exit:

         ret

;-------------------------------------------------------------------------------

put_char:                                ;顯示一個字元

                                         ;輸入:cl=字元ascii
         push ax

         push bx

         push cx

         push dx

         push ds

         push es

         ;以下取目前光标位置

         mov dx,0x3d4

         mov al,0x0e

         out dx,al

         mov dx,0x3d5

         in al,dx                        ;高8位 

         mov ah,al


         mov dx,0x3d4

         mov al,0x0f

         out dx,al

         mov dx,0x3d5

         in al,dx                        ;低8位 

         mov bx,ax                       ;BX=代表光标位置的16位數
         

         cmp cl,0x0d                     ;回車符?

         jnz .put_0a                     ;不是。看看是不是換行等字元 

         mov ax,bx                       ; 

         mov bl,80                       

         div bl

         mul bl

         mov bx,ax

         jmp .set_cursor

 .put_0a:

         cmp cl,0x0a                     ;換行符?

         jnz .put_other                  ;不是,那就正常顯示字元 

         add bx,80

         jmp .roll_screen

 .put_other:                             ;正常顯示字元

         mov ax,0xb800

         mov es,ax

         shl bx,1

         mov [es:bx],cl


         ;以下将光标位置推進一個字元

         shr bx,1

         add bx,1

 .roll_screen:

         cmp bx,2000                     ;光标超出螢幕?滾屏

         jl .set_cursor


         mov ax,0xb800

         mov ds,ax

         mov es,ax

         cld

         mov si,0xa0

         mov di,0x00

         mov cx,1920

         rep movsw

         mov bx,3840                     ;清除螢幕最底一行

         mov cx,80
 .cls:

         mov word[es:bx],0x0720

         add bx,2

         loop .cls

         mov bx,1920

 .set_cursor:

         mov dx,0x3d4

         mov al,0x0e

         out dx,al

         mov dx,0x3d5

         mov al,bh

         out dx,al

         mov dx,0x3d4

         mov al,0x0f

         out dx,al

         mov dx,0x3d5

         mov al,bl

         out dx,al
         

         pop es

         pop ds

         pop dx

         pop cx

         pop bx

         pop ax

         ret

;===============================================================================

SECTION data align=16 vstart=0

    init_msg       db 'Starting...',0x0d,0x0a,0                   

    inst_msg       db 'Installing a new interrupt 70H...',0    

    done_msg       db 'Done.',0x0d,0x0a,0

    tips_msg       db 'Clock is now working.',0
       
;===============================================================================

SECTION stack align=16 vstart=0
                 resb 256
ss_pointer:
;===============================================================================

SECTION program_trail

program_end:
           
;代碼清單9-2

         ;檔案名:c09_2.asm

         ;檔案說明:用于示範BIOS中斷的使用者程式      

;===============================================================================

SECTION header vstart=0                     ;定義使用者程式頭部段 

    program_length  dd program_end          ;程式總長度[0x00]    

    ;使用者程式入口點

    code_entry      dw start                ;偏移位址[0x04]

                    dd section.code.start   ;段位址[0x06] 

    realloc_tbl_len dw (header_end-realloc_begin)/4

                                            ;段重定位表項個數[0x0a]

    realloc_begin:

    ;段重定位表           

    code_segment    dd section.code.start   ;[0x0c]

    data_segment    dd section.data.start   ;[0x14]

    stack_segment   dd section.stack.start  ;[0x1c]    

header_end:                
   
;===============================================================================

SECTION code align=16 vstart=0           ;定義代碼段(16位元組對齊) 

start:

      mov ax,[stack_segment]

      mov ss,ax

      mov sp,ss_pointer

      mov ax,[data_segment]

      mov ds,ax

      mov cx,msg_end-message

      mov bx,message
     
 .putc:

      mov ah,0x0e

      mov al,[bx]

      int 0x10

      inc bx

      loop .putc

 .reps:

      mov ah,0x00

      int 0x16
      mov ah,0x0e

      mov bl,0x07

      int 0x10

      jmp .reps

;===============================================================================

SECTION data align=16 vstart=0

    message       db 'Hello, friend!',0x0d,0x0a

                  db 'This simple procedure used to demonstrate '

                  db 'the BIOS interrupt.',0x0d,0x0a

                  db 'Please press the keys on the keyboard ->'

    msg_end:
                 
;===============================================================================

SECTION stack align=16 vstart=0           

                 resb 256

ss_pointer:

;===============================================================================

SECTION program_trail

program_end:
           

繼續閱讀