3中斷描述符表

好了,現在,我們知道了80x86微處理器在硬件級對中斷和異常做了些什麼,接下來,我們繼續關注的是如何初始化中斷描述符表。

內核啓用中斷以前,必須把IDT表的初始地址裝到idtr寄存器,並初始化表中的每一項。這項工作是在初始化系統時完成的。

int指令允許用戶態進程發出一箇中斷信號,其值可以是0-255的任意一個向量。因此,爲了防止用戶通過int指令模擬非法的中斷和異常,IDT的初始化必須非常小心。這可以通過把中斷或陷阱門描述符的DPL字段設置成0來實現。如果進程試圖發出其中的一箇中斷信號,控制單元將會發現CPL的值與DPL字段有衝突,並且產生一個"General protection”異常。

然而,在少數情況下,用戶態進程必須能發出一個編程異常。爲此,只要把中斷或陷阱門描述符的DPL字段設置成3,即特權級儘可能一樣高就足夠了。

現在,讓我們來看一下Linux是如何實現這種策略的。

1 中斷門、陷阱門及系統門


我們先回憶一下前一篇博文的“中斷描述符表”,Intel提供了三種類型的中斷描述符:任務門、中斷門及陷阱門描述符。Linux使用與Intel稍有不同的細目分類和術語,把它們如下進行分五類:

中斷門(interrupt gate):用戶態的進程不能訪問Intel中斷門(門的DPL字段爲0)。所有的Linux中斷處理程序都通過中斷門激活,並全部限制在內核態。

系統門(system gate):用戶態的進程可以訪問Intel陷阱門(門的DPL字段爲3)。通過系統門來激活三個Linux異常處理程序,它們的向量是4,5及128,因此,在用戶態下,可以發佈into、bound及int $0x80三條彙編語言指令。

系統中斷門(system interrupt gate):能夠被用戶態進程訪問的Intel中斷門(門的DPL字段爲3)。與向量3相關的異常處理程序是由系統中斷門激活的,因此,在用戶態可以使用彙編語言指令int3。

陷阱門(trap gate):用戶態的進程不能訪問的一個Intel陷阱門(門的DPL字段爲0)。大部分Linux異常處理程序都通過陷阱門來激活。

任務門(task gate):不能被用戶態進程訪問的Intel任務門(門的DPL字段爲0)。Linux對“Double fault”異常的處理程序是由任務門激活的。

下列體系結構相關的函數用來在IDT中插入門:

set_intr_gate(n,addr)

在IDT的第n個表項插入一箇中斷門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置爲中斷處理程序的地址addr,DPL字段設置爲0。

set_system_gate(n,addr)

在IDT的第n個表項插入一個陷阱門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置爲異常處理程序的地址addr,DPL字段設置爲3。

set_system_intr_gate(n,addr)

在IDT的第n個表項插入一箇中斷門。門中的段選擇符設置成內核代碼的段選擇符,偏移量設置爲異常處理程序的地址addr,DPL字段設置爲3。

set_trap_gate(n,addr)

與前一個函數類似,只不過DPL的字段設置成0。

set_task_gate(n,gdt)

在IDT的第n個表項中插入一箇中斷門。門中的段選擇符中存放一個TSS的全局描述符表的指針,該TSS中包含要被激活的函數。偏移量設置爲0,而DPL字段設置爲3。

2 IDT的初步初始化


當計算機還運行在實模式時,IDT就被初始化並由BIOS例程使用。然而,一旦Linux接管,IDT就被移到RAM主存的另一個區域,並進行第二次初始化,因爲Linux沒有利用任何BIOS的例程。

在源代碼中,IDT存放在idt_table表中,有256個表項。6字節的idt_descr變量指定了IDT的大小和它的地址,只有當內核用lidt彙編指令初始化idtr寄存器時纔用到這個變量(回憶一下,idtr寄存器存放的是IDT的基址)。

在內核初始化過程中,setup_idt()彙編語言函數用同一個中斷門(即指向ignore_int()中斷處理程序)來填充所有這256個idt_table表項:
    setup_idt:
        lea ignore_int, %edx
        movl $(_ _KERNEL_CS << 16), %eax
        movw %dx, %ax       /* selector = 0x0010 = cs */
        movw $0x8e00, %dx   /* interrupt gate, dpl=0, present */
        lea idt_table, %edi
        mov $256, %ecx
    rp_sidt:
        movl %eax, (%edi)
        movl %edx, 4(%edi)
        addl $8, %edi
        dec %ecx
        jne rp_sidt
        ret

用彙編語言寫成的ignore_int()中斷處理程序,可以看作一個空的處理程序,它執行下列動作:

1. 在棧中保存一些寄存器的內容。
2. 調用printk()函數打印“Unknown interrupt”系統消息。
3. 從棧恢復寄存器的內容。
4. 執行iret指令以恢復被中斷的程序。

ignore_int()處理程序應該從不被執行。如果在控制檯或日誌文件中出現的“Unknown interrupt”消息,則標誌着要麼是出現了一個硬件的問題(一個I/O設備正在產生沒有預料到的中斷),要麼就是出現了一個內核的問題(一箇中斷或異常未被適當地處理)。

緊接着這個預初始化,內核將在IDT中進行第二遍初始化,用有意義的陷阱和中斷處理程序替換這個空處理程序。一旦這個過程完成,對控制單元產生的每個不同的異常,IDT都有一個專門的陷阱或系統門,而對於可編程中斷控制器確認的每一個IRQ,IDT都將包含一個專門的中斷門。

好了,現在有了對中斷硬件環境的瞭解,以及在得到一個空的IDT的表以後,接下來的博文中,將分別針對異常和中斷來詳細地說明這個工作是如何完成的。隨後將分別爲中斷和異常舉一個實例,一個是進程調度的“心臟”——時鐘中斷,一個是虛擬存儲的核心內容——缺頁異常。敬請期待!


發佈了0 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章