1. 程式人生 > 其它 >Linux核心設計與實現

Linux核心設計與實現

Linux核心設計與實現

關於程序管理
  1. 核心並不區分執行緒和其他的一般程序。對於核心來說,所有的程序都一樣——只不過其中的一些共享資源而已。每個執行緒都有唯一隸屬於自己的task_struct.

    建立程序:clone(SIGCHLD, 0) //普通的fork()

    建立執行緒:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0) //共享地址空間、檔案系統資源、檔案描述符和訊號處理程式

  2. fork出的子程序執行完後通過exit()系統呼叫退出執行,這個函式會終結程序並將其佔用的資源釋放掉。父程序可以通過wait4()系統呼叫查詢子程序是否終結,這其實使得程序擁有了等待特定程序執行完畢的能力。子程序退出執行後被設定為殭屍程序

    狀態,直到他的父程序呼叫wait()或waitpid()為止。

  3. TASK_RUNNING

    TASK_INTERRUPTIBLE

    TASK_UNINTERRUPTIBLE 執行ps(1)命令時所看到的標為D狀態而又不能被殺死的程序,因為其無法響應SIGKILL訊號

    __TASK_TRACED

    __TASK_STOPPED

  4. 偽喚醒:處在等待佇列中的程序被訊號喚醒,發現並不是自己所等待事件所發出的訊號,因此不滿足跳出條件,繼續阻塞。

  5. 使用者搶佔發生在(檢查need_resched):

    1 從核心返回使用者空間

    2 從中斷處理程式返回使用者空間

  6. linux支援核心搶佔(沒有持有鎖的核心程式碼可以搶佔也可以被其他程式碼搶佔)

  7. 核心搶佔發生在(檢查need_resched):

    1 中斷處理程式正在執行,且返回核心空間之前

    2 核心程式碼再次具有可搶佔性的時候(preempt_count變為0)

    3 核心中的任務顯式地呼叫schedule()

    4 核心中的任務阻塞(導致呼叫schedule())

  8. 上述程序排程涉及程序都屬於非實時排程策略(SCHED_NORMAL),實時排程策略有SCHED_FIFO和SCHED_RR(都是靜態優先順序)。FIFO沒有時間片,RR有時間片,二者都是高優先順序隨時搶佔低優先順序。linux實時排程只滿足軟實時(盡力使程序在限定時間前排程執行,但不嚴格保證)。

  9. SYSCALL_DEFINE0(getpid) {
    // return current->tgid;
    }

    tgid即執行緒組ID。對於普通程序來說,TGID和PID相等。對於執行緒來說,同一執行緒組內的所有執行緒其TGID都相等。這使得執行緒呼叫getpid()能返回相同的PID。

  10. 程序切換:

    1 切換地址空間

    2 切換上下文環境(暫存器堆、暫存器變數)

關於系統呼叫
  1. 核心在執行系統呼叫的時候處於程序上下文。在程序上下文中,核心可以休眠並且可以被搶佔。因此應該保證系統呼叫是可重入的。

  2. 對於大多數體系結構來說,系統呼叫表位於entry.S檔案中。

  3. x86中通過eax等暫存器傳遞系統呼叫引數和返回值。

關於記憶體管理
  1. 真實模式——保護模式

  2. 核心棧的大小是固定的,其準確大小隨cpu體系結構而變。在x86上,棧的大小在編譯時配置,從歷史上說,核心棧的大小是兩頁,即32位機的核心棧是8KB,而64位機是16KB,這是固定不變的。每個程序都有一個自己獨佔的核心棧(兩頁連續且不可換出的核心記憶體)。

  3. 每個中斷處理程式有自己獨佔的中斷棧,大小為1頁。

  4. Linux記憶體分割槽種類:

· ZONE_DMA 這個區包含的頁能用來執行DMA(直接記憶體訪問)操作

· ZONE_DMA32 同上,只能被32位裝置訪問

· ZONE_NORMAL 這個區包含的都是能正常對映的頁

· ZONE_HIGHMEM 這個區包含“高階記憶體”,其中的頁不能永久地對映到核心地址空間

  1. kmalloc()分配連續的物理頁,vmalloc()分配邏輯地址連續的記憶體(有一定效能損耗,一般用來申請大空間)。

  2. slab層負責記憶體緊缺情況下所有底層的對齊、著色、分配、釋放和回收等。如果要頻繁建立和撤銷很多大的資料結構,那麼考慮建立slab快取記憶體。slab層為每個處理器維護一個物件高階快取(空閒連結串列),當需要一個新的資料結構時,slab無須另外分配記憶體,而是直接從空閒連結串列上摘取一個。

