現在,我們知道了80x86微處理器在硬件級對中斷和異常做了些什麼,接下來,我們可以繼續描述如何初始化中斷描述表。
內核啓用中斷以前,必須把IDT表的初始地址裝到idtr寄存器,並初始化表中的每項。這項工作是在初始化系統時完成的。
int指令允許用戶態進程發出一箇中斷信號,其值可以是0~255的任意一個向量。因此,爲了防止用戶通過int指令模擬非法的中斷和異常,IDT的初始化必須非常小心。這可以通過把中斷或陷阱門描述符的DPL字段設置成0來實現。如果進程試圖發出其中的一箇中斷信號,控制單元將檢查出CPL的值與DPL字段有衝突,並且產生一個”Generalprotection”異常。
然而,在少數情況下,用戶態進程必須能發出一個編程異常。爲此,只要把中斷或陷阱門描述符的DPL字段設置成3,即特權級儘可能一樣高就足夠了。
現在,讓我們來看一下Linux是如何實現這種策略的。
中斷門、陷阱門及系統門
與在前面”中斷描述符表”中所提到的一樣,Intel提供了三種類型的中斷描述符:任務門、中斷門及陷阱門描述符。Linux使用與Intel稍有不同的細目分類和術語,把它們如下進行分類:
中斷門
用戶態的進程不能訪問的一個Intel中斷門。所有的Linux中斷處理程序都通過中斷門激活,並全部限制在內核態。
系統門
用戶態的進程可以訪問的一個Intel陷阱門。通過系統門來激活三個Linux異常處理程序,它們的向量是4,5及128。因此,在用戶態下,可以發佈into、bound及int$0x80三條彙編語言指令。
系統中斷門
能夠被用戶態進程訪問的Intel中斷門。與向量3相關的異常處理程序是由系統中斷門激活的,因此,在用戶態可以使用匯編語言指令int3。
陷阱門
用戶態的進程不能訪問的一個Intel陷阱門。大部分Linux異常處理程序都通過陷阱門來激活。
任務門
不能被用戶態進程訪問的Intel任務門。Linux對”Doublefault”異常的處理程序是由任務門激活的。
下列體系結構相關的函數用來IDT中插入門:
set_intr_gate(n,addr)
在IDT的第n個表項插入一箇中斷門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置成中斷處理程序的地址addr,DPL字段設置成0。
set_system_gate(n,addr)
在IDT的每n個表項插入一個陷阱門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置成中斷處理程序的地址addr,DPL字段設置成0。
set_system_intr_gate(n,addr)
在IDT的第n個表項插入一箇中斷門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置成中斷處理程序的地址addr,DPL字段設置成0。
set_trap_gate(n,addr)
與前一個函數類似,只不過DPL的字段設置成0。
set_task_gate(n,gdt)
在IDT的第n個表項插入一箇中斷門。門中的段選擇符存放一個TSS的全局描述符指針,該TSS中包含要被激活的函數,偏移量設置成0,DPL字段設置成3。
IDT的初步初始化
當計算機還運行在模式時,IDT被初始化並由BIOS例程使用。然而,一旦Linux接管,IDT就被移到RAM的另一個區域,並進行第二次初始化,因爲Linux沒有利用任何BIOS例程。
IDT存放在idt_table表中,有256個表項。6字節的idt_descr變量指定了IDT的大小和它的地址,只有當內核用lidt彙編指令初始化idtr寄存器時纔用到這個變量。
在內核初始化過程中,setup_idt()彙編函數用同一個斷門來填充所有這256個idt_table表項。
用匯編語言寫成的ignore_int()中斷處理程序,可以看作一個空的處理程序,它執行下列動作:
在棧中保存一些寄存器的內容。
調用printk()函數打印”Unknowninterrupt”系統消息。
從棧恢復寄存器的內容。
執行iret指令以恢復被中斷的程序。
ignore_int()處理程序應該從不被執行,在控制檯或日誌文件中出現的”Unknowninterrupt”消息標誌着要麼是出現了一個硬件問題,要麼就是出現了一個內核的問題。
緊接着這個預初始化,內核將在IDT中進行第二遍初始化,用有意義的陷阱和斷處理程序替換這個空處理程序。一旦這個過程完成,對控制單元產生的每個不同的異常,IDT都有一個專門的陷阱或系統門,而對於可編程中斷控制器確認的每一個IRQ,IDT都將包含一個專門的中斷門。
在接下來的兩節中,將分別針對異常和中斷來詳細地說明這個工作是如何完成。