聊聊運維應該瞭解的一些內核知識

前言

本文主要是《Linux內核設計與實現》這本書的讀書筆記,這本書我讀了不下十遍,但依然感覺囫圇吞棗。我結合自己的理解,從這本書中整理出了一些運維應該瞭解的內核知識,希望對大家能夠有所幫助。另外,推薦大家讀下這邊書,這本書主要講內核設計、實現原理和方法,有利於理解內核的一些機理。

目錄

  1. 運維爲什麼要了解內核

  2. 進程

  3. 系統調用

  4. 中斷

  5. 內核同步

  6. 定時器和時間管理

  7. 內存分配

  8. 虛擬文件系統

  9. 塊I/O層

  10. I/O算法

  11. 頁高速緩存和頁回寫

  12. 關於內核的幾個概念

一、運維爲什麼要了解內核

運維爲什麼要了解內核

大神Linus說了解內核的方法就是閱讀源碼(*Read The Fucking Source Code*),但是linux內核學習曲線公認的陡峭,對於運維來說難度非常大,而且現代Linux已經非常龐大,別說運維了,就是專門從事Linux內核開發的人,也不可能瞭解到內核的全部代碼。

但是運維應該瞭解內核的工作原理,設計哲學,瞭解CPU、網絡的調度方法,瞭解內存、文件系統的結構。

瞭解了Linux系統如何工作,我們才能更好的使用它,讓它爲我們服務。

Linux的由來

內核爲什麼吸引人,很重要的一個原因是自由精神,可以隨手拿到源碼,只有願意,可以瞭解到每個功能非常細微的地方。

Linux內核是如何來的,1991年,芬蘭的大學生Linus熱衷於使用Minix,一種教學用的Unix系統,但是他不能隨意修改和發佈該系統的源代碼,這令他對這個系統的設計理念感到失望,於是就自己在386上設計了一款系統,併發布到了互聯網上,很快就流行了起來。

順便說下,Linux的吉祥物爲什麼是企鵝,那是因爲Linus小的時候,被一隻企鵝咬過,令他印象深刻。關於Linus還有一本書,叫做《只是爲了好玩--linux之父林納斯自傳》,大家有興趣可以閱讀下。

我這裏有一些數據,來自2017年度Linux內核開發者報告,通過這些數字,大家對目前的內核生態會有簡單的瞭解。

目前,已有超過1400家公司的15600名開發人員參與了Linux內核的開發。僅就2016年到2017年,超過500家公司的4300多名開發人員對內核做出了貢獻;其中有1670個開發者是第一次貢獻,約佔貢獻者的三分之一。

2017年度,贊助Linux內核開發的十大組織包括英特爾、Red Hat、Linaro、IBM、三星、SUSE、谷歌、AMD、Renesas和Mellanox。

Linux開發的速度繼續增加,參與開發的人員和公司的數量也在不斷增加。內核每小時的平均變化量爲8.5,比2016年報告中的7.8個變化顯著增加,這意味着每天有204個變化,每週超過1400個變化。

從2016年的66天開始,平均每個版本的開發天數從去年的66天增加到67.66天,每一個版本的間隔時間分別爲63或70天,提供了顯著的可預測性。4.9和4.12開發週期的特點是,在內核項目歷史上看到的最高補丁率。

未領取薪酬的開發者可能正在趨於穩定,這些開發者貢獻了8.2%的貢獻,比去年的7.7%有所增加。這一數字仍遠低於2014年的11.8%。這可能是由於內核開發人員短缺,導致那些有能力提交一定質量補丁的人,在找到工作時沒有困難。

新加入內核開發的前三名是英特爾、谷歌、華爲,其中華爲投入33名工程師。

Linux內核的設計哲學

Linux內核設計參考了Unix,並且兼容Unix API,但是Linux內核吸收了Unix系統的優點,摒棄了一些缺點。

先來了解一個概念,單內核和微內核。

  • 單內核是整體單獨的一個過程,存儲方式往往也是一個大的二進制文件,使用的也是連續的一整塊內存。所有服務都運行在內核態,內核之間的通信就很容易,內核可以直接調用函數。

  • 微內核是按照功能劃分爲多個獨立過程,這個過程叫做服務器,只有少數特權服務的服務器才運行在特權模式下,大部分服務運行在用戶空間。大部分服務都使用自己的內存地址,不可能像單內核那樣直接調用函數,而是要通過消息傳遞,系統採用進程間通訊的機制,專業術語叫IPC機制。這樣的好處是一項服務失效,並不會影響到其他服務,因爲彼此隔離。

因爲IPC機制的開銷多用於函數調用,有大量的內核空間和用戶空間的上下文切換,因此,消息傳遞需要一定的週期,而單內核就沒有這個問題。

這樣還造成一個結果,就是實際上,微內核爲了提高效率,會讓大部分服務位於內核態。