關於中斷
  1. 當硬體裝置想和系統通訊的時候,它首先要傳送一個非同步的訊號打斷處理器的執行,繼而打斷核心的執行。中斷通常對應著一箇中斷號,核心通過這個中斷號查詢對應的中斷服務程式,並呼叫這個程式響應和處理中斷。

  2. 異常也叫同步中斷(如程式執行錯誤、執行系統呼叫、缺頁異常);中斷是由硬體產生的,是非同步的。

  3. 中斷上下文不可被搶佔(打斷)。中斷處理程式不能被重新排程。

  4. 中斷處理程式一般是管理硬體的驅動程式的組成部分,分為上半部(top half)和下半部(bottom halves)。上半部也被單獨稱為中斷處理程式,上半部在執行時會遮蔽當前中斷線(IRQF_DISABLED甚至會遮蔽當前處理器的所有中斷線),因此為了提高處理器響應中斷的效能,需要使上半部儘可能簡潔。

  5. 多個裝置(中斷處理程式)通過設定為IRQF_SHARED可以共享同一個中斷線(中斷號)。

  6. Linux中的中斷處理程式是無須重入的。當一個給定的中斷處理程式正在執行時,相應的中斷線在所有的處理器上都會被遮蔽掉,以防止在同一中斷線上接收另一個新的中斷。通常情況下,所有其他的中斷都是開啟的。因此不存在巢狀的中斷。

  7. 核心接收一箇中斷後,它將依次呼叫該中斷線上註冊的每一個處理函式。因此,一個處理程式必須知道它是否應該為這個中斷負責(這需要硬體的支援)。

  8. unsigned long全域性變數jiffies用來記錄自系統啟動以來產生的節拍的總數。32位jiffies在100HZ下497天后會溢位,1000HZ下49.7天后溢位。(64位就別指望會看到他溢位了QAQ)

關於鎖機制
  1. 自旋鎖spin lock: 只能被最多一個可執行執行緒持有,如果另一個執行執行緒試圖獲得一個被已經持有(即所謂的爭用)的自旋鎖,那麼該執行緒就會一直進行忙迴圈——旋轉——等待鎖重新可用。若鎖未被爭用,請求鎖的執行緒便能立刻得到它,繼續執行。因此自旋鎖不應該被長時間持有,實際上這正是自旋鎖設計的初衷:在短期內進行輕量級加鎖。(相比於將等待執行緒阻塞的鎖方式,自旋鎖的持有時間就等價於系統的排程等待時間)

  2. 自旋鎖可以用在中斷處理程式中,由於中斷處理程式不能睡眠所以不能用訊號量。中斷處理程式在加自旋鎖前,需要先禁止本地中斷(本處理器的中斷),否則中斷處理程式就會打斷正持有鎖的核心程式碼,有可能會試圖去爭用這個已經被持有的自旋鎖。

  3. 讀-寫自旋鎖(共享(併發)-排斥)。缺點:容易造成寫者飢餓。

  4. 互斥(二值)訊號量:計數等於1的訊號量。讀寫訊號量rw_semaphore是互斥訊號量,引用計數為1。

  5. 互斥體mutex是更高效的互斥訊號量。

  6. 當子程序執行或者退出時,vfork()系統呼叫使用完成變數completion variable喚醒父程序。

  7. linux系統啟動時間計時器jiffies使用順序鎖(seq鎖)。

檔案系統
  1. VFS中四個主要的物件型別:

    · 超級塊super_block:儲存特定檔案系統的資訊,通常對應於存放在磁碟特定扇區中的檔案系統超級塊或檔案系統控制塊。對於非基於磁碟的檔案系統(如基於記憶體的檔案系統sysfs),它們會在使用現場建立超級塊並將其儲存在記憶體中。

    · 索引節點inode:inode包含了核心在操作檔案或目錄時需要的全部資訊。對於Unix風格的檔案系統來說,這些資訊可以從磁碟索引節點直接引入。

    · 目錄項dentry:VFS把目錄當作檔案對待。每個dentry代表路徑中的一個特定部分。在路徑中(包括普通檔案在內),每一個部分都是目錄項物件。目錄項引用計數d_count(記錄是否開啟)。

    · 檔案file:表示程序開啟的檔案。open, read, write, close.

  2. 給定路徑開啟檔案:VFS會先在目錄項dcache快取中搜索路徑名,若沒找到,VFS會遍歷檔案系統為每個路徑分量解析路徑,解析完畢後,再將目錄項物件加入dcache中,以便以後可以快速查詢到它(時間區域性性)。dcache在一定意義上也提供對索引節點的快取icache(空間區域性性)。

疑問
  1. 為什麼將nice值對映到時間片,會使得程序切換不優?

    將nice值對映到時間片,意味著程序切換會處在一個相對(完全公平排程演算法CFS)固定的頻率,對於CPU密集型的程序,其所得時間片不夠,對於IO密集型程序,其所得時間片太多。

  2. 為什麼鎖粒度過細也會增大系統開銷?

    鎖粒度過細會產生很多額外的資料結構,導致降低slab的效能,增大系統開銷。