1. 程式人生 > >2018-2019-1 20189203《Linux核心原理與分析》第九周作業

2018-2019-1 20189203《Linux核心原理與分析》第九周作業

第一部分 課本學習

程序的切換和系統的一般執行過程

  • 程序排程的時機
    Linux核心系統通過schedule函式實現程序排程,程序排程的時機就是核心呼叫schedule函式的時機。當核心即將返回使用者空間時,核心會檢查need_resched標誌是否設定。如果設定,則呼叫schedule函式,此時是從中斷(異常/系統呼叫)處理程式返回使用者空間的時間點作為一個固定的排程時機點。
    簡單總結程序排程時機如下:
    使用者程序通過特定的系統呼叫主動讓出CPU
    中斷處理程式在核心返回使用者態時進行排程。
    核心執行緒主動呼叫schedule函式讓出CPU。
    中斷處理程式主動呼叫schedule函式讓出CPU,涵蓋以上第一種和第二種情況。

  • 排程策略與演算法
    排程策略:Linux系統中常用的幾種排程策略為SCHED_NORMAL、SCHED_FIFO、SCHED_RR。其中SCHED_NORMAL是用於普通程序的排程類,而SCHED_FIFO和SCHED_RR是用於實時程序的排程類,優先順序高於SCHED_NORMAL。核心中根據程序的優先順序來區分普通程序和實時程序,Linux核心程序優先順序為0~139,數值越高,優先順序越低,0為最高有限級。實時程序的優先順序取值為0~99;而普通程序只具有nice值,nice值對映到優先順序為100~139。子程序會繼承父程序的優先順序。
    CFS排程演算法:CFS即為完全公平排程演算法,其基本原理是基於權重的動態優先順序排程演算法。每個程序使用CPU的順序由程序已使用的CPU虛擬時間(vruntime)決定,已使用的虛擬時間越少,程序排序就越靠前,程序再次被排程執行的概率就越高。每個程序每次佔用CPU後能夠執行的時間(ideal_runtime)由程序的權重決定,並且保證在某個時間週期(__sched_period)內執行佇列裡的所有程序都能夠至少被排程執行一次。

  • 程序上下文切換
    在實際程式碼中,每個程序切換基本由兩個步驟組成。
    切換頁全域性目錄(CR3)以安裝一個新的地址空間,這樣不同程序的虛擬地址如0x8048400就會經過不同的頁錶轉換為不同的實體地址。
    切換核心態堆疊和硬體上下文,因為硬體上下文提供了核心執行新程序所需要的所有資訊,包含CPU暫存器狀態

  • Linux系統的執行過程
    最基本和一般場景是:正在執行的使用者態程序X切換到使用者態程序Y的過程。具體過程如下。
    1、 正在執行的使用者態程序X。
    2、發生中斷(包括異常、系統呼叫等)。
    3、SAVE_ALL,儲存現場,此時完成了中斷上下文切換,即從程序X的使用者態到程序X的核心態。
    4、中斷處理過程中或中斷返回前呼叫了schedule函式,其中的switch_to做了關鍵的程序上下文切換。
    5、標號1,之後開始執行使用者態程序Y(這裡Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)。
    6、restore_all,恢復現場,與3中儲存現場相對應。
    7、iret - pop cs:eip/ss:esp/eflags,從Y程序的核心堆疊中彈出2中硬體完成的壓棧內容。此時完成了中斷上下文的切換,即從程序Y的核心態返回到程序Y的使用者態。
    8、繼續執行使用者態程序Y。

  • Linux系統架構與執行過程概覽
    Linux系統的整體架構如圖所示:

    Ls命令執行過程示意圖如下:

第二部分 程式碼分析

  • context_switch程式碼