Windows NT內核系統,包括Windows7 Windows10 Windows Server系列,MacOS都是典型的微內核系統。

前段時間,華爲推出的鴻蒙系統,也宣稱是微內核系統。

Linux系統是單內核系統,也就是說Linux系統運行在單獨的內核地址空間上,不過Linux吸取了微內核的精華,引入了模塊化設計,搶佔式內核,支持內核線程,及動態裝載內核的能力。同時還避免了微內核設計上的性能損失。

可見Linux的設計哲學是實用主義優先。

再解釋下什麼是內核搶佔,搶佔指的是內核具有允許在內核運行的任務優先執行的能力,大部分Unix系統是不支持這個能力的。

再來介紹下內核的版本,內核有兩種版本,穩定版和開發版,穩定版有工業級的強度,可以廣泛部署,開發版主要用於實現新的功能。

Linux內核通過簡單的命名機制區分穩定版和開發版,使用3個或者4個點分隔數字,代表不同的版本,第一個數字是主版本號,第二個數字是從版本號,第三個數字是修改版本號,第四個數字是可選,是穩定版本號。從第二個數字可以看出是穩定版還是開發版,如果是偶數就是穩定版,如果是奇數就是開發版。

比如內核版本2.6.26.1就是穩定版,因爲它的第二個數字是6,是偶數。內核版本4.9就是開發版,因爲9是奇數。

二、進程

先來聊聊Linux內核開發,內核開發和普通應用開發有兩個地方不一樣:

  • 自己要管理內存,普通應用跑在內核之上,內核可以幫你管理內存,但是你自己就是內核,你必須自己做好內核管理,要不很容易就內核溢出了。

  • 沒有庫文件,普通應用程序有很多庫文件可以調用,內核開發則沒有,內核開發就是標準的C。

由此看見,做內核開發還是要對內核有深刻的理解纔可以,請注意,這裏的內核開發指的是內核核心功能的開發。

我們再來看看進程,進程簡單的講,就是運行中的程序,我個人理解,進程是一種生命形式,就像一個人的生命,從呱呱墜地開始一直到生命的終結,中間需要不停的從周圍的環境吸收資源,並且對環境也施加影響。

進程需要的資源就是CPU、內存、文件、網絡等資源,進程雖然是從程序文件開始,但是不等於程序文件,一個程序文件可以啓動多個進程,一個進程也可能是由多個程序文件產生的,所以進程是一種運行中的狀態。

內核用一個雙向循環鏈表來描述進程的狀態,這一鏈表在32位的機器上是1.7KB大小,鏈表中的每一項都是類型爲task_struct,稱爲進程描述符的結構。進程描述符就不詳細介紹了。

下面我們來看看進程的狀態標誌,進程有5種狀態標誌:

  • 第一 task_running 運行,進程正在運行,或者正在隊列中等待運行,運行的進程可以在用戶空間,也可以在內核中。

  • 第二 task_interruptible 可中斷,或者被阻塞,等待某些條件,一旦達到條件就被喚醒,然後進入運行狀態。

  • 第三 task_uninterruptible 不可中斷,這種狀態,即使收到外部的信號,也不會被喚醒,這種狀態一般用的比較少。

  • 第四 task_traced 被其他進程跟蹤,例如通過ptrace對進程進行跟蹤調試。

  • 第五 task_stoped 停止,進程沒有運行也不能運行的狀態。

下面在介紹幾個概念

第一個概念,進程上下文

進程從可執行文件載入進程的內存地址空間運行,一般是在用戶空間,當進程調用了系統接口,或者觸發了某種異常,它就進入了內核空間,此時,我們稱內核代表進程執行,並處於進程上下文中,總結下,就是內核和進程交互的時候,就是上下文狀態,請注意,後面我們還會介紹中斷的上下文,和進程的上下文是有區別的,中斷上下文中,系統不代表進程執行,而是執行一箇中斷程序。

第二個概念,進程家族樹

Linux系統中,所有的進程都是PID爲1的init進程的後代,內核在系統啓動的最後階段啓動init進程,該進程讀取系統的初始化腳本,並執行其他的相關程序,最終完成系統的啓動。

系統中的每個進程必有一個父進程,每個進程也會擁有靈感或者多個子進程,擁有同一父進程的所有進程被稱爲兄弟進程,進程間的關係也保存在前面提到的進程描述符中。

第三個概念,寫時拷貝

Linux系統創建新的進程的時候,使用的是寫時拷貝的技術,這樣的好處是可以推遲甚至免除數據拷貝,子進程共享父進程的資源,只有當需要寫入的時候

我們通過幾個概念的解釋,來清晰化下內核對進程的調度

第一 什麼是進程調度

進程調度就是決定進程什麼時候運行,可以運行多長時間,進程調度程序的使命就是儘可能的讓進程多運行,提高效率。

第二 什麼是多任務

