linux内核响应,读书笔记——Linux内核源代码情景分析——3.4 中断的响应和服务...
CPU響應中斷
CPU從中斷控制器取得中斷向量,然后根據具體的中斷向量從中斷向量表IDT中找到對應的表項,而該表項應該是一個中斷門。這樣,CPU就根據中斷門的設置而到達了該通道的總服務程序入口。
由于中斷是當CPU在用戶空間運行時發生的,運行級別CPL為3,而中斷服務程序屬于內核,其運行級別CPL為0。所以CPU要從寄存器TR所指向的當前TSS中取出用于內核0級的堆棧指針,并把堆棧切換到內核堆棧,即當前進程的系統空間堆棧。
每次從系統空間返回到用戶空間時堆棧指針一定回到其原點,“堆棧底部”。也就是說,當CPU從TSS中取出內核堆棧指針并切換到內核堆棧時,這個堆棧一定是空的。
所有公用中斷請求的服務程序總入口是由gcc在預處理階段生成的。它將一個中斷請求號相關的數值壓入堆棧(中斷請求號減去256使其變成負數!)。
系統堆棧中的這個位置在因系統調用而進入內核時要用來存放系統調用號,而系統調用又與中斷服務共用一部分子程序。這樣,要有個手段來加以區分。
將一個整數裝入一個通用寄存器之后,要判斷它是否大于等于0很方便的,只要一條寄存器指令就可以了。而如果要與另一個常數相比較,就至少要多訪問一次內存。
公共得跳轉目標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,是保存現場,見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以后,又將一個程序標號入口ret_from_intr壓入堆棧,并通過jmp指令轉入另一段程序do_IRQ()。common_interrupt本質上不是函數,它們都沒有與return相關得指令,所以從common_interrupt不能方回到某一個interrupt,可是,do_IRQ()卻是一個函數。在通過jmp指令轉入do_IRQ()之前將返回地址ret_from_intr壓入堆棧就模擬了一次函數調用,仿佛對do_IRQ()的調用就發生在CPU進入ret_from_intr的的一條指令前夕一樣。這樣,當從do_IRQ()返回時就會“返回”到ret_from_intr繼續執行。
do_IRQ()是在arch/i386/kernel/irq.c中定義的,
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
調用參數是一個pt_regs數據結構,主要這是一個數據結構,不是指向數據結構得指針!也就是說,在堆棧中的返回地址以上的位置應該是一個數據結構的映象。
數據結構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來看這個結構!)
前面所做得一切,包括CPU在進入中斷時自動做的,實際上都是在為do_IRQ()建立一個模擬得子程序調用環境,使得在do_IRQ()中既可以方便地知道進入中斷前夕各個寄存器得內容,又可以在執行完畢后返回到ret_from_intr,并且從那里執行中斷放回。
對系統堆棧的這種安排不光用于中斷,還用于系統調用。
在IRQ0xXX_interrupt中把中斷請求號相關的數值減去256壓入堆棧的目的是使得在公共的中斷處理程序中可以知道中斷的來源。以IRQ3為例,3-265
= -253 =
0xffffff03,通過regs.orig_eax讀出來并且把高位屏蔽掉,又得到0x03。由于do_IRQ()僅用于中斷服務,所以不需要顧及系統調用時的情況。
do_IRQ()中調用spin_lock()加鎖,是為了多處理器的情況而設置的。
desc->status的標志位IRQ_PENDING和IRQ_INPROGRESS是為了同一個CPU不允許中斷服務套嵌,不同CPU不允許并發地進入同一個中斷服務程序而設計的。
handle_IRQ_event()函數依次執行對列中的各個中斷服務程序,讓它們辨認本次中斷請求是否來自各自的服務對象,即中斷源,如果是就提供相應的服務。
action->flags標志位SA_INTERRUPT表示這個中斷服務程序應該在開啟中斷的情況下執行。_sti()為開中斷,_cli為關中斷。標志位SA_SAMPLE_RANDOM表示服務程序要為系統引入一些隨機性。
handle_IRQ_event()返回值status最低位必須為1。Force the "do bottom
halves" bit.
當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
即使跳轉到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是相互對應的。addl
$4,%esp將堆棧指針的當前值加4,是為了跳過ORIG_EAX,那是在進入中斷之初壓入堆棧的經過變形的中斷請求號。
當CPU到達iret指令時,系統堆棧又恢復到剛進入中斷門時的狀態,而iret則使CPU從中斷返回。
總結
以上是生活随笔為你收集整理的linux内核响应,读书笔记——Linux内核源代码情景分析——3.4 中断的响应和服务...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 打开端口1935,CentO
- 下一篇: linux更新模块,Linux下Ngin