《庖丁解牛Linuxn內核分析》 部分摘錄和筆記

第2章

  1. 計算機的3個法寶:存儲程序計算機、函數調用堆棧機制、中斷
  2. 堆棧的具體做用有:
    • 記錄程序調用框架
    • 傳遞函數參數
    • 保存返回值地址
    • 提供函數內部局部變量的存儲空間
  3. 操做系統有2把寶劍:中斷上下文、進程上下文

第3章

  1. start_kernel中的最後一句爲rest_init,內核啓動完成後,有一個call_cpu_idle,當系統沒有進程須要執行時就調用idle進程。
  2. start_kernel()至關於C語言中的main函數,start_kernel是一切的起點,在此函數被調用前,內核代碼主要是用匯編語言寫的,用於完成硬件系統的初始化工做。該函數幾乎涉及了內核的全部主要模塊,如:trap_init()(中斷向量的初始化),mm_init()(內存管理的初始化),sched_init()(調度模塊的初始化)。
  3. init_task是使用宏初始化的,這是0號進程,是惟一沒有經過fork方式產生的進程。而kernel_init已是經過kernel_thread()函數fork出一個新的進程來執行了,這是1號內核線程。
  4. 調用kernel_thread執行kthreadd,建立PID爲2的內核線程。kthreadd()的任務是管理和調度其餘內核線程kernel_thread,全部的內核線程都是直接或者間接地以kthreadd爲父進程的。

第4章

  1. Inter x86 CPU有4種不一樣的執行級別,分別是的0、一、二、3,數字越小,特權越高。按照Inter的設想,操做系統內核運行在Ring0級別,驅動程序運行在Ring1和Ring2級別,應用程序運行在Ring3級別,實際的操做系統都沒有用到這4個級別。其中Linux操做系統只採用了其中的0和3兩個特權級別,分別對應內核態和用戶態。
  2. 用戶態和內核態很顯著的區分方法就是CS:EIP的指向範圍。在內核態是,CS:EIP的值能夠是任意的地址,在用戶態時(假設32位x86機器,有4GB的進程地址空間),則只能訪問0xc0000000如下的地址。
  3. 系統調用也是一種中斷,中斷處理是從用戶態進入內核態的主要方式。

第5章

  1. arch/x86/kernel/traps.c中trap_init函數調用了set_system_trap_gate函數將系統調用的中斷向量號0x80和system_call中斷服務程序入口的函數指針綁定。
  2. system_call在arch/x86/kernel/entry_32.S,它不是一個正常的函數,是一段特殊的彙編代碼(起點),內部沒有嚴格遵照函數調用的堆棧機制,所以沒法用gdb調試。理解這段代碼有助於理解整個Linux運做機制,由於它的執行過程能夠類推到其餘中斷信號觸發的中斷服務處理過程。
  3. 從系統調用服務程序system_call入口開始,
    • SAVE_ALL保存現場,
    • 而後找到syscall_call和sys_call_table,經過call *sys_call_table(,%eax,4)調用系統調用的內核處理函數,
    • 調用完系統調用後,把eax裏的返回值保存到棧中,在退出前判斷是否須要一個syscall_exit_work,
    • 須要則進入該函數,該函數裏有work_pending,work_pending又有work_notifysig用來處理信號,可能還會調用schedule(work_resced),schedule是很是關鍵的部分,是進程切換的代碼。所以syscall_exit_work是最多見的進程調度時機點。
    • 以後restore_all和最後一個INTERRUPT_RETURN(iret)用於恢復現場並返回系統調用到用戶態結束。