多任務就是能夠併發的執行多個進程,在單處理器上,這是一個假象,其實就是多個進程快速的在處理器上快速切進切出。

第三 什麼是搶佔式內核

多任務系統可以劃分兩類,非搶佔式多任務和搶佔式多任務,搶佔式多任務就是由內核決定什麼時候停止進程的運行,這個強制的動作就叫搶佔。相反,除非進程主動停止,否則就一直運行,就是非搶佔式多任務,顯然,非搶佔式多任務要依靠進程的自覺和良好設計,很古老的Windows3.1就是這樣的系統,我大概是20年前接觸到的,1996年的時候,這樣的系統一個特點就是容易死機,但是當時看慣了黑黑屏幕的dos,看到窗口式的Windows,給人還是非常震撼的感覺。

第四 時間片

進程被搶佔之前的時間是預先設置好的,有一個專門的名字,就是進程的時間片,調度策略必須規定一個默認的時間片,這裏需要平衡,時間片太長影響系統的交互體驗,時間片太短,會增加進程切換的頻率,引起過多處理器消耗。

許多操作系統有默認的時間片長度,比如10ms,但是linux沒有默認的時間片長度,Linux按照比例來劃分,這樣負載大的進程獲得的處理器使用時間就更長。

第五 Linux的調度算法

在2.4內核以前,Linux內核調度很簡陋,2.5內核中引入了Q(1)的調度程序,可以完美支持幾十個處理器的進程調度,但是Q(1)算法對對時間敏感的程序有一些先天不足,因此Q(1)適合服務器,但是不適合桌面系統。

2.6內核中,引入了完全公平算法,簡稱是CFS,目前Linux系統默認使用的都是CFS算法。

第六 IO消耗型和處理器消耗型的進程

IO消耗型的進程總在等待IO請求,佔有處理器時間比較少,大部分用戶圖形界面程序都是IO消耗型。相反,如果處理器消耗型進程,就是把時間大多用於代碼執行上,IO請求比較少。

當然也有即是IO消耗型也是處理器消耗型的進程,比如字處理程序,大部分時間是IO消耗型,但是當執行拼寫檢查的時候,就是處理器消耗型。

進程調度策略經常要在進程響應速度和最大系統利用率之間找平衡,這個背後是複雜的算法,不同操作系統的傾向性也不一樣,Linux系統傾向io消耗型,這樣響應速度快,用戶體驗好。

第七 進程優先級

Linux採用兩種不同的優先級範圍:

  • 第一種是nice值,範圍是-20到+19,默認是0,越低的nice值,可以獲得更多的處理器時間。

  • 第二種是實時優先級,變化範圍是0到99,和nice值相反,越高的值,優先級越高。

兩種優先級劃分有什麼區別,任何實時進程優先級高於普通進程,就是說兩種優先級處於互不交互的兩個範疇。

進一步說明下, 進程分爲普通進程和實時進程,普通進程使用CFS算法調度,優先級按照nice值區分。

實時進程有兩種調度算法,FIFO,即先進先出,這種進程一直佔用處理器,直到自己受阻塞或者釋放處理器,如果有多個FIFO優先級進程,則會輪流執行。

另外一種實時進程調度算法是RR,RR進程是按照時間片分配的,優先級範圍就是0到99 。

注意,再強調下,實時進程總會搶佔普通進程。

三、系統調用

用戶空間進程不是和硬件設備直接通訊的,而是有一箇中間層,這樣做的好處有三個:

  • 第一, 爲用戶空間提供了一種硬件的抽象接口,這樣用戶空間進程就不用關心具體的硬件信息。

  • 第二,限制了用戶空間進程的行爲,防止對其他進程造成影響,保證了系統的穩定和安全。

  • 第三,隔離進程使用的資源, 方便內核調度。

一般的進程調用是通過API實現的,不是直接調用內核,API有一套標準,叫POSIX,Unix,Linux,甚至Windows都支持POSIX,只是大家支持的程度不一樣。

具體內核的API如何實現,這個要依靠Linux內核程序員,關於系統調用,運維瞭解到這些知識就可以了。

四、中斷

還是通過幾個概念來了解中斷。

第一 什麼是中斷

中斷就是鍵盤、鼠標、硬盤、顯卡、網卡等硬件和處理器的通訊。

大部分硬件的運行速度和處理器比起來低很多,硬件要和處理器通訊,有兩種方式,一種方式是處理器輪詢各個硬件,一種方式是硬件主動來找處理器,實際上是硬件給處理器主動上報,因爲這種方式效率更高,硬件在需要的時候給處理器發出信息,處理器來響應,這個就是中斷處理。

中斷信息實際就是電信號,硬件,比如鍵盤控制器,在你敲擊鍵盤的時候會發出中斷,信號進入中斷控制器,然後進入處理器,處理器再通知操作系統。

第二 IRQ

