《Linux核心分析》筆記與課件整理

yz764127031發表於2017-04-30

課程地址:
http://mooc.study.163.com/course/USTC-1000029000#/info
學習思路
孟寧老師這門課並沒有完整的分析Linux核心中程式碼,而是針對關鍵部分進行了講解分析,個人認為核心程式碼也是存在二八定律的情況,少部分關鍵程式碼經常被使用,而理解這部分程式碼對我們認識作業系統的真實工作細節和建立作業系統工作的流程框架有很好的幫助。
總體來說,整門課程的內容可以分為三個部分:
(1)核心分析所需要的知識基礎
(2)系統呼叫的原理和實現
(3)程式管理
第一部分對X86彙編,函式呼叫堆疊,儲存計算機工作原理等進行了講解,第二部分主要針對系統呼叫的實現,包括使用者態與核心態,中斷上下文的切換等進行了講解,第三部分主要是針對程式來講解,程式是作業系統中最重要的抽象,因為他是硬體資源分配的基本單位,其他的抽象都是圍繞他來進行的,這部分針對程式的建立,程式的執行環境,程式的切換進行了講解。

筆記和課件
(1)知識基礎
X86彙編
這門課程中主要涉及的硬體就是CPU,選擇的CPU指令集是X86彙編,學習的重點是程式管理部分,對記憶體管理,檔案管理比較忽略,相關的硬體細節也被忽略。
學習X86指令集的關鍵事實上在於以下幾個部分:

  • 記憶理解X86的CPU暫存器
  • 程式的邏輯分段(程式碼段,堆疊段,資料段)
  • 函式呼叫堆疊

計算機工作的基礎

計算機是如何工作的?(總結)——三個法寶

1.儲存程式計算機工作模型,計算機系統最最基礎性的邏輯結構;
2.函式呼叫堆疊,高階語言得以執行的基礎,只有機器語言和組合語言的時候堆疊機制對於計算機來說並不那麼重要,但有了高階語言及函式,堆疊成為了計算機的基礎功能;
enter  
pushl %ebp
 movl %esp,%ebp
leave  
movl %ebp,%esp
popl %ebp
函式引數傳遞機制和區域性變數儲存
3.中斷,多道程式作業系統的基點,沒有中斷機制程式只能從頭一直執行結束才有可能開始執行其他程式。

C語言函式呼叫堆疊框架
1.堆疊是C語言執行時必須的一個記錄呼叫路徑和引數的空間
2.相關暫存器和堆疊操作
SP BP PUSH POP
這裡寫圖片描述
3.函式呼叫和返回
CALL RET
這裡寫圖片描述
4.函式堆疊框架
這裡寫圖片描述
這裡寫圖片描述

(2)系統呼叫
作業系統提供的抽象的本質:
作業系統提供了程式,地址空間,檔案三個抽象,本質上是對硬體的虛擬,使得硬體由物理上的一個變為邏輯上的多個,利用的就是共享(時間或者空間)。
系統的呼叫的本質:
這裡寫圖片描述
系統呼叫可以看做特殊的函式呼叫,所以每次進行系統呼叫都要新建堆疊。
這裡寫圖片描述
這裡寫圖片描述

(3)程式管理
檔案和地址空間事實上都是依賴程式這個抽象,程式通過系統呼叫來控制其他的資源。
程式的描述

  • 程式控制塊PCB——task_struct
  • 程式的標示pid
  • 所有程式連結串列struct list_head tasks;(雙向連結串列)
  • 程式建立的程式具有父子關係
  • Linux為每個程式分配一個8KB大小的記憶體區域,用於存放該程式兩個不同的資料結構:Thread_info和程式的核心堆疊
    程式處於核心態時使用,不同於使用者態堆疊,即PCB中指定了核心棧,
    核心控制路徑所用的堆疊很少,因此對棧和Thread_info來說,8KB足夠

程式的建立

建立一個新程式在核心中的執行過程
fork、vfork和clone三個系統呼叫都可以建立一個新程式,而且都是通過呼叫do_fork來實現程式的建立;
Linux通過複製父程式來建立一個新程式,那麼這就給我們理解這一個過程提供一個想象的框架:
複製一個PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
要給新程式分配一個新的核心堆疊
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //這裡只是複製thread_info,而非複製核心堆疊

要修改複製過來的程式資料,比如pid、程式連結串列等等都要改改吧,見copy_process內部。
從使用者態的程式碼看fork();函式返回了兩次,即在父子程式中各返回一次,父程式從系統呼叫中返回比較容易理解,子程式從系統呼叫中返回,那它在系統呼叫處理過程中的哪裡開始執行的呢?這就涉及子程式的核心堆疊資料狀態和task_struct中thread記錄的sp和ip的一致性問題,這是在哪裡設定的?copy_thread in copy_process
*childregs = *current_pt_regs(); //複製核心堆疊
childregs->ax = 0; //為什麼子程式的fork返回0,這裡就是原因!

p->thread.sp = (unsigned long) childregs; //排程到子程式時的核心棧頂
p->thread.ip = (unsigned long) ret_from_fork; //排程到子程式時的第一條指令地址

程式的裝載
程式生成可執行檔案的過程
可執行檔案裝載生成程式,並進行靜態與動態連結庫檔案的過程
詳細過程略,主要參考書 《程式設計師的自我修養》

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

程式的排程時機與程式的切換

