?? 中斷.txt
字號:
if (auto_eoi)
outb_p(0x03, 0x21); /* master does Auto EOI */
else
outb_p(0x01, 0x21); /* master expects normal EOI */
outb_p(0x11, 0xA0); /* ICW1: select 8259A-2 init */
outb_p(0x20 + 8, 0xA1); /* ICW2: 8259A-2 IR0-7 mapped to 0x28-0x2f */
outb_p(0x02, 0xA1); /* 8259A-2 is a slave on master's IR2 */
outb_p(0x01, 0xA1); /* (slave's support for AEOI in flat mode is to be
investigated) */
這樣,在IDT的向量0x20-0x2f可以分別填入相應的中斷處理函數的地址了。
i386中斷門描述符
段選擇符和偏移量決定了中斷處理函數的入口地址
在這里段選擇符指向內核中唯一的一個代碼段描述符的地址__KERNEL_CS(=0x10),而這個描述符定義的段為0到4G:
---------------------------------------------------------------------------------
ENTRY(gdt_table) .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
... ...
---------------------------------------------------------------------------------
而偏移量就成了絕對的偏移量了,在IDT的描述符中被拆成了兩部分,分別放在頭和尾。
P標志著這個代碼段是否在內存中,本來是i386提供的類似缺頁的機制,在Linux中這個已經不用了,都設成1(當然內核代碼是永駐內存的,但即使不在內存,推測linux也只會用缺頁的標志)。
DPL在這里是0級(特權級)
0D110中,D為1,表明是32位程序(這個細節見i386開發手冊).110是中斷門的標識,其它101是任務門的標識, 111是陷阱(trap)門標識。
Linux對中斷門的設置
于是在Linux中對硬件中斷的中斷門的設置為:
init_IRQ(void)
---------------------------------------------------------
for (i = 0; i < NR_IRQS; i++) {
int vector = FIRST_EXTERNAL_VECTOR + i;
if (vector != SYSCALL_VECTOR)
set_intr_gate(vector, interrupt[ i]);
}
----------------------------------------------------------
其中,FIRST_EXTERNAL_VECTOR=0x20,恰好為8259芯片的IR0的中斷門(見8259部分),也就是時鐘中斷的中斷門),interrupt[
i]為相應處理函數的入口地址
NR_IRQS=224, =256(IDT的向量總數)-32(CPU保留的中斷的個數),在這里設置了所有可設置的向量。
SYSCALL_VECTOR=0x80,在這里意思是避開系統調用這個向量。
而set_intr_gate的定義是這樣的:
----------------------------------------------------
void set_intr_gate(unsigned int n, void *addr){
_set_gate(idt_table+n,14,0,addr);
}
----------------------------------------------------
其中,需要解釋的是:14是標識指明這個是中斷門,注意上面的0D110=01110=14;另外,0指明的是DPL.
中斷入口
以8259的16個中斷為例:
通過宏BUILD_16_IRQS(0x0), BI(x,y),以及
#define BUILD_IRQ(nr) \
asmlinkage void IRQ_NAME(nr); \
__asm__( \
"\n"__ALIGN_STR"\n" \
SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
"pushl $"#nr"-256\n\t" \
"jmp common_interrupt");
得到的16個中斷處理函數為:
IRQ0x00_interrupt:
push $0x00 - 256
jump common_interrupt
IRQ0x00_interrupt:
push $0x01 - 256
jump common_interrupt
... ...
IRQ0x0f_interrupt:
push $0x0f - 256
jump common_interrupt
這些處理函數簡單的把中斷號-256(為什么-256,也許是避免和內部中斷的中斷號有沖突)壓到棧中,然后跳到common_interrupt
其中common_interrupt是由宏BUILD_COMMON_IRQ()展開:
#define BUILD_COMMON_IRQ() \
asmlinkage void call_do_IRQ(void); \
__asm__( \
"\n" __ALIGN_STR"\n" \
"common_interrupt:\n\t" \
SAVE_ALL \
"pushl $ret_from_intr\n\t" \
SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \
"jmp "SYMBOL_NAME_STR(do_IRQ));
.align 4,0x90common_interrupt:
SAVE_ALL展開的保護現場部分
push $ret_from_intrcall
do_IRQ:
jump do_IRQ;
從上面可以看出,這16個的中斷處理函數不過是把中斷號-256壓入棧中,然后保護現場,最后調用do_IRQ
.在common_interrupt中,為了使do_IRQ返回到entry.S的ret_from_intr標號,所以采用的是壓入返回點ret_from_intr,用jump來模擬一個從ret_from_intr上面對do_IRQ的一個調用。
和IDT的銜接
為了便于IDT的設置,在數組interrupt中填入所有中斷處理函數的地址:
void (*interrupt[NR_IRQS])(void) = {
IRQ0x00_interrupt,
IRQ0x01_interrupt,
... ...
}
在中斷門的設置中,可以看到是如何利用這個數組的。
硬件中斷處理函數do_IRQ
do_IRQ的相關對象
在do_IRQ中,一個中斷主要由三個對象來完成
其中,
irq_desc_t對象構成的irq_desc[]數組元素分別對應了224個硬件中斷(idt一共256項,cpu自己前保留了32項,256-32=224,當然這里面有些項是不用的,比如x80是系統調用).
當發生中斷時,函數do_IRQ就會在irq_desc[]相應的項中提取各種信息來完成對中斷的處理。
irq_desc有一個字段handler指向發出這個中斷的設備的處理對象hw_irq_controller,比如在單CPU,這個對象一般就是處理芯片8259的對象。為什么要指向這個對象呢?因為當發生中斷的時候,內核需要對相應的中斷進行一些處理,比如屏蔽這個中斷等。這個時候需要對中斷設備(比如8259芯片)進行操作,于是可以通過這個指針指向的對象進行操作。
irq_desc還有一個字段action指向對象irqaction,后者是產生中斷的設備的處理對象,其中的handler就是處理函數。由于一個中斷可以由多個設備發出,Linux內核采用輪詢的方式,將所有產生這個中斷的設備的處理對象連成一個鏈表,一個一個執行。
例如,硬盤1,硬盤2都產生中斷IRQx,在do_IRQ中首先找到irq_desc[x],通過字段handler對產生中斷IRQx的設備進行處理(對8259而言,就是屏蔽以后的中斷IRQx),然后通過action先后運行硬盤1和硬盤2的處理函數。
hw_irq_controller
hw_irq_controller有多種:
1.在一般單cpu的機器上,通常采用兩個8259芯片,因此hw_irq_controller指的就是i8259A_irq_type
2.在多CPU的機器上,采用APIC子系統來處理芯片,APIC有3個部分組成,一個是I/O APIC模塊,其作用可比做8259芯片,但是它發出的中斷信號會通過
APIC總線送到其中一個(或幾個)CPU中的Local APIC模塊,因此,它還起一個路由的作用;它可以接收16個中斷。
中斷可以采取兩種方式,電平觸發和邊沿觸發,相應的,I/O APIC模塊的hw_irq_controller就有兩種:
ioapic_level_irq_type
ioapic_edge_irq_type
(這里指的是intel的APIC,還有其它公司研制的APIC,我沒有研究過)
3. Local APIC自己也能單獨處理一些直接對CPU產生的中斷,例如時鐘中斷(這和沒有使用Local
APIC模塊的CPU不同,它們接收的時鐘中斷來自外圍的時鐘芯片),因此,它也有自己的 hw_irq_controller:
lapic_irq_type
struct hw_interrupt_type {
const char * typename;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, unsigned long mask);
};
typedef struct hw_interrupt_type hw_irq_controller;
startup 是啟動中斷芯片(模塊),使得它開始接收中斷,一般情況下,就是將 所有被屏蔽的引腳取消屏蔽
shutdown 反之,使得芯片不再接收中斷
enable 設某個引腳可以接收中斷,也就是取消屏蔽
disable 屏蔽某個引腳,例如,如果屏蔽0那么時鐘中斷就不再發生
ack 當CPU收到來自中斷芯片的中斷信號,給相應的引腳的處理,這個各種情況下 (8259, APIC電平,邊沿)的處理都不相同
end 在CPU處理完某個引腳產生的中斷后,對中斷芯片(模塊)的操作。
irqaction
將一個硬件處理函數掛到相應的處理隊列上去(當然首先要生成一個irqaction結構):
-----------------------------------------------------
int request_irq(unsigned int irq,
void (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
-----------------------------------------------------
參數說明在源文件里說得非常清楚。
handler是硬件處理函數,在下面的代碼中可以看得很清楚:
---------------------------------------------
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
---------------------------------------------
第二個參數就是action的dev_id,這個參數非常靈活,可以派各種用處。而且要保證的是,這個dev_id在這個處理鏈中是唯一的,否則刪除會遇到麻煩。
第三個參數是在entry.S中壓入的各個積存器的值。
它的大致流程是:
1.在slab中分配一個irqaction,填上必需的數據
以下在函數setup_irq中。
2.找到它的irq對應的結構irq_desc
3.看它是否想對隨機數做貢獻
4.看這個結構上是否已經掛了其它處理函數了,如果有,則必須確保它本身和這個隊列上所有的處理函數都是可共享的(由于傳遞性,只需判斷一個就可以了)
5.掛到隊列最后
6.如果這個irq_desc只有它一個irqaction,那么還要進行一些初始化工作
7在proc/下面登記 register_irq_proc(irq)(這個我不太明白)
將一個處理函數取下:
void free_irq(unsigned int irq, void *dev_id)
首先在隊列里找到這個處理函數(嚴格的說是irqaction),主要靠dev_id來匹配,這時dev_id的唯一性就比較重要了。
將它從隊列里剔除。
如果這個中斷號沒有處理函數了,那么禁止這個中斷號上再產生中斷:
if (!desc->action) {
desc->status |= IRQ_DISABLED;
desc->handler->shutdown(irq);
}
如果其它CPU在運行這個處理函數,要等到它運行完了,才釋放它:
#ifdef CONFIG_SMP
/* Wait to make sure it's not being used on another CPU */
while (desc->status & IRQ_INPROGRESS)
barrier();
#endif
kfree(action);
do_IRQ
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
1.首先取中斷號,并且獲取對應的irq_desc:
int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code */
int cpu = smp_processor_id();
irq_desc_t *desc = irq_desc + irq;
2.對中斷芯片(模塊)應答:
desc->handler->ack(irq);
3.修改它的狀態(注:這些狀態我覺得只有在SMP下才有意義):
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
status |= IRQ_PENDING; /* we _want_ to handle it */
IRQ_REPLAY是指如果被禁止的中斷號上又產生了中斷,這個中斷是不會被處理的,當這個中斷號被允許產生中斷時,會將這個未被處理的中斷轉為IRQ_REPLAY。
IRQ_WAITING
探測用,探測時,會將所有沒有掛處理函數的中斷號上設置IRQ_WAITING,如果這個中斷號上有中斷產生,就把這個狀態去掉,因此,我們就可以知道哪些中斷引腳上產生過中斷了。
IRQ_PENDING , IRQ_INPROGRESS是為了確保:
同一個中斷號的處理程序不能重入
不能丟失這個中斷號的下一個處理程序
具體的說,當內核在運行某個中斷號對應的處理程序(鏈)時,狀態會設置成IRQ_INPROGRESS。如果在這期間,同一個中斷號上又產生了中斷,并且傳給CPU,那么當內核打算再次運行這個中斷號對應的處理程序(鏈)時,發現已經有一個實例在運行了,就將這下一個中斷標注為IRQ_PENDING,
然后返回。這個已在運行的實例結束的時候,會查看是否期間有同一中斷發生了,
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -