1. 程式人生 > >Linux核心完全註釋之概述

Linux核心完全註釋之概述

1.1 Linux的誕生與發展

Linux創始人:Linus Toravlds
Linux第一版釋出時間:1991年9月
Linux誕生髮展的五大支柱:

  1. UNIX作業系統
    Ken. Thompson和Dennis Ritchie開發的分時作業系統
  2. MINIX作業系統
    AST編寫的用於學生學習作業系統原理的作業系統
  3. GNU計劃
    GNU計劃是Richard Stallman在1983年提出的,希望發展出一套完整的開放原始碼作業系統來取代Unix,計劃中的作業系統,名為GNU(GUN’s Not Unix)。
  4. POSIX標準
    該標準是基於UNIX實踐和經驗,描述了作業系統的呼叫服務介面。用於保證編寫的應用程式在原始碼一級上在不同作業系統之間的可移植性。
  5. Internet網路

1.2 內容綜述

本書是基於Linux 0.11版本,內容主要分為三個部分:第1至4章是描述核心引導啟動和32位執行方式的準備階段;第5至10章是核心程式碼主要部分;第11至13章可以作為第二部分程式碼的參考資訊。

2.1 Linux核心模式和體系結構

作業系統核心的結構模式主要分為整體式的單核心結構和層次式的微核心結構。Linux 0.11版核心採用單核心結構,其結構緊湊,執行速度快,不足之處是層次結構性不強。

2.2 Linux中斷機制

80x86使用兩片8259A可程式設計中斷控制晶片,PC/AT系列也採用了兩片8259A,可以管理15級中斷向量。其中從晶片的INT引腳連線到主晶片的IR2引腳上。主8259A晶片的埠基地址是0x20,從晶片是0xA0。

8259A分為程式設計狀態和操作狀態,程式設計即CPU通過IN、OUT指令對晶片進行初始化程式設計,完成後晶片即進入操作狀態,當收到外部裝置的中斷請求時,首先通過中斷判優選擇其中優先順序最高的,並通過CPU引腳INT通知CPU,之後通過資料匯流排將當前服務物件的中斷號送出,CPU就獲得了中斷向量,並執行中斷服務對於LINUX中斷分為硬體中斷和軟體中斷(異常),每個中斷由0-255的一個數字標識,其中0-31屬於軟體中斷,由intel公司設定/保留。中斷int32-int47對應8259A的15個發出中斷請求訊號。

在這裡插入圖片描述

2.3 Linux系統定時

PC的可程式設計定時晶片Intel 8253被設定成每隔10毫秒就發出一個時鐘中斷,稱之為系統滴答,每一個滴答都會呼叫一次時鐘中斷處理程式,其通過jiffies變數累計自系統啟動經過的滴答數。然後從被中斷程式的段選擇符中取得當前特權級CPL作為引數呼叫do_timer()函式。do_timer()函式則根據特權級對當前程序執行時間作累計。如果CPL=0,則表示程序是執行在核心態時被中斷,因此把程序的核心執行時間統計值stime增1,否則把程序使用者態執行時間統計值增1。對於新增過定時器的程式,定時器時間到,就會呼叫該定時器的處理函式,然後對當前程序執行時間進行處理,把當前程序執行時間片減1。如果此時程序時間片大於0,推出do_timer()函式繼續執行當前程序,否則根據特權級判定操作,特權級為0,則退出do_timer(),特權級大於0,則呼叫schedule()切換到其它程序去執行。

2.4 Linux核心程序控制

Linux作業系統採用分時技術,在巨集觀上達到程序併發執行的效果,微觀上則為程序並行執行。Linux中的程序執行狀態分為核心態和使用者態,其對應的用於儲存執行引數的核心堆疊和使用者堆疊是分開的。

2.4.1 任務資料結構

核心程式通過程序表對程序進行管理,程序表項是一個task_struct(定義在/include/linux/sched.h)任務結構指標。主要包括程序當前執行的狀態資訊、程序優先順序、程序號、父程序號、執行時間累計值、正在使用的檔案和本任務的區域性描述符以及任務狀態段資訊。

CPU所有暫存器中的值、程序的狀態以及堆疊中的內容都被稱為程序上下文,儲存在程序的任務資料結構中。程序切換時首先儲存當前程序上下文,以便在再次執行該程序時,能夠恢復到切換時的狀態執行下去。

2.4.2 程序執行狀態

在這裡插入圖片描述

- 執行狀態(TASK_RUNNING):
當程序正在被 CPU 執行,或已經準備就緒隨時可由排程程式執行,則稱該程序為處於執行狀態。

- 可中斷睡眠狀態(TASK_INTERRUPTIBLE):
當程序處於可中斷等待狀態時,系統不會排程該進行執行。當系統產生一箇中斷或者釋放了程序正在等待的資源,或者程序收到一個訊號,都可以喚醒程序轉換到就緒狀態(執行狀態)。

- 不可中斷睡眠狀態(TASK_UNINTERRUPTIBLE):
與可中斷睡眠狀態類似。但處於該狀態的程序只有被使用 wake_up()函式明確喚醒時才能轉換到可執行的就緒狀態。