不同的設備對應的中斷不同,每個中斷都有唯一的數字標誌,這樣系統就能區分具體的設備,這些中斷值被稱作IRQ,中文的意思就是中斷請求線。

比如,在經典的PC機上,IRQ0是時鐘中端,IRQ1是鍵盤中斷,但是這樣也有問題,設備越來越多,原來的設計,中斷號有限,經常會引起衝突,我記00年初,剛有聲卡的時候,經常聲卡因爲中斷衝突而不能使用,解決方法就是更換一個PCI插槽。

所以後來就有了動態分配中斷值的方法,PCI設備都是動態分配中斷號的,最終的目標關鍵是硬件能和處理器通訊,能夠引起處理器注意。

第三 異常

異常簡單的說,就是程序出錯,需要內核來處理的時候,通常由於編程失誤而導致的錯誤指令,比如被0除,或者是在執行期間出現特殊情況,比如缺頁。這時候就需要內核來處理,因爲處理器體系結構處理異常與處理中斷方式類似,因此,內核對他們的處理也很類似,實際上,異常也常常被稱爲同步中斷。

第四 中斷處理程序

在響應一個特定的中斷的時候,內核會執行一個函數,這個函數就是中斷處理程序interrput handler ,或者中斷服務例程interrupt service routine,簡稱ISR。產生中斷的每個設備都一個相應的中斷處理程序。

第五 中斷的上半部和下半部

又想中斷處理程序運行的快,又想中斷處理程序完成的工作量多,這是矛盾的,爲了解決這個矛盾,我們把中斷處理切爲兩個部分,中斷處理程序是上半部top half,接收到中斷,立即開始執行,但只做嚴格時限的工作,例如對接收的中斷進行應答和復位硬件,這些工作都是在所有中斷被禁止的情況下完成的,所以必須儘可能快的完成。能夠被允許稍後完成的工作推遲到下半部,bottom half.

用網卡做一個例子解釋下,當網卡接收到網絡的數據包的時候,需要通知內核數據包到了,網卡需要立即完成這件事,從而優化網絡的吞吐量和傳輸週期,以避免超時。這時候中斷開始執行,通知硬件,拷貝最新的網絡數據包到內存,然後讀取網卡更多的數據包,這些都是重要、緊張而又與硬件相關的工作。

內核需要快速拷貝網絡數據包到內存,因爲網卡的緩存的大小是固定的,如果速度不夠快,就會造成溢出,網卡就會丟棄數據包。

當數據拷貝到內存,中斷的任務就完成了,它將控制權交還給系統系統中斷前運行的程序,數據處理在隨後的下半部進行。

第六 中斷上下文

當執行一箇中斷處理程序的時候,內核處於中斷上下問interrput context,我們回憶下前面提到的進程上下文,進程上下文是內核所處的操作模式,此時內核代表進程執行。

與進程上下文相反,中斷上下文和進程沒有關係,因爲沒有後備進程,所以中斷上下文不可以睡眠,中斷上下文有嚴格的時間限制,因爲它打短了其他代碼,

在Linux系統中,查看中斷的情況,可以使用命令,可以看出詳細的中斷情況:

cat /proc/interrputs

中斷上半部處理需要緊急處理的任務,包括對時間敏感,和硬件息息相關,不希望被其他中斷打斷的任務,其他不緊急的任務,都交給下半部處理。

通常我們希望儘可能的將任務交給中斷下半部處理,因爲上半部處理的時候,會造成其他中斷被屏蔽,那麼下半部是如何處理的呢,有三種方法。

  • 第一種方法, BH,即bottom half,這是最早的中斷處理機制,也是早期的唯一方法,同時只能有一個BH處理,即使有多個處理器。從內核2.5 版本開始,BH方法已經被放棄。

  • 第二種方法,任務隊列,爲了充分使用多處理性能,內核開發者引入了任務隊列的機制,task queue,內核定義了一組隊列,驅動程序來和隊列匹配,任務隊列的方案在處理性能要求比較高的子系統,比如網絡部分,也不能勝任。

  • 第三種方法,軟中斷和tasklet,這種方法是在內核2.3版本中引入的,軟中斷可以在所有處理器上同時執行,tasklet是一種基於軟中斷實現的靈活性強、動態創建的下半部實現機制,兩個不同類型的tasklet可以同時在不同的處理器上執行,但是類型相同的tasklet不能同時執行,tasklet是性能和易用性之間平衡的產物,可以處理大部分下半部中斷處理。像網絡這樣對性能要求比較高的情況,才需要使用軟中斷。

五、內核同步

我們還是通過幾個概念來了解下什麼是內核同步。

第一個概念 爲什麼會有內核同步問題

在使用共享內存的應用程序中,程序員必須特別留意保護共享資源,防止共享資源併發訪問,防止多個線程同時訪問和操作數據,造成數據互相覆蓋,和數據不一致。

在單一處理器的時候,這個還好辦,只有在中斷髮生,或者重新調度另一個任務的時候,數據纔可能被併發訪問。