作業系統原理中介紹了大量程式排程演算法,這些演算法從實現的角度看僅僅是從執行佇列中選擇一個新程式,選擇的過程中運用了不同的策略而已。
對於理解作業系統的工作機制,反而是程式的排程時機與程式的切換機制更為關鍵。

程式排程的時機

  • 中斷處理過程(包括時鐘中斷、I/O中斷、系統呼叫和異常)中,直接呼叫schedule(),或者返回使用者態時根據need_resched標記呼叫schedule();
  • 核心執行緒可以直接呼叫schedule()進行程式切換,也可以在中斷處理過程中進行排程,也就是說核心執行緒作為一類的特殊的程式可以主動排程,也可以被動排程;
  • 使用者態程式無法實現主動排程,僅能通過陷入核心態後的某個時機點進行排程,即在中斷處理過程中進行排程。

程式的切換

  • 為了控制程式的執行,核心必須有能力掛起正在CPU上執行的程式,並恢復以前掛起的某個程式的執行,這叫做程式切換、任務切換、上下文切換;
  • 掛起正在CPU上執行的程式,與中斷時儲存現場是不同的,中斷前後是在同一個程式上下文中,只是由使用者態轉向核心態執行;
  • 程式上下文包含了程式執行需要的所有資訊 使用者地址空間:包括程式程式碼,資料,使用者堆疊等 控制資訊:程式描述符,核心堆疊等
    硬體上下文(注意中斷也要儲存硬體上下文只是儲存的方法不同)
  • schedule()函式選擇一個新的程式來執行,並呼叫context_switch進行上下文的切換,這個巨集呼叫switch_to來進行關鍵上下文切換
    next = pick_next_task(rq, prev);//程式排程演算法都封裝這個函式內部 context_switch(rq,
    prev, next);//程式上下文切換
    switch_to利用了prev和next兩個引數:prev指向當前程式,next指向被排程的程式
   define switch_to(prev, next, last)                    \
32do {                                 \
33  /*                              \
34   * Context-switching clobbers all registers, so we clobber  \
35   * them explicitly, via unused output variables.     \
36   * (EAX and EBP is not listed because EBP is saved/restored  \
37   * explicitly for wchan access and EAX is the return value of   \
38   * __switch_to())                     \
39   */                                \
40  unsigned long ebx, ecx, edx, esi, edi;                \
41                                  \
42  asm volatile("pushfl\n\t"      /* save    flags */   \
43           "pushl %%ebp\n\t"        /* save    EBP   */ \
44           "movl %%esp,%[prev_sp]\n\t"  /* save    ESP   */ \
45           "movl %[next_sp],%%esp\n\t"  /* restore ESP   */ \
46           "movl $1f,%[prev_ip]\n\t"    /* save    EIP   */ \
47           "pushl %[next_ip]\n\t"   /* restore EIP   */    \
48           __switch_canary                   \
49           "jmp __switch_to\n"  /* regparm call  */ \
50           "1:\t"                        \
51           "popl %%ebp\n\t"     /* restore EBP   */    \
52           "popfl\n"         /* restore flags */  \
53                                  \
54           /* output parameters */                \
55           : [prev_sp] "=m" (prev->thread.sp),     \
56             [prev_ip] "=m" (prev->thread.ip),        \
57             "=a" (last),                 \
58                                  \
59             /* clobbered output registers: */     \
60             "=b" (ebx), "=c" (ecx), "=d" (edx),      \
61             "=S" (esi), "=D" (edi)             \
62                                       \
63             __switch_canary_oparam                \
64                                  \
65             /* input parameters: */                \
66           : [next_sp]  "m" (next->thread.sp),        \
67             [next_ip]  "m" (next->thread.ip),       \
68                                       \
69             /* regparm parameters for __switch_to(): */  \
70             [prev]     "a" (prev),              \
71             [next]     "d" (next)               \
72                                  \
73             __switch_canary_iparam                \
74                                  \
75           : /* reloaded segment registers */           \
76          "memory");                  \
77} while (0)

Linux系統的一般執行過程

最一般的情況:正在執行的使用者態程式X切換到執行使用者態程式Y的過程

  • 正在執行的使用者態程式X
  • 發生中斷——save cs:eip/esp/eflags(current) to kernel stack,then load
    cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
    SAVE_ALL //儲存現場
  • 中斷處理過程中或中斷返回前呼叫了schedule(),其中的switch_to做了關鍵的程式上下文切換
  • 標號1之後開始執行使用者態程式Y(這裡Y曾經通過以上步驟被切換出去過因此可以從標號1繼續執行)
  • restore_all //恢復現場
  • iret - pop cs:eip/ss:esp/eflags from kernel stack
  • 繼續執行使用者態程式Y

幾種特殊情況

  • 通過中斷處理過程中的排程時機,使用者態程式與核心執行緒之間互相切換和核心執行緒之間互相切換,與最一般的情況非常類似,只是核心執行緒執行過程中發生中斷沒有程式使用者態和核心態的轉換;
  • 核心執行緒主動呼叫schedule(),只有程式上下文的切換,沒有發生中斷上下文的切換,與最一般的情況略簡略;
  • 建立子程式的系統呼叫在子程式中的執行起點及返回使用者態,如fork; 載入一個新的可執行程式後返回到使用者態的情況,如execve;

這裡寫圖片描述

相關文章