- 暫停狀態(TASK_STOPPED):
當程序收到訊號SIGSTOP、SIGTSTP、SIGTTIN 或SIGTTOU 時就會進入暫停狀態。可向其傳送SIGCONT 訊號讓程序轉換到可執行狀態。

- 僵死狀態(TASK_ZOMBIE):
當程序已停止執行,但其父程序還沒有詢問其狀態時,則稱該程序處於僵死狀態。

程序在時間片用完時進行程序切換,或者在核心態執行時等待某個系統資源時會從“執行態”轉移到“睡眠態”進行程序切換。只有當程序從“核心執行態”轉移到“睡眠狀態”時,核心才會進行程序切換操作。在核心態下執行的程序不能被其它程序搶佔。

2.4.3 程序初始化

在 boot/目錄中載入程式把核心從磁碟上載入到記憶體中,並讓系統進入保護模式下執行後,就開始執行系統初始化程式init/main.c。該程式首先確定如何分配使用系統實體記憶體,然後呼叫核心各部分的初始化函式分別進行初始化處理。在完成了這些操作之後,程式把自己“手工”移動到任務0(程序0)中執行,並使用fork()呼叫首次創建出程序1。在程序1 中程式將繼續進行應用環境的初始化並執行shell 登入程式。而原程序0 則會在系統空閒時被排程執行,此時任務0 僅執行pause()系統呼叫,並又會呼叫排程函式。

“移動到任務0 中執行”這個過程由巨集move_to_user_mode完成。它把main.c程式執行流從核心態(特權級0)移動到了使用者態(特權級3)的任務0 中繼續執行。巨集move_to_user_mode 使用了中斷返回指令造成特權級改變的方法。該方法的主要思想是在堆疊中構築中斷返回指令需要的內容,把返回地址的段選擇符設定成任務0 程式碼段選擇符,其特權級為3。此後執行中斷返回指令iret 時將導致系統CPU 從特權級0 跳轉到外層的特權級3 上執行。

2.4.4 建立新程序

Linux 系統中建立新程序使用fork()系統呼叫。所有程序都是通過複製程序0 而得到的,都是程序0的子程序。
建立新程序的過程:

  • 系統首先在任務陣列中找出一個還沒有被任何程序使用的空項(空槽),若沒有找到則返回錯誤。
  • 然後系統為新建程序在主記憶體區中申請一頁記憶體來存放其任務資料結構資訊,並複製當前程序任務資料結構中的所有內容作為新程序任務資料結構的模板。
  • 隨後對複製的任務資料結構進行修改。
  • 此後系統設定新任務的程式碼和資料段基址、限長並複製當前程序記憶體分頁管理的頁表。如果父程序中有檔案是開啟的,則應將對應檔案的開啟次數增1。
  • 接著在GDT 中設定新任務的TSS 和LDT 描述符項,其中基地址資訊指向新程序任務結構中的tss 和ldt。
  • 最後再將新任務設定成可執行狀態並返回新程序號。

2.4.5 程序排程

Linux 程序是搶佔式的。被搶佔的程序仍然處於TASK_RUNNING 狀態,只是暫時沒有被CPU 執行。程序的搶佔發生在程序處於使用者態執行階段,在核心態執行時是不能被搶佔的。在Linux 0.11 中採用了基於優先順序排隊的排程策略。

排程程式

schedule()函式首先掃描任務陣列。比較就緒態任務的counter 的值,值大表示執行時間還不長,則選擇並切換到該程序。若所有處於TASK_RUNNING 狀態程序的時間片都已經用完,系統會根據每個程序的優先權值priority,對所有程序(包括正在睡眠的程序)重新計算counter值。重複上述過程,直到選擇出一個程序為止,呼叫switch_to()執行程序切換操作。

程序切換

switch_to()首先檢查要切換到的程序是否就是當前程序,如果是則什麼也不做,直接退出。否則就首先把核心全域性變數current 置為新任務的指標,然後長跳轉到新任務的任務狀態段TSS 組成的地址處,造成CPU 執行任務切換操作。此時CPU 會把其所有暫存器的狀態儲存到當前任務暫存器TR 中TSS 段選擇符所指向的當前程序任務資料結構的tss 結構中,然後把新任務狀態段選擇符所指向的新任務資料結構中tss 結構中的暫存器資訊恢復到CPU 中,系統就正式開始執行新切換的任務了

2.4.6 終止程序

當一個程序結束了執行或在半途中終止了執行,那麼核心就需要釋放該程序所佔用的系統資源,包括程序執行時開啟的檔案、申請的記憶體等。

當一個使用者程式呼叫exit()系統呼叫時,就會執行核心函式do_exit():

  • 首先釋放程序程式碼段和資料段佔用的記憶體頁面,關閉程序開啟著的所有檔案,對程序使用的當前工作目錄、根目錄和執行程式的i 節點進行同步操作。
  • 如果程序有子程序,則讓init
    程序作為其所有子程序的父程序。如果程序是一個會話頭程序並且有控制終端,則釋放控制終端,並向屬於該會話的所有程序傳送結束通話訊號SIGHUP,這通常會終止該會話中的所有程序。
  • 然後把程序狀態置為僵死狀態TASK_ZOMBIE。並向其原父程序傳送SIGCHLD 訊號,通知其某個子程序已經終止。
  • 最後do_exit()呼叫排程函式去執行其它程序。