到了多處理的時代,問題變的複雜,多處理器意味者着內核代碼可以同時在兩個或者兩個以上的處理器上運行,爲了防止同時改寫內存數據的情況發生,就必須引入內核同步機制。

第二個概念 臨界區和競爭條件

臨界區是指訪問和操作共享數據的代碼段,多個執行線程併發訪問同一個資源通常是不安全的,爲了避免在臨界區中併發訪問,編程者必須保證這些代碼是原子的執行,也就是說,操作在執行結束前不可被打斷,就如同整個臨界區是一個不可分割的指令一樣。如果兩個執行線程有可能處於同一個臨界區中同時執行,那麼就是程序包含的bug。如果這種情況確實發生了,我們就稱它爲競爭條件,這種情況出現的機會非常小,就是因爲競爭引起的錯誤非常不容易重現,所以調試這種錯誤纔會非常困難,避免併發和防止競爭條件稱爲同步。

第三個概念,加鎖

爲了防止一個處理器的進程在處理數據,而另外一個處理器上的進程也同時修改這些數據,就需要給這塊數據加鎖,確保同時只能有一個進程訪問數據。

加鎖也是技術活,鎖有多種多樣的形式,加鎖的粒度和範圍也各不相同。

第四個概念 僞併發和真併發

在單處理器上,用戶進程可能在任何時刻被搶佔,也可能造成共享內存被修改,兩個進程是交叉進行的,所以被稱爲僞併發。

在多處理器上,有可能真的兩個進程在同時訪問共享內存,因此被稱爲真併發。

內核中有以下類似的可能,造成併發執行,他們是:

  • 中斷,中斷可能隨時打斷正在執行的代碼。

  • 軟中斷和tasklet,內核能在任何時刻喚醒或者調度軟中斷和tasklet,打斷當前正在執行的代碼。

  • 內核搶佔,因爲內核具有搶佔性,內核中的任務可能會被另一任務搶佔。

  • 睡眠及用戶空間的同步,在內核執行的進程可能睡眠,這就會喚醒調度程序執行另外一個進程。

  • 對稱多處理,兩個或者多個處理器同時執行代碼。

第五個概念,死鎖

死鎖的產生需要一定條件,要有一個或多個執行線程和一個或者多個資源,每個線程都在等待其中的一個資源,但所有的資源都被佔用了。所有線程都在等待,但他們永遠不會釋放已經佔有的資源,於是所有線程都無法繼續,這便意味着死鎖的發生。如何防止死鎖的發生,也是程序設計的時候要考慮的問題。

第六個概念 爭用和擴展性

一個資源被鎖定,多個進程都在競爭這個資源,被稱爲鎖的爭用,鎖的爭用會造成系統瓶頸,嚴重降低系統性能。

解決辦法就是擴展性,將鎖的範圍儘量精細,這樣就可以減少鎖的爭用,但是過於精細,也會額外消耗系統資源,所以掌握好平衡就需要技巧。

六、定時器和時間管理

時間管理在內核中佔有非常重要的位置,內核中的函數驅動方式,可以分爲事件驅動和時間驅動,其實時間驅動也可以認爲是特殊的事件驅動,但是內核中,時間驅動的頻率特別高。

時間驅動也可以分爲週期驅動,比如每秒100次,或者推後執行,比如500ms以後執行某個任務。

另外,內核還必須管理系統的運行時間以及當前日期和時間。

這裏還有一個概念,相對時間和絕對時間,如果某個事件在5s之後被執行,那麼系統需要的是相對時間,相反,如果要求管理當前日期和當前時間,則內核不但要計算流逝的時間而且還要計算絕對時間。

週期性產生的事件,比如每10ms一次,都是由系統定時器產生的,系統定時器是一種硬件可編程芯片,可以固定頻率產生中斷,這個中斷就是定時器中斷.

在x86體系中,系統定時器默認頻率是100Hz,也就是說i386處理器上每秒中斷100次,即10ms一次,注意,每種體系的頻率可能不一樣,有的是250,有的是1000。頻率可以在編譯內核時指定。

從2.5內核版本開始,中斷頻率被設定爲1000Hz,使用高頻率的好處是準確度,精確性更高,但是同時系統負擔更重,也更耗電,但是處理器性能越來越高,這點消耗不會對系統造成過大的影響。

七、內存分配

內核把物理頁作爲內存管理的基本單元,儘管處理器的最小可尋址單元通常爲字(甚至字節),但是,內存管理單元MMU通常以頁爲單位進行處理。

體系不同,頁的大小也不一樣,大部分32位體系結構支持4KB的頁,64位體系結構一般支持8KB的頁,這意味着,在1GB物理內存的機器上,4KB頁大小,物理內存會被劃分爲262144個頁。

