1. 程式人生 > >排程時機分析之被動排程(之中斷處理返回)

排程時機分析之被動排程(之中斷處理返回)

之前一篇部落格中是關於被動排程的系統呼叫返回部分,這篇部落格將接著寫被動排程的中斷返回部分。

分析基於核心版本2.6.12.6

Linux程序的排程主要分為主動排程和被動排程兩大類。整個linux執行過程中,被動排程分為使用者態搶佔排程和核心態搶佔排程。

使用者態搶佔排程發生在當系統呼叫、中斷處理、異常處理等返回使用者態時,或者程序的時間片用完時。

這篇部落格就是寫使用者態搶佔排程的中斷處理返回部分的,是看程式碼的一個總結,寫下來希望能夠加深理解。

中斷處理返回

當從中斷返回時是一個排程點,下面分析從中斷返回相關的程式碼。

ENTRY(common_interrupt /*中斷公共入口

*/

interrupt do_IRQ  

/* 0(%rsp): oldrsp-ARGOFFSET */  

ret_from_intr:    

popq  %rdi  

cli   //關中斷

subl $1,%gs:pda_irqcount  

#ifdef CONFIG_DEBUG_INFO  

movq RBP(%rdi),%rbp  

#endif

leaq ARGOFFSET(%rdi),%rsp  

exit_intr:     

GET_THREAD_INFO(%rcx //獲取當前核心棧的thread_info的地址儲存到rcx暫存器中

testl $3,CS-ARGOFFSET(

%rsp//讀取棧中的cs,判斷中斷是否發生在使用者態

je retint_kernel  //中斷髮生在核心態,返回核心態

common_interrupt函式是中斷的公共入口,它首先使用用巨集interrupt來呼叫do_IRQ函式處理中斷。ret_from_intr標籤開始就是執行完中斷之後,中斷返回相關的程式碼。

先來看下interrupt巨集的實現程式碼:

/* 0(%rsp): interrupt number */  

 .macro interrupt func  

 CFI_STARTPROC simple  /*用在每個函式的開始用於初始化一些內部資料結構*/

 CFI_DEF_CFA rsp

,(SS-RDI

 CFI_REL_OFFSET rsp,(RSP-ORIG_RAX

 CFI_REL_OFFSET rip,(RIP-ORIG_RAX

 cld  /*清方向標誌,在串指令操作期間,方向標誌為DISI暫存器選擇遞增方式或遞減方式。如果D=1.則暫存器內容自動地遞減;如果D=0,則暫存器內容自動地遞增*/

SAVE_ARGS  /*儲存現場*/

 leaq -ARGOFFSET(%rsp),%rdi # arg1 for handler  

testl $3,CS(%rdi/*判斷中斷髮生時CPU是否運行於使用者態*/

je 1f  /*是核心態*/

 swapgs   /*切換gs暫存器的使用者態值和核心值*/

1: addl $1,%gs:pda_irqcount # RED-PEN should check preempt count  

 movq %gs:pda_irqstackptr,%rax  

 cmoveq %rax,%rsp         

 pushq %rdi   # save old stack   

call \func  /*呼叫func函式*/

 .endm  

SAVE_ARGS巨集是將暫存器壓棧,也就是所謂的“儲存現場”。執行SAVE_ARGS巨集之後系統堆疊的示意圖如下:

程式碼段暫存器cs的最低兩位代表著中斷髮生時cpu的執行級別CPL0表示核心態,3表示使用者態。如果最低兩位為非0,則說明中斷髮生於使用者空間。此巨集中判斷中斷髮生時cpu是否運行於使用者態,如果是核心態則直接跳轉到標籤1處執行,否則先呼叫swapgs命令切換gs暫存器的使用者態值和核心值。Testl指令將$3CS(%rdi)相與,je就是當相與結果為0,也就是為核心態。

接下來就是將當前cpupdapda_irqcount欄位加1,並將pda中的pda_irqstackptr欄位賦給rsp,即中斷棧幀。最後使用call命令呼叫中斷處理函式func,也就是do_IRQ函式。do_IRQ函式的引數為pt_regs結構,引數是通過rdi暫存器傳入的。

從ret_from_intr開始處理中斷返回,如果中斷髮生在核心態,則返回核心態,跳轉到retint_kernel執行;如果中斷髮生在使用者態,則返回使用者態,跳轉到retint_with_reschedule執行。

retint_kernel將在核心態搶佔排程部分分析,這裡是分析中斷處理返回時使用者態搶佔的情況,因此接下來看看retint_with_reschedule程式碼。

/* Interrupt came from user space */

/*

 * Has a correct top of stack, but a partial stack frame

 * %rcx: thread info. Interrupts off.

 */

retint_with_reschedule:

movl $_TIF_WORK_MASK,%edi

retint_check:

movl threadinfo_flags(%rcx),%edx   

andl %edi,%edx   

jnz  retint_careful  //判斷是否還有其他工作需要做有的話跳轉到retint_careful執行

retint_swapgs:

swapgs     //當處理器離開核心時使用swapgs命令在gs暫存器的核心與使用者態值之間切換

retint_restore_args:

cli    

RESTORE_ARGS 0,8,0

iret_label:

iretq

首先判斷是否還有其他工作需要處理,有其他工作處理的情況下跳轉到retint_careful執行。沒有其他工作處理則首先呼叫swapgs命令在gs暫存器的核心與使用者態值之間切換,為離開核心做準備。然後呼叫RESTORE_ARGS巨集恢復之前SAVE_ARGS巨集儲存現場所壓棧的那些暫存器。最後就是呼叫iretq離開中斷。

下面看下當還有其他工作需要處理時retint_careful的執行情況。

/* edi: workmask, edx: work */

retint_careful:

bt    $TIF_NEED_RESCHED,%edx     //檢查是否需要重新排程

jnc   retint_signal      //不需要重新排程跳轉到retint_signal

sti        //開中斷

pushq %rdi

call  schedule

popq %rdi

GET_THREAD_INFO(%rcx)

cli       //關中斷

jmp retint_check

首先檢查是否需要重新排程,不需要重新排程就跳轉到retint_signal執行。需要重新排程的話先開中斷,然後呼叫schedule函式執行任務的排程。當排程的任務執行完之後,重新獲得cpu時,跳轉到retint_check處再次檢查是否有工作需要處理。