static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
    struct mm_struct *mm, *oldmm;

    prepare_task_switch(rq, prev, next);

    mm = next->mm;
    oldmm = prev->active_mm;
    /*
     * For paravirt, this is coupled with an exit in switch_to to
     * combine the page table reload and the switch backend into
     * one hypercall.
     */
    arch_start_context_switch(prev);

    if (!mm) {    //如果被切換進來的程序的mm為空切換,核心執行緒mm為空
        next->active_mm = oldmm;  //將共享切換出去的程序的active_mm
        atomic_inc(&oldmm->mm_count);  //有一個程序共享,所有引用計數加一
        enter_lazy_tlb(oldmm, next);  //普通mm不為空,則呼叫switch_mm切換地址空間
    } else
        switch_mm(oldmm, mm, next);

    if (!prev->mm) {
        prev->active_mm = NULL;
        rq->prev_mm = oldmm;
    }
    /*
     * Since the runqueue lock will be released by the next
     * task (which is an invalid locking op but in the case
     * of the scheduler it's an obvious special-case), so we
     * do an early lockdep release here:
     */
    spin_release(&rq->lock.dep_map, 1, _THIS_IP_);

    context_tracking_task_switch(prev, next);
    // 這裡切換暫存器狀態和棧 
    switch_to(prev, next, prev);

    barrier();
    /*
     * this_rq must be evaluated again because prev may have moved
     * CPUs since it called schedule(), thus the 'rq' on its stack
     * frame will be invalid.
     */
    finish_task_switch(this_rq(), prev);
}
  • switch_to程式碼
#define switch_to(prev, next, last) //prev指向當前程序,next指向被排程的程序                                   
do {                                                                              
                                                        
         unsigned long ebx, ecx, edx, esi, edi;
                                  
         asm volatile("pushfl\n\t"  //把prev程序的flag儲存到prev程序的核心堆疊中
                      "pushl %%ebp\n\t" //把prev程序的基址ebp儲存到prev程序的核心堆疊中
           
                      "movl %%esp,%[prev_sp]\n\t"//儲存ESP
                      "movl %[next_sp],%%esp\n\t"//更新ESP,將下一棧頂儲存到ESP中 
                      
                      "movl $1f,%[prev_ip]\n\t"//儲存當前程序EIP*  
                      "pushl %[next_ip]\n\t"//把next程序起點壓入next程序的核心堆疊棧頂 
                      __switch_canary                                        
                      "jmp __switch_to\n"//prev程序中設定next程序堆疊
                                         //jmp不同於call,是通過暫存器傳遞引數,而不是通過堆疊傳遞引數,所以ret時彈出的是之前壓入棧頂的next程序起點
                                         //wancheng EIP的切換
                      "1:\t"                                                    
                      "popl %%ebp\n\t"   
                      "popfl\n"                         
                                                                                
                      /* output parameters */                                   
                      : [prev_sp] "=m"(prev->thread.sp),     //儲存prev程序的esp
                        [prev_ip] "=m"(prev->thread.ip),     //儲存prev程序的eip
                        "=a" (last),                                                
                                                                                   
                      /* clobbered output registers: */              
                        "=b" (ebx), "=c"(ecx), "=d" (edx),              
                        "=S" (esi), "=D"(edi)                            
                                                                                    
                       __switch_canary_oparam                                     
                                                                                    
                          /* input parameters: */                                  
                      : [next_sp]  "m" (next->thread.sp),      //next程序核心堆疊棧頂地址,即esp
                        [next_ip]  "m" (next->thread.ip),      //next程序的原eip
                                                                                   
                      /* regparm parameters for __switch_to():*/  
                      //jmp通過eax暫存器和edx暫存器傳遞引數
                        [prev]     "a" (prev),                                   
                        [next]     "d" (next)                                    
                                                                                     
                        __switch_canary_iparam                             
                                                                                    
                      : /* 重新載入段暫存器            
                     "memory");                                           
} while (0)  

第三部分 實驗樓實驗

在實驗樓中配置執行MenuOS系統:


配置gdb斷點:

開始執行,分別停在各斷點處:


Schedule函式的作用非常重要,是程序排程的主體函式。其中pick_next_task函式是schedule函式中重要的函式,負責根據排程策略和排程演算法選擇下一個程序,context_switch函式是schedule函式中實現程序切換的函式,switch_to是context_switch函式中進行程序關鍵上下文切換的函式。由於switch_to內部是內嵌彙編程式碼,無法跟蹤除錯。