由於硬件的限制,內核並不能對所有的頁一視同仁,有些頁位於特定的物理地址上,所以不能將其用於特定的任務,由於存在這種限制,所以內核把頁劃分爲不同的區zone。

Linux必須處理如下兩種由於硬件存在缺陷引起的尋址問題:

  • 一些硬件只能用某些特定的內存地址來執行DMA,即直接內存訪問。

  • 一些體系結構的內存物理尋址範圍比虛擬尋址範圍大得多,這樣,就有一些內存不能永久地映射到內核空間上。

因爲存在這些限制條件,Linux主要使用了四種區:

  • ZONE_DMA 這個區包含的頁用來執行DMA操作

  • ZONE_DMA32 和ZONE_DMA相似,但是這個區只能被32位設備訪問

  • ZONE_NORMAL 這個區包含的都是能正常映射的頁

  • ZONE_HIGHEM 這個區包含高端內存,其中的頁不能永久映射到內核地址空間。

一般DMA區使用0-16MB的內存,NORMAL區使用16-896MB的內存,HIGHEM區使用896MB以上的內存。

八、虛擬文件系統

虛擬文件系統作爲內核的子系統,簡稱VFS,爲用戶空間程序提供了文件和文件系統相關的接口,系統中的所有文件系統不但依賴VFS共存,並且依靠VFS協同工作。

VFS提供了通用的接口和方法,比如open(),read(),write(),系統調用的無需考慮具體文件系統和實際物理介質。

之所以可以這樣,是因爲內核在底層文件系統接口上建立一個抽象層,抽象層使Linux能夠支持各種文件系統。VFS抽象層定義了所有文件系統都支持的、基本的、概念上的接口和數據結構。任何新的文件系統和新介質只要符合VFS規範,都可以直接使用。

unix系統使用四種和文件系統相關的傳統抽象概念:文件、目錄項、索引節點和掛載點。從本質上講文件系統是特殊的數據分層存儲結構,包含文件、目錄和相關的控制信息。

VFS採用面向對象的設計思路,使用一組數據結構來代表通用文件對象。

VFS有四個主要的對象類型:

  • 超級塊對象,代表一個具體的已安裝文件系統;

  • 索引節點對象,代表一個具體文件;

  • 目錄項對象,代表一個目錄項,是路徑的一個組成部分;

  • 文件對象,代表由進程打開的文件。

另外,說明下,因爲VFS將目錄作爲文件來處理,所以不存在目錄對象。

我們總結下,Linux支持了多種類型的文件系統,從本地文件系統,例如ext3,ext4,到網絡文件系統比如NFS。LInux在標準內核中已支持的文件系統超過60種。VFS層提供給這些不同文件系統一個統一的實現框架,而且也提供了能和標準系統調用交互工作的統一接口。由於VFS層的存在,使得Linux上實現新文件系統的工作變得簡單起來,它可以輕鬆地使這些文件系統通過標準Unix系統調用而協同工作。

九、塊I/O層

我們還是通過五個概念瞭解塊IO層

第一個概念 塊設備和字符設備。

系統能夠隨機訪問固定大小數據片的硬件設備稱爲塊設備,數據片的英文術語是chunk,硬盤、軟盤、光盤、SSD、U盤都屬於塊設備,因爲系統隨時可以訪問這些介質上的任意位置數據,另外,說明下,對這些介質的訪問,是通過訪問文件系統實現的。

字符設備是按照字符流的方式被有序訪問的設備,像串口和鍵盤就屬於字符設備。

塊設備和字符設備主要的區別就是隨機訪問方式還是順序訪問方式。

第二個概念 扇區和塊。

塊設備中最小的可尋址單元是扇區,扇區大小一般是2的整數倍,硬盤最常見的扇區大小是512字節,CD-ROM的扇區一般是2KB。

每種文件系統都有自己最小的邏輯可尋址單元,塊。塊是文件系統的抽象,只能基於塊來訪問文件系統。

扇區和塊的區別是,物理磁盤尋址是按照扇區級進行的,文件系統是按照塊來進行的。塊大小必須是扇區的倍數,一般是2的整數倍,並且不能超過一個內存頁大小,因爲文件塊需要被緩存到內存中。所以一般文件塊的大小是512字節,1KB,4KB。

另外,磁盤還有一些術語,比如簇,柱面,磁頭,請大家自己找資料看下。

第三個概念 緩衝區。

當一個塊被調入內存時,就存儲在一個緩衝區中,每個緩衝區與一個塊對應,相當於磁盤塊在內存中的表示。像前面介紹的,塊包含一個或多個扇區,但是大小不能超過一個頁面,所以一個內存頁可以容納一個或者多個內存中的塊。

第四個概念 請求隊列。

塊設備將它們掛起的塊IO請求保存在請求隊列中,請求隊列只要不爲空,隊列對應的塊設備驅動程序就會從隊列頭部獲取請求,然後將其送入對應的塊設備上去。

