歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux中斷詳解

這裡以linux-kernel 0.11版本為基礎整理中斷相關知識,目的在於對於中斷有一個全面、清晰和簡潔的認識

1、Linux的中斷類型

Linux的各種中斷都是由系統負責統一處理的。在響應一個特定的中斷的時候,內核會執行一個函數,該函數叫做中斷處理函數或中斷服務例程。CPU執行完一條指令後,下一條指令的邏輯地址會被放在相應的寄存器中(CS和EIP),在執行新指令之前,系統會檢查是否有中斷產生(有相應的寄存器來表示狀態),如果有,就對中斷進行處理。Linux處理中斷,大致可分為如下的幾步:

(1)、保存正在執行的進程的上下文,便於中斷處理返回後能恢復進程的執行

(2)、對中斷進行解析,確定產生中斷的中斷源,識別中斷的類型。但系統接受中斷時,能夠從物理硬件中獲得一個關於中斷的數

,用這個數去做查表操作,能獲得關於這個中斷類型、中斷處理程序等相關信息

(3)、內核調用由第二步獲得的中斷處理程序,對中斷進行處理

(4)、中斷處理程序執行完對中斷的處理後返回。恢復之前被中斷的進程的上下文,或者根據需要調度優先級更高的進程去執行

中斷信號可分為兩類:硬件中斷和軟件中斷,軟件中斷一般被稱為異常。Intel x86公有256個中斷,每個中斷都有一個0~255之間的數來表示,Intel將前32個中斷號(0~31)已經固定設定好或者保留未用。中斷號32~255分配給操作系統和應用程序使用。在Linux中,中斷號32~47對應於一個硬件芯片的16個中斷請求信號,這16個中斷包括時鐘、鍵盤、軟盤、數學協處理器、硬盤等硬件的中斷。系統調用設為中斷號128,即0x80。

2、中斷描述表

獲取中斷號(或稱為中斷向量)的過程涉及到太多的硬件過程,這裡不做介紹。系統獲得中斷向量之後,要獲得相應的處理程序,需要去做一個查表的操作,這裡所查詢的這個表,就是中斷描述表。

中斷描述表是存儲系統中斷向量對應中斷處理程序的表。Intel x86中共有256個中斷,因此中斷描述表中也有256項,每一項對應一個中斷。系統以中斷向量作為索引從表中取出相應的中斷服務例程。

[中斷描述表在內存中斷的位置]

中斷描述表可以在內存中的任意位置,它的具體地址保存在IDTR寄存器中。

[中斷描述表的初始化]

在Linux內核版本0.11中,中斷描述表的初始化分為兩個部分。第一部分是在內核引導啟動部分,在head.s中實現。這裡的效果是將中斷描述符表(用IDT表示),設置成具有256項,並且都指向ignore_int的中斷門。然後將IDT的地址加載到IDTR寄存器中。因此,這裡的初始化只是把所有中斷的服務例程都設置成了一個啞的中斷服務程序,即不做任何中斷的處理。第一部分的初始化代碼(AT&T的匯編語言格式)很短,這裡摘抄如下。setup_idt子程序在head.s的第24行被調用

setup_idt:

 

lea ignore_idt, %edx #獲取ignore_ldt的有效地址,並且把結果保存在edx寄存器中

 

movl $0x0080000, %eax #eax寄存器中的高16位變成0x0008。用來做段選擇符

 

movw %dx,%ax #ax寄存器中值現在變成ignore_idt的有效地址的低16位,ax是eax寄存器的低半部分 。那麼現在eax寄存器的高

 

#16位的值是段選擇符0x0008,低16位是ignore_idt的偏移地址。簡單理解可以用來找到ignore_idt程序即可

 

movw $0x8E00, %dx #設置中斷描述符表中斷門的數據

 

lea _idt, %edi #_idt是中斷描述符表的地址,將中斷描述符表的地址存放在edi寄存器中

 

mov $256,%ecx #循環使用

 

rp_sidt:

 

movl %eax , (%edi) #edi指向的是中斷描述符表,而eax中保存的是可以用來找到中斷服務例程ignore_idt的地址,所以這裡的效果是把

 

#用於找到ignore_idt的數據存入edi寄存器中地址指向的內存中

 

movl %edx, 4(%edi) #中斷描述符表的每一項都是64位(中斷描述符表中每一項的數據結構後續會將)

 

addl $8, %edi #指向下一項

 

dec %ecx #循環計數器減一

 

jne rp_sidt #jne的意思是如果不相等就跳到rp_sidt子程序去。jne使用的是ZF標志位。ZF=0就執行下一條指令,

 