第6章

  1. 操做系統內核實現操做系統的三大管理功能:進程管理、內存管理和文件系統,對應操做系統原理課上最重要的三個抽象概念:進程、虛擬內存和文件。
  2. 在Linux內核中用一個數據結構struct task_struct(include/linux/sched.h)來描述進程,即進程描述符。
    • state是運行狀態
    • stack是進程堆棧
    • struct list_head tasks把全部進程用雙向循環鏈表鏈起來,第0個節點天然是init_task(0號進程,其進程描述符結構體變量的初始化是經過硬編碼固定的,其餘進程都是經過do_fork()複製父進程的方式初始化)
    • struct mm_struct mm, active_mm是和進程地址空間、內存管理相關的數據結構指針。每一個進程都有若干個數據段、代碼段、堆棧段等,都有這個數據結構統領。(每一個進程都有獨立的邏輯地址空間)
    • (struct task_struct) real_parent、parent爲當前進程的父進程,(struct list_head) children爲當前進程的子進程,sibling是兄弟進程,這兩個都是雙向鏈表。
    • struct thread_struct thread用於保存進程上下文中CPU相關的一些狀態信息。
      • 在x86體系中,該結構體定義在arch/x86/include/asm/processor.h。
      • 其中比較關鍵的是sp和ip,分別用於保存進程上下文中的ESP寄存器狀態和EIP寄存器狀態。
    • 還有和文件系統相關的數據結構、已打開的文件描述符,和信號處理有關的,和pipe管道相關的。
  3. 要研究Linux內核某一部分的特定內容,進程描述符起提綱挈領的做用。
  4. 操做系統原理中的進程有三種基本狀態:就緒、運行、阻塞。實際Linux內核管理的進程狀態中的就緒和運行都是TASK_RUNNING。關鍵在於有沒有得到CPU控制權,便是否在CPU中實際執行。
  5. 阻塞態有兩種:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE,前者可被信號和wake_up()喚醒,後者只能被wake_up()喚醒。阻塞條件沒了,就進入就緒隊列。
  6. start_kernel()中最後的rest_init()經過kernel_thread建立了兩個內核線程:
    • kernel_init,最終把用戶態的進程init給啓動起來。
    • kthreadd,管理全部的內核線程,是全部內核線程的祖先。
  7. 用戶態經過庫函數fork()系統調用建立一個子進程。fork系統調用把當前進程複製了一個子進程,也就是一個進程變成了兩個進程,兩個進程執行相同的代碼,只是fork系統調用在父進程和子進程中的返回值不一樣。
    • fork也是系統調用,所以也是經過int $0x80觸發中斷機制。
  8. 子進程複製了父進程中全部的進程信息,包括內核堆棧、進程描述符等,子進程做爲一個獨立的進程也會被調度。
    • 絕大部分信息徹底同樣,有些信息不同,例如內核堆棧(即會修改)、把新進程連接到各鏈表、保存進程執行到什麼位置、thread數據結構等
    • 複製時採用寫時複製技術(Copy On Write),不須要修改進程資源,父子進程共享內存存儲空間。
  9. (kernel/fork.c)fork()、vfork()、clone()三個系統調用和kernel_thread()內核函數均可以建立一個新進程,都是經過do_fork()函數建立。
  10. (kernel/fork.c)do_fork()主要完成調用copy_process()複製父進程信息(建立一個進程內容的主要代碼,返回一個進程描述符指針)、得到pid、調用wake_up_new_task()將子進程加入調度器隊列等待得到分配CPU資源運行,經過clone_flags標誌作一些輔助工做。
  11. (kernel/fork.c)copy_process()主要調用dup_task_struct()複製當前(父)進程描述符(struct task_struct)(最關鍵)、信息檢查、初始化、把子進程狀態設置爲TASK_RUNNING(就緒態)、採用寫時複製技術逐一複製全部其餘進程資源、調用copy_thread()初始化子進程內核棧、設置子進程pid等。
  12. (在x86_32體系中arch/x86/kernel/process_32.c)copy_thread()完成真正內核棧關鍵新的初始化,會設置子進程開始執行的起點ret_from_kernel_thread(內核線程)或ret_from_fork(用戶態進程),而且將本身成的eax置0,所以在執行pid=fork();後(父子進程都會執行,只不過子進程不是真的徹底執行,而是有返回值),fork調用的一個奇妙之處就是它僅僅被調用一次,卻可以返回兩次。父進程獲得的返回值是子進程的process ID(即它自己調用fork的返回值),而子進程是0(eax被設爲0)。
  13. 寫入時複製是一種計算機程序設計領域的優化策略。其核心思想是,若是有多個調用者同時請求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本(private copy)給該調用者,而其餘調用者所見到的最初的資源仍然保持不變。這個過程對其餘的調用者是透明的(transparently)。此做法的主要優勢是若是調用者沒有修改該資源,就不會有副本(private copy)被創建,所以多個調用者只是讀取操做是能夠共享同一份資源。