第五個概念 IO調度程序。

如果簡單的以內核產生請求的次序直接將請求發向塊設備的話,性能肯定讓人難以忍受,磁盤尋址是整個計算機中最慢的操作之一,每次尋址,定位磁頭到特定的塊上的某個位置,需要花費不少時間,所以儘量縮短尋址時間無疑是提高系統性能的關鍵。

爲了優化尋址操作,內核既不會簡單的按請求接收文件,也不會立刻將其提交給磁盤。相反,內核會在提交前,先執行合併與排序的操作,這種操作可以極大的提高系統性能,在內核中負責提交IO請求的子系統,稱爲IO調度程序。

IO調度程序將磁盤IO資源分配給系統中所有掛起的塊IO請求,這種資源分配是通過請求隊列中掛起的請求合併和排序來完成的。

IO調度器的工作是管理塊設備的請求隊列,它決定隊列中的請求排列順序以及在什麼時刻發送請求到塊設備,這樣做有利於減少磁盤尋址時間,從而提高全局吞吐量。注意,全局這個定語很重要,因爲IO調度器可能爲了提高系統整體性能,會對某些請求不公。

IO調度器通過兩種方法減少磁盤尋址時間,合併與排序。舉個例子,文件系統接到多個請求隊列,IO調度器可以按照磁盤扇區順序進行排序,那麼相鄰扇區的訪問就可以合併爲一次,這樣就大大減少了磁盤尋址消耗。即使沒有相鄰扇區的訪問,通過IO調度器,按照磁盤旋轉方向訪問,也縮短了所有請求的磁盤尋址時間。

十、I/O算法

第一種算法 linus電梯

在2.4版本內核中,linus是默認的IO調度程序,linus算法能夠執行合併與排序預處理,當有新的請求加入隊列時,它首先檢查其他每一個掛起的請求是否可以和新請求合併。linus電梯算法可以執行向前和向後合併,如果新的請求沒有合適的插入點,則會被放入隊列尾部。

另外,系統中如果有駐留時間過長的請求,新的請求也會被放到隊列尾部,這樣做的目的是防止對一個磁盤位置訪問的過多,造成對其他磁盤位置的請求被餓死。但是這樣的做法,因爲僅僅是改變隊列排序,沒有隊列的時間檢測,不能完全避免有隊列被餓死的情況。

第二種算法 最終期限

最終期限deadline IO調度算法是爲了解決linus電梯算法所帶來的飢餓問題而提出的。出於減少磁盤尋址時間的考慮,對某個磁盤區域的頻繁操作,會使對磁盤其他位置的操作請求餓死。

更糟糕的是,普通的請求還會造成寫-飢餓-讀這種問題。

寫請求通常可以緩存,但是讀請求的時候,程序會被阻塞,直到拿到請求的讀數據,也就是寫請求是異步的,讀請求是同步的,如果有大量的讀請求的時候,寫請求就會被餓死。

問題可能還會更嚴重,如果讀請求和寫請求是相互依靠的,寫請求沒有操作,讀操作又去請求數據,就會造成應用更長時間的等待。

最終期限算法中,每個請求都有一個超時時間,默認讀請求的超時時間是500ms,寫請求的超時時間是5s。

最終期限算法有三個隊列,在超時時間內,調度類似於linus電梯,有一個排序隊列,另外維護兩個按照時間順序的讀fifo隊列,和寫fifo隊列。

在超時時間內,按照排序隊列派發操作,如果讀寫隊列的列頭請求超時,那麼IO調度程序便從隊列中提取請求進行服務,這樣就能保證不發生磁盤操作請求超時的情況。

通過最終期限算法,可以避免寫操作餓死,同時因爲讀操作超時時間短,這種算法也優化了大量讀操作的響應。

第三種 算法 預測IO調度

預測IO調度和最終期限一樣,也是維護三個一樣的隊列,不同的是,在提交請求的之前,會有意等待一段時間,默認是6ms,如果有新的請求來,在將相鄰扇區的請求合併,這樣可以優化磁盤操作。當然,如果沒有操作請求,會浪費幾毫秒的時間。

第四種算法 完全公平的排隊IO調度CFQ

CFQ調度程序把進入IO的請求放去特定的隊列中,這種隊列請求是根據引起IO請求的進程組織的,在每個隊列中,剛進入的請求和相鄰請求進行合併。

CFQ調度程序以時間片輪轉調度隊列,從每個隊列中選取請求固定數字的操作,默認爲4,然後進入下一輪調度。這樣在進程級實現了公平。

目前內核默認的調度算法是CFQ。

第五種算法 空操作

之所以這樣命名,是因爲這種算法基本不作什麼事情,基本就是先進先出,當然,如果相鄰的操作能夠合併,還是會合並,空操作懶惰是有道理的,因爲這種算法是用在閃存設備上,如果設備沒有尋址負擔,那麼也沒有必要對其排序。