#ZF=1就跳轉。而dec %ecx會影響這個標志位。只有當ecx寄存器中的值減為0的時候,才會使得ZF=0

 

# 所以,這裡實現的效果就是會使得這段程序執行256次。將中斷描述表中的每一項都填入相同的內容

 

lidt idt_descr #使用lidt指令將中斷描述表的地址加載到IDTR寄存器中

 

ret

 

 

 

經過第一部分的操作,中斷描述表已經得到初始化。但其中的數據,是沒有意義的,無法服務任何中斷。為了使中斷描述表中的數據真正有意義,還需要往中斷描述表中對應項插入合適的數據。這第二部分的過程是在各個驅動初始化的時候完成的。這裡以時鐘中斷為例,來解析數據插入過程。在這之前可以先簡要介紹下其它中斷的初始化,0~16號中斷的初始化在kernel/traps.c的trap_init函數中實現(181行)。trap_init函數的調用則是在main.c的main函數中調用的。

 

對於時鐘中斷來說,它往IDT中真正寫入中斷處理例程的實現代碼是sched.c文件的406行中。相關的部分代碼如下所示

 

/* 下面的匯編語句是清楚NT標志,NT表示是用來表示是否有程序遞歸調用的標志Nested Task。但NT標志為1時,那麼當前中斷任務執行iret指令時就會引起任務切換。NT標志指出了在TSS中的back_link字段是否有效 */

 

__asm__( "pushfl ; andl $0xffffbfff,(%esp) ; popfl" );

 

ltr(0); //將任務0的TSS加載到任務寄存器tr中

 

lldt(0); //加載局部描述符表到局部描述符表寄存器

 

//下面是設置8253定時器

 

outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */

 

outb_p(LATCH & 0xff , 0x40); /* LSB */

 

outb(LATCH >> 8 , 0x40); /* MSB */

 

set_intr_gate(0x20,&timer_interrupt); //設置時鐘中斷的中斷處理程序

 

outb(inb_p(0x21)&~0x01,0x21);

 

set_system_gate(0x80,&system_call);

 

這段代碼是在sched.c的sched_init函數中,sched_init函數的調用是在main.c的main函數中調用的。到這裡,就完整地實現了在中斷描述表中設置時鐘中斷的工作 [中斷描述表的數據格式] 從上面對時鐘中斷的設置中來看,使用的是set_intr_gate來進行設定的,這是一個宏。類似的,還有兩個宏來實現對中斷描述表的寫操作,分別是set_trap_gate和set_system_gate。對set_intr_gate宏的定義如下 #define set_intr_gate(n,addr) \ _set_gate(&idt[n],14,0,addr) 使用這個宏,需要傳遞兩個參數,一個是中斷號,一個是中斷處理函數。在set_intr_gate宏中又調用了_set_gate宏。另外的兩個宏,也是調用_set_gate宏來完成工作的。因此從_set_gate宏中可以了解中斷描述表中每一項的數據結構。_set_gate宏的定義如下 gate_addr描述符的地址,type描述中斷類型,dpl描述中斷處理的特權級別,addr中斷處理程序的地址。 #define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \ //將偏移地址低字與選擇符組合成描述符低4直接(eax) "movw %0,%%dx\n\t" \ //將類型標志字與偏移高字組合成描述符高4字節 (edx) "movl %%eax,%1\n\t" \ //設置描述符的低4字節 "movl %%edx,%2" \ //設置描述符的高4字節 : \ : "i" ((short ) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ //每一個描述符占64位,獲取高32位地址,即高4字節地址 "d" ((char *) (addr)),"a" (0x00080000)) 這種嵌入了匯編語句,GUN gcc手冊中描述的嵌入匯編的基本格式為 asm("匯編語句" :輸出寄存器 :輸入寄存器 :會被修改的寄存器); %0,%1……等是使用參數的編號,從輸出寄存器開始,從左往右,從上往下。那麼%0表示的則是有0x8000+(dpl<<13)+(type<<8)的值。我們分析這個值,dpl左移13位,這剛好是下圖中的DPL的部分(左邊32開始算起的左移),type左移8位,這對應的是9,10,和11位的三位類型碼。0x8000表示成二進制就是1000 0000 0000 0000,剛好是把P標志位設置成1。 僅僅從這段匯編上不容易理解具體的數據結構,下面用圖來進行說明  
內核中關於IDT相應的定義如下(head.h) typedefstruct desc_struct { unsignedlong a,b; } desc_table[256]; extern desc_table idt,gdt;
Copyright © Linux教程網 All Rights Reserved