關於內核堆棧和用戶堆棧

轉自http://www.javashuo.com/article/p-oiotaccu-cs.html,修改了錯別字和排版html

  1. 每個進程(包括普通進程和內核進程)的地址空間都分爲用戶地址空間和內核地址空間兩部分,在32位的x86機器上,用戶地址空間的範圍是0~3G,內核地址空間的範圍是3G~4G。
  2. 對於不一樣的進程,其用戶地址空間會隨着進程不一樣而不一樣,但全部進程的內核地址空間則都是同樣的。
    • 對於內核進程,因爲其始終運行在內核態,因此沒有用戶地址空間,其對應的tast_struct結構體中的mm域也就被賦值爲NULL。
    • 對於用戶進程,其既有用戶地址空間中的棧,也有它本身的內核棧;而內核進程就只有內核棧。
  3. 堆的概念應該是隻存在於進程的用戶地址空間中,因此內核進程是沒有堆一說的。
  4. 內核線程能夠用kmalloc或vmalloc在運行時申請內存。kmalloc或vmalloc申請到的內存在整個內核中均可以使用。比方說內核線程a申請到了一塊內存A,只要把該內存的首地址傳給另外一個內核線程b,則在b中一樣也可使用這塊內存。
  5. 全部進程(包括內核進程和普通進程)都有一個內核棧,在x86的32位機器上內核棧大小能夠爲4KB或8KB,這個能夠在編譯內核的時候配置。
  6. 內核棧的用途有兩個:
    • 當進程陷入內核態,即內核表明進程執行系統調用時,系統調用的參數就放在內核棧上,內核棧記錄着進程的在內核中的調用鏈;
    • 在內核棧被配置成8KB大小的狀況下,當中斷服務程序中斷當前進程時,它將使用當前被中斷進程的內核棧。
  7. 進程的堆棧
    • 內核在建立進程的時候,在建立task_struct的同時,會爲進程建立相應的堆棧。
    • 每一個進程會有兩個棧,一個用戶棧,存在於用戶空間;一個內核棧,存在於內核空間。
    • 當進程在用戶空間運行時,cpu堆棧指針寄存器裏面的內容是用戶堆棧地址,使用用戶棧;當進程在內核空間時,cpu堆棧指針寄存器裏面的內容是內核棧空間地址,使用內核棧。
  8. 進程用戶棧和內核棧的切換
    • 當進程由於中斷或者系統調用而陷入內核態之行時,進程所使用的堆棧也要從用戶棧轉到內核棧。
    • 進程陷入內核態後,先把用戶態堆棧的地址保存在內核棧之中,而後設置堆棧指針寄存器的內容爲內核棧的地址,這樣就完成了用戶棧向內核棧的轉換;當進程從內核態恢復到用戶態之行時,在內核態執行的最後將保存在內核棧裏面的用戶棧的地址恢復到堆棧指針寄存器便可。這樣就實現了內核棧和用戶棧的互轉。
    • 從內核轉到用戶態時用戶棧的地址是在陷入內核的時候保存在內核棧裏面的,可是在陷入內核的時候如何知道內核棧的地址的呢?關鍵在進程從用戶態轉到內核態的時候,進程的內核棧老是空的。這是由於,當進程在用戶態運行時,使用的是用戶棧,當進程陷入到內核態時,內核棧保存進程在內核態運行的相關信息,可是一旦進程返回到用戶態後,內核棧中保存的信息無效,會所有恢復,所以每次進程從用戶態陷入內核的時候獲得的內核棧都是空的。因此在進程陷入內核的時候,直接把內核棧的棧頂地址給堆棧指針寄存器就能夠了。
相關文章
相關標籤/搜索