十一、頁高速緩存和頁回寫

聊聊運維應該瞭解的一些內核知識

我們還是通過解答幾個問題,來了解頁高速緩存和頁回寫。

第一個問題,爲什麼會有頁高速緩存

這個主要原因是因爲內存和磁盤的速度差距非常大,磁盤的讀寫速度是毫秒級別的,內存的讀寫速度是納秒級別的,如果能夠通過內存緩存磁盤數據,就可以大大提高系統速度。另外,被訪問的數據,很有可能再次被訪問,如果能夠把數據緩存到內存中,那麼數據如果再次被頻繁訪問,就可以提高系統性能。

第二個問題,寫磁盤如何緩存

寫緩存有三種方式,第一種是不緩存,就是當寫數據時,直接寫到磁盤,這種方式數據最安全,但是性能最低。第二種方式是透寫,數據先寫到緩存,然後立刻寫磁盤,這樣數據也是很安全,但是性能也比較低。第三種方式是回寫,writeback,數據寫到緩存,就認爲成功,到一定時間,或者數據比較多的時候,再寫盤,這種方式性能很好,但是如果數據在緩存中的時候,機器突然斷電,有可能數據丟失。

第三個問題,讀緩存的回收策略是什麼

因爲內存有限,不可能把整個磁盤的數據緩存到內存中,只能保證把比較熱的數據緩存起來,那麼如何確認數據比較熱呢,有兩種算法,一種是根據時間,系統掃描頁面,沒有被訪問的,時間比較久的頁面,就會被釋放掉,還有一種算法,是雙鏈表,或者多鏈表,增加了一些統計的概念,更精確一些。

第四個問題,筆記本電腦模式

在筆記本電腦上,因爲有電池,同時爲了提升性能,一般啓用的都是回寫模式,並且刷新磁盤的時間間隔更長,這樣還可以省電。目前的大部分系統也可以在筆記本電腦啓用電池時,自動修改回寫策略。

另外也可以執行命令sync,強制系統刷盤。一般在個人版的系統上,默認都是開啓回寫,這樣性能會好很多,但是在服務器系統上,一般默認都是透寫模式,因爲在服務器上,數據更重要。這也是爲什麼有時候你會發現,在個人PC上,磁盤寫性能居然要好於服務器的原因。

十二、關於內核的幾個概念

第一個概念,Linux的設備類型

在Linux及Unix中,設備被分爲三種類型:

  • 塊設備

  • 字符設備

  • 網絡設備

塊設備縮寫爲blkdev,塊設備以塊爲單位,並且是可以尋址的,即可以隨機訪問任何位置的數據。塊設備通常被掛載爲文件系統來使用。

字符設備縮寫爲cdev,字符設備不可尋址,只能流式訪問,與塊設備不同,應用程序通常直接和塊設備交互。

網絡設備通常是通過物理設備和IP協議提供的,網絡設備打破了unix一切皆文件的設計原則,對網絡設備的訪問是通過套接字API實現的。

Linux還提供了其他設備類型,但都是針對單個任務,而非通用的。

另外,並不是所有設備驅動都表示物理設備,有些設備驅動是虛擬的,稱之爲“僞設備”,最常見的是內核隨機數發生器/dev/urandom,空設備/dev/null,零設備/dev/zero等。

儘快Linux內核是單塊內核的操作系統,但是整個內核是模塊化的,允許在運行時動態的插入或者刪除代碼,即所謂的可裝載內核模塊。

第二個概念,內核的可移植性

Linux是可移植性非常好的操作系統,支持許多不同體系的計算機。可移植性是指操作系統代碼從一套體系遷移到另外一套體系的方便程度。

在操作系統可移植性方面,設計有兩種思路。

  • 一種思路是儘量追求通用性,儘量少的使用彙編語言,這樣設計出來的操作系統可移植性非常高,但是缺點是不能針對某種體系深入優化。

  • 還有一種思路就是基本不考慮可移植性,只對一種體系深度優化,Windows系統就是這樣的系統,主要就是針對x86系統優化,但是可移植性極差。

Linux系統走了一條中間道路,差不多所有的接口和核心代碼都是獨立於硬件的,但是,對於性能要求很嚴格的部分,內核會針對不同體系調整,這使得linux在可移植性和性能之間取得比較好的平衡。

第三個概念 社區

大家都知道,linux是開源的,社區和代碼隨時可以訪問,只要有興趣,也可以隨時參與社區活動。但是linux入門門檻比較高。需要一個比較長的過程,只要堅持,最終會跨過這個門檻。


後記

本文通過十二部分蜓蜓點水式的介紹,希望能夠幫助大家能記住並理解幾個概念。如果有興趣更深入的瞭解,推薦閱讀下《Linux內核設計與實現》這本書。收聽本文音頻版,請在“喜馬拉雅FM”上搜索“力哥聊運維與雲計算界面”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章