linux内核响应,读书笔记——Linux内核源代码情景分析——3.4 中断的响应和服务...
CPU響應(yīng)中斷
CPU從中斷控制器取得中斷向量,然后根據(jù)具體的中斷向量從中斷向量表IDT中找到對應(yīng)的表項,而該表項應(yīng)該是一個中斷門。這樣,CPU就根據(jù)中斷門的設(shè)置而到達了該通道的總服務(wù)程序入口。
由于中斷是當(dāng)CPU在用戶空間運行時發(fā)生的,運行級別CPL為3,而中斷服務(wù)程序?qū)儆趦?nèi)核,其運行級別CPL為0。所以CPU要從寄存器TR所指向的當(dāng)前TSS中取出用于內(nèi)核0級的堆棧指針,并把堆棧切換到內(nèi)核堆棧,即當(dāng)前進程的系統(tǒng)空間堆棧。
每次從系統(tǒng)空間返回到用戶空間時堆棧指針一定回到其原點,“堆棧底部”。也就是說,當(dāng)CPU從TSS中取出內(nèi)核堆棧指針并切換到內(nèi)核堆棧時,這個堆棧一定是空的。
所有公用中斷請求的服務(wù)程序總?cè)肟谑怯蒰cc在預(yù)處理階段生成的。它將一個中斷請求號相關(guān)的數(shù)值壓入堆棧(中斷請求號減去256使其變成負數(shù)!)。
系統(tǒng)堆棧中的這個位置在因系統(tǒng)調(diào)用而進入內(nèi)核時要用來存放系統(tǒng)調(diào)用號,而系統(tǒng)調(diào)用又與中斷服務(wù)共用一部分子程序。這樣,要有個手段來加以區(qū)分。
將一個整數(shù)裝入一個通用寄存器之后,要判斷它是否大于等于0很方便的,只要一條寄存器指令就可以了。而如果要與另一個常數(shù)相比較,就至少要多訪問一次內(nèi)存。
公共得跳轉(zhuǎn)目標(biāo)common_interrupt()是在include/asm-i386/hw_irq.h中定義的:
#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));
其中SAVE_ALL,是保存現(xiàn)場,見arch/i386/kernel/entry.S中
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__KERNEL_DS),%edx; \
movl %edx,%ds; \
movl %edx,%es;
在SAVE_ALL以后,又將一個程序標(biāo)號入口ret_from_intr壓入堆棧,并通過jmp指令轉(zhuǎn)入另一段程序do_IRQ()。common_interrupt本質(zhì)上不是函數(shù),它們都沒有與return相關(guān)得指令,所以從common_interrupt不能方回到某一個interrupt,可是,do_IRQ()卻是一個函數(shù)。在通過jmp指令轉(zhuǎn)入do_IRQ()之前將返回地址ret_from_intr壓入堆棧就模擬了一次函數(shù)調(diào)用,仿佛對do_IRQ()的調(diào)用就發(fā)生在CPU進入ret_from_intr的的一條指令前夕一樣。這樣,當(dāng)從do_IRQ()返回時就會“返回”到ret_from_intr繼續(xù)執(zhí)行。
do_IRQ()是在arch/i386/kernel/irq.c中定義的,
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
調(diào)用參數(shù)是一個pt_regs數(shù)據(jù)結(jié)構(gòu),主要這是一個數(shù)據(jù)結(jié)構(gòu),不是指向數(shù)據(jù)結(jié)構(gòu)得指針!也就是說,在堆棧中的返回地址以上的位置應(yīng)該是一個數(shù)據(jù)結(jié)構(gòu)的映象。
數(shù)據(jù)結(jié)構(gòu)struct pt_regs是在include/asm-i386/ptrace.h中定義的:
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int?xds;
int?xes;
long orig_eax;
long eip;
int?xcs;
long eflags;
long esp;
int?xss;
};
(對照前面的SAVE_ALL來看這個結(jié)構(gòu)!)
前面所做得一切,包括CPU在進入中斷時自動做的,實際上都是在為do_IRQ()建立一個模擬得子程序調(diào)用環(huán)境,使得在do_IRQ()中既可以方便地知道進入中斷前夕各個寄存器得內(nèi)容,又可以在執(zhí)行完畢后返回到ret_from_intr,并且從那里執(zhí)行中斷放回。
對系統(tǒng)堆棧的這種安排不光用于中斷,還用于系統(tǒng)調(diào)用。
在IRQ0xXX_interrupt中把中斷請求號相關(guān)的數(shù)值減去256壓入堆棧的目的是使得在公共的中斷處理程序中可以知道中斷的來源。以IRQ3為例,3-265
= -253 =
0xffffff03,通過regs.orig_eax讀出來并且把高位屏蔽掉,又得到0x03。由于do_IRQ()僅用于中斷服務(wù),所以不需要顧及系統(tǒng)調(diào)用時的情況。
do_IRQ()中調(diào)用spin_lock()加鎖,是為了多處理器的情況而設(shè)置的。
desc->status的標(biāo)志位IRQ_PENDING和IRQ_INPROGRESS是為了同一個CPU不允許中斷服務(wù)套嵌,不同CPU不允許并發(fā)地進入同一個中斷服務(wù)程序而設(shè)計的。
handle_IRQ_event()函數(shù)依次執(zhí)行對列中的各個中斷服務(wù)程序,讓它們辨認本次中斷請求是否來自各自的服務(wù)對象,即中斷源,如果是就提供相應(yīng)的服務(wù)。
action->flags標(biāo)志位SA_INTERRUPT表示這個中斷服務(wù)程序應(yīng)該在開啟中斷的情況下執(zhí)行。_sti()為開中斷,_cli為關(guān)中斷。標(biāo)志位SA_SAMPLE_RANDOM表示服務(wù)程序要為系統(tǒng)引入一些隨機性。
handle_IRQ_event()返回值status最低位必須為1。Force the "do bottom
halves" bit.
當(dāng)do_IRQ()返回時,返回到ret_from_intr處,見
arch/i386/kernel/entry.S
ENTRY(ret_from_intr)
GET_CURRENT(%ebx)
ret_from_exception:
movl EFLAGS(%esp),%eax?# mix EFLAGS and CS
movb CS(%esp),%al
testl $(VM_MASK | 3),%eax?# return to VM86 mode or
non-supervisor?
jne ret_from_sys_call
jmp restore_all
即使跳轉(zhuǎn)到ret_from_sys_call,最終還是會到restore_all
#define RESTORE_ALL?\
popl %ebx;?\
popl %ecx;?\
popl %edx;?\
popl %esi;?\
popl %edi;?\
popl %ebp;?\
popl %eax;?\
1:?popl %ds;?\
2:?popl %es;?\
addl $4,%esp;?\
3:?iret;?\
這與SAVE_ALL是相互對應(yīng)的。addl
$4,%esp將堆棧指針的當(dāng)前值加4,是為了跳過ORIG_EAX,那是在進入中斷之初壓入堆棧的經(jīng)過變形的中斷請求號。
當(dāng)CPU到達iret指令時,系統(tǒng)堆棧又恢復(fù)到剛進入中斷門時的狀態(tài),而iret則使CPU從中斷返回。
總結(jié)
以上是生活随笔為你收集整理的linux内核响应,读书笔记——Linux内核源代码情景分析——3.4 中断的响应和服务...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: linux 打开端口1935,CentO
- 下一篇: linux更新模块,Linux下Ngin
