天天看點

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程序排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

文章目錄

  • Linux記憶體管理
    • 虛拟記憶體
    • 虛拟位址
    • 虛拟位址的好處
    • 程序如何使用記憶體?
    • 程序如何組織這些區域?
    • 核心空間和使用者空間
    • 程序記憶體空間
  • TCP為什麼采用随機初始序列号
    • 初始序列号是什麼?
    • TCP初始序列号為什麼是随機的
  • 擁塞控制
  • TCP與UDP的差別
    • 什麼時候選擇TCP,什麼時候選UDP?
    • HTTP可以使用UDP嗎?
    • 面向連接配接和無連接配接的差別
    • TCP如何保證傳輸的可靠性
  • Linux事件管理
  • Linux磁盤
  • Linux程序排程
    • 程序的分類
    • 排程時刻
    • 程序上下文切換
  • 檔案系統
    • 檔案系統的基本組成
    • 目錄項和目錄是一個東西嗎?
    • 那檔案資料是如何存儲在磁盤的呢?
  • 虛拟檔案系統
  • 檔案的使用
  • 檔案 I/O
    • 緩沖與非緩沖 I/O
    • 直接與非直接 I/O
      • 如果用了非直接 I/O 進行寫資料操作,核心什麼情況下才會把緩存資料寫入到磁盤?
    • 阻塞與非阻塞 I/O VS 同步與異步 I/O
  • Linux 的IO棧

接着分布式存儲面試的學習,把Linux基礎知識,内部的一些東西也都系統的學習一下

Linux記憶體管理

虛拟記憶體

也被稱作“頁面檔案”,是一種邏輯上擴充實體記憶體的技術。簡單的說就是将硬碟的一部分作為記憶體來使用。基本思想是用軟、硬體技術把記憶體與外存這兩級存儲器當做一級存儲器來用。虛拟記憶體技術的實作利用了自動覆寫和交換技術。

存在形式:PAGEFILE.SYS

虛拟位址

即使是現代作業系統中,記憶體依然是計算機中很寶貴的資源,看看你電腦幾個T固态硬碟,再看看記憶體大小就知道了。

為了充分利用和管理系統記憶體資源,Linux采用虛拟記憶體管理技術,利用虛拟記憶體技術讓每個程序都有4GB 互不幹涉的虛拟位址空間。

程序初始化配置設定和操作的都是基于這個「虛拟位址」,隻有當程序需要實際通路記憶體資源的時候才會建立虛拟位址和實體位址的映射,調入實體記憶體頁。

打個不是很恰當的比方,這個原理其實和現在的某某網盤一樣。假如你的網盤空間是1TB,真以為就一口氣給了你這麼大空間嗎?那還是太年輕,都是在你往裡面放東西的時候才給你配置設定空間,你放多少就分多少實際空間給你,但你和你朋友看起來就像大家都擁有1TB空間一樣。

虛拟位址的好處

避免使用者直接通路實體記憶體位址,防止一些破壞性操作,保護作業系統

每個程序都被配置設定了4GB的虛拟記憶體,使用者程式可使用比實際實體記憶體更大的位址空間

4GB 的程序虛拟位址空間被分成兩部分:「使用者空間」和「核心空間」

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

程序如何使用記憶體?

毫無疑問,所有程序(執行的程式)都必須占用一定數量的記憶體,它或是用來存放從磁盤載入的程式代碼,或是存放取自使用者輸入的資料等等。不過程序對這些記憶體的管理方式因記憶體用途不一而不盡相同,有些記憶體是事先靜态配置設定和統一回收的,而有些卻是按需要動态配置設定和回收的。

對任何一個普通程序來講,它都會涉及到5種不同的資料段。稍有程式設計知識的朋友都能想到這幾個資料段中包含有“程式代碼段”、“程式資料段”、“程式堆棧段”等。不錯,這幾種資料段都在其中,但除了以上幾種資料段之外,程序還另外包含兩種資料段。下面我們來簡單歸納一下程序對應的記憶體空間中所包含的5種不同的資料區。

  • 代碼段:代碼段是用來存放可執行檔案的操作指令,也就是說是它是可執行程式在記憶體中的鏡像。代碼段需要防止在運作時被非法修改,是以隻準許讀取操作,而不允許寫入(修改)操作——它是不可寫的。
  • 資料段:資料段用來存放可執行檔案中已初始化全局變量,換句話說就是存放程式靜态配置設定[1]的變量和全局變量。
  • BSS段[2]:BSS段包含了程式中未初始化的全局變量,在記憶體中 bss段全部置零。
  • 堆(heap):堆是用于存放程序運作中被動态配置設定的記憶體段,它的大小并不固定,可動态擴張或縮減。當程序調用malloc等函數配置設定記憶體時,新配置設定的記憶體就被動态添加到堆上(堆被擴張);當利用free等函數釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)
  • 棧:棧是使用者存放程式臨時建立的局部變量,也就是說我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味着在資料段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的程序棧中,并且待到調用結束後,函數的傳回值也會被存放回棧中。由于棧的先進後出特點,是以棧特别友善用來儲存/恢複調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時資料的記憶體區。

程序如何組織這些區域?

上述幾種記憶體區域中資料段、BSS和堆通常是被連續存儲的——記憶體位置上是連續的,而代碼段和棧往往會被獨立存放。

有趣的是,堆和棧兩個區域關系很“暧昧”,他們一個向下“長”(i386體系結構中棧向下、堆向上),一個向上“長”,相對而生。

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

從使用者向核心看,所使用的記憶體表象形式會依次經曆**“邏輯位址”——“線性位址”——“實體位址”**幾種形式。

邏輯位址經段機制轉化成線性位址;線性位址又經過頁機制轉化為實體位址。(但是我們要知道Linux系統雖然保留了段機制,但是将所有程式的段位址都定死為0-4G,是以雖然邏輯位址和線性位址是兩種不同的位址空間,但在Linux中邏輯位址就等于線性位址,它們的值是一樣的)。沿着這條線索,我們所研究的主要問題也就集中在下面幾個問題。

  1. 程序空間位址如何管理?
               
  2. 程序位址如何映射到實體記憶體?
               
  3. 實體記憶體如何被管理?
               

以及由上述問題引發的一些子問題。如系統虛拟位址分布;記憶體配置設定接口;連續記憶體配置設定與非連續記憶體配置設定等。

核心空間和使用者空間

Linux的虛拟位址空間範圍為0~4G,Linux核心将這4G位元組的空間分為兩部分, 将最高的1G位元組(從虛拟位址0xC0000000到0xFFFFFFFF)供核心使用,稱為“核心空間”。而将較低的3G位元組(從虛拟位址0x00000000到0xBFFFFFFF)供各個程序使用,稱為“使用者空間。因為每個程序可以通過系統調用進入核心,是以,Linux核心由系統内的所有程序共享。于是,從具體程序的角度來看,每個程序可以擁有4G位元組的虛拟空間。

核心空間中存放的是核心代碼和資料,而程序的使用者空間中存放的是使用者程式的代碼和資料。不管是核心空間還是使用者空間,它們都處于虛拟空間中。

雖然核心空間占據了每個虛拟空間中的最高1GB位元組,但映射到實體記憶體卻總是從最低位址(0x00000000),另外, 使用虛拟位址可以很好的保護 核心空間被使用者空間破壞,虛拟位址到實體位址轉換過程有作業系統和CPU共同完成(作業系統為CPU設定好頁表,CPU通過MMU單元進行位址轉換)。

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

注:多任務作業系統中的每一個程序都運作在一個屬于它自己的記憶體沙盒中,這個 沙盒就是虛拟位址空間(virtual address space),在32位模式下,它總是一個4GB的記憶體位址塊。這些虛拟位址通過頁表(page table)映射到實體記憶體,頁表由作業系統維護并被處理器引用。每個程序都擁有一套屬于它自己的頁表。

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

通常32位Linux核心位址空間劃分03G為使用者空間,34G為核心空間

注: 1.這裡是32位核心位址空間劃分,64位核心位址空間劃分是不同的

程序記憶體空間

Linux作業系統采用虛拟記憶體管理技術,使得每個程序都有各自互不幹涉的程序位址空間。該空間是塊大小為4G的線性虛拟空間,使用者所看到和接觸到的都是該虛拟位址,無法看到實際的實體記憶體位址。利用這種虛拟位址不但能起到保護作業系統的效果(使用者不能直接通路實體記憶體),而且更重要的是,使用者程式可使用比實際實體記憶體更大的位址空間(具體的原因請看硬體基礎部分)。

slabtop 實時顯示核心 slab 記憶體緩存資訊

TCP為什麼采用随機初始序列号

初始序列号是什麼?

TCP連接配接的一方A,随機選擇一個32位的序列号(Sequence Number)作為發送資料的初始序列号(Initial Sequence Number,ISN),比如為1000,以該序列号為原點,對要傳送的資料進行編号:1001、1002…三次握手時,把這個初始序列号傳送給另一方B,以便在傳輸資料時,B可以确認什麼樣的資料編号是合法的;同時在進行資料傳輸時,A還可以确認B收到的每一個位元組,如果A收到了B的确認編号(acknowledge number)是2001,就說明編号為1001-2000的資料已經被B成功接受。

  1. TCP發送端和接收端都擁有32位的序列号,用來控制每次發送的資料包是否接受。當發送端發送資料包時,資料包中會包含一個序列号,接收端接收到時,會傳回發送端序列号,用來通知接收資料接受完畢
  2. TCP三向交握中,每端都會擁有一個标志位或者1bit位的布爾域控制連接配接狀态

TCP初始序列号為什麼是随機的

在TCP的三次握手中,采用随機産生的初始化序列号進行請求,這樣做主要是出于網絡安全的因素着想。如果不是随機産生初始序列号,黑客将會以很容易的方式擷取到你與其他主機之間通信的初始化序列号,并且僞造序列号進行攻擊,這已經成為一種很常見的網絡攻擊手段。

擁塞控制

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

擁塞控制主要由四個算法組成:慢啟動(Slow Start)、擁塞避免(Congestion voidance)、快重傳 (Fast Retransmit)、快恢複(Fast Recovery)

  • 慢啟動:剛開始發送資料時,先把擁塞視窗(congestion window)設定為一個最大封包段MSS的數值,每收到一個新的确認封包之後,就把擁塞視窗加1個MSS。這樣每經過一個傳輸輪次(或者說是每經過一個往返時間RTT),擁塞視窗的大小就會加倍
分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧
  • 擁塞避免:當擁塞視窗的大小達到慢開始門限(slow start threshold)時,開始執行擁塞避免算法,擁塞視窗大小不再指數增加,而是線性增加,即每經過一個傳輸輪次隻增加1MSS.

    無論在慢開始階段還是在擁塞避免階段,隻要發送方判斷網絡出現擁塞(其根據就是沒有收到确認),就要把慢開始門限ssthresh設定為出現擁塞時的發送方視窗值的一半(但不能小于2)。然後把擁塞視窗cwnd重新設定為1,執行慢開始算法。 (這是不使用快重傳的情況)

  • 快重傳:快重傳要求接收方在收到一個失序的封包段後就立即發出重複确認(為的是使發送方及早知道有封包段沒有到達對方)而不要等到自己發送資料時捎帶确認。快重傳算法規定,發送方隻要一連收到三個重複确認就應當立即重傳對方尚未收到的封包段,而不必繼續等待設定的重傳計時器時間到期。
    分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧
  • 快恢複:當發送方連續收到三個重複确認時,就把慢開始門限減半,然後執行擁塞避免算法。不執行慢開始算法的原因:因為如果網絡出現擁塞的話就不會收到好幾個重複的确認,是以發送方認為現在網絡可能沒有出現擁塞。 也有的快重傳是把開始時的擁塞視窗cwnd值再增大一點,即等于 ssthresh + 3*MSS 。這樣做的理由是:既然發送方收到三個重複的确認,就表明有三個分組已經離開了網絡。這三個分組不再消耗網絡的資源而是停留在接收方的緩存中。可見現在網絡中減少了三個分組。是以可以适當把擁塞視窗擴大些。

TCP與UDP的差別

  • TCP是面向連接配接的,UDP是無連接配接的;(UDP發送資料之前不需要建立連接配接)
  • TCP是可靠的,UDP不可靠;(UDP接收方收到封包後,不需要給出任何确認)
  • TCP隻支援點對點通信,UDP支援一對一、一對多、多對一、多對多;
  • TCP是面向位元組流的,UDP是面向封包的;(面向位元組流是指發送資料時以位元組為機關,一個資料包可以拆分成若幹組進行發送,而UDP一個封包隻能一次發完。)
  • TCP有擁塞控制機制,UDP沒有。網絡出現的擁塞不會使源主機的發送速率降低,UDP對某些實時應用是很重要的,比如媒體通信,遊戲;
  • TCP首部開銷(20位元組)比UDP首部開銷(8位元組)要大
  • UDP 的主機不需要維持複雜的連接配接狀态表

什麼時候選擇TCP,什麼時候選UDP?

對某些實時性要求比較高的情況,選擇UDP,比如遊戲,媒體通信,實時視訊流(直播),即使出現傳輸錯誤也可以容忍;其它大部分情況下,HTTP都是用TCP,因為要求傳輸的内容可靠,不出現丢失

HTTP可以使用UDP嗎?

HTTP不可以使用UDP,HTTP需要基于可靠的傳輸協定,而UDP不可靠

面向連接配接和無連接配接的差別

無連接配接的網絡服務(資料報服務)-- 面向連接配接的網絡服務(虛電路服務)

虛電路服務:首先建立連接配接,所有的資料包經過相同的路徑,服務品質有較好的保證;

資料報服務:每個資料包含目的位址,資料路由互相獨立(路徑可能變化);網絡盡最大努力傳遞資料,但不保證不丢失、不保證先後順序、不保證在時限内傳遞;網絡發生擁塞時,可能會将一些分組丢棄;

TCP如何保證傳輸的可靠性

  • 資料包校驗
  • 對失序資料包重新排序(TCP封包具有序列号)
  • 丢棄重複資料
  • 應答機制:接收方收到資料之後,會發送一個确認(通常延遲幾分之一秒);
  • 逾時重發:發送方發出資料之後,啟動一個定時器,逾時未收到接收方的确認,則重新發送這個資料;
  • 流量控制:確定接收端能夠接收發送方的資料而不會緩沖區溢出

Linux事件管理

在 Linux核心 2.6版本之後,有了 epoll,它的做法就不是這樣了。

epoll在 Linux核心中申請了一個簡易的檔案系統。在程序中使用 epoll的時候,首先調用 epoll_create建立 epoll句柄,當需要對 TCP連接配接進行監控時,直接調用 epoll_ctl向 epoll句柄中添加這10萬+個連接配接的套接字即可。然後調用 epoll_wait收集發生事件的連接配接。

這樣,隻需要在程序啟動時建立1個 epoll句柄,并在需要的時候向它添加或删除連接配接就可以了,然後選擇阻塞或者非阻塞的方式調用 epoll_wait,作業系統就會傳回發生事件的連接配接。

說起來很簡單,Linux核心将如何實作以上的想法呢?當某一個程序調用 epoll_create方法時,Linux核心會建立一個 eventpoll結構體,這個結構體中有兩個成員與 epoll的使用方式密切相關,如下所示:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

每一個 epoll對象都有一個獨立的 eventpoll結構體,這個結構體會在核心空間中創造獨立的記憶體,用于存儲使用 epoll_ctl方法向 epoll對象中添加進來的事件。這些事件都會挂到 rbr紅黑樹中,這樣,重複添加的事件就可以通過紅黑樹而高效地識别出來(epoll_ctl方法會很快)。

所有添加到 epoll中的事件都會與裝置(如網卡)驅動程式建立回調關系,相應的事件發生時會調用這裡的回調方法。這個回調方法在核心中叫做 ep_poll_callback,它會把這樣的事件放到上面的rdllist雙向連結清單中(進而使用者調用 epoll_wait的時候直接檢查 rdlink就可以了)。(Nginx代碼與 Linux核心代碼很相似,紅黑樹與雙向連結清單基本一緻)。在 epoll中,對于每一個事件都會建立一個 epitem結構體,如下所示:

epoll高效的原因:

當調用 epoll_wait檢查是否有發生事件的連接配接時,隻是檢查 eventpoll對象中的 rdllist雙向連結清單是否有 epitem元素而已,如果 rdllist連結清單不為空,則把這裡的事件複制到使用者态記憶體中,同時将事件數量傳回給使用者。是以,epoll_wait的效率非常高。epoll_ctl在向 epoll對象中添加、修改、删除事件時,從 rbr紅黑樹中查找事件也非常快,也就是說,epoll是非常高效的,它可以輕易地處理百萬級别的并發連接配接。

epoll高效的本質:

1.減少使用者态和核心态之間的檔案句柄拷貝;

2.減少對可讀可寫檔案句柄的周遊。

Linux磁盤

df
           
分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

Linux程序排程

程序的分類

在 CPU 的角度看程序行為的話,可以分為兩類:

  • CPU 消耗型:此類程序就是一直占用 CPU 計算,CPU 使用率很高
  • IO 消耗型:此類程序會涉及到 IO,需要和使用者互動,比如鍵盤輸入,占用 CPU 不是很高,隻需要 CPU 的一部分計算,大多數時間是在等待 IO

    CPU 消耗型程序需要高的吞吐率,IO 消耗型程序需要強的響應性,這兩點都是排程器需要考慮的。

為了更快響應 IO 消耗型程序,核心提供了一個搶占(preempt)機制,使優先級更高的程序,去搶占優先級低的程序運作。核心用以下宏來選擇核心是否打開搶占機制:

CONFIG_PREEMPT_NONE: 不打開搶占,主要是面向伺服器。此配置下,CPU 在計算時,當輸入鍵盤之後,因為沒有搶占,可能需要一段時間等待鍵盤輸入的程序才會被 CPU 排程。

CONFIG_PREEMPT : 打開搶占,一般多用于手機裝置。此配置下,雖然會影響吞吐率,但可以及時響應使用者的輸入操作。

排程時刻

排程的本質就是選擇下一個程序,然後切換。在執行排程之前需要設定排程标記 TIF_NEED_RESCHED,然後在排程的時候會判斷目前程序有沒有被設定 TIF_NEED_RESCHED,如果設定則調用函數 schedule 來進行排程。

程序上下文切換

了解了下一個程序的選擇後,就需要做目前程序和所選程序的上下文切換。

Linux 核心用函數 context_switch 進行程序的上下文切換,程序上下文切換主要涉及到兩部分:程序位址空間切換和處理器狀态切換:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

程序的位址空間切換

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

将下一個程序的 pgd 虛拟位址轉化為實體位址存放在 ttbr0_el1 中(這是使用者空間的頁表基址寄存器),當通路使用者空間位址的時候 mmu 會通過這個寄存器來做周遊頁表獲得實體位址。完成了這一步,也就完成了程序的位址空間切換,确切的說是程序的虛拟位址空間切換。

寄存器狀态切換

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

其中 x19-x28 是 arm64 架構規定需要調用儲存的寄存器,可以看到處理器狀态切換的時候将前一個程序(prev)的 x19-x28,fp,sp,pc 儲存到了程序描述符的 cpu_contex 中,然後将即将執行的程序 (next) 描述符的 cpu_contex 的 x19-x28,fp,sp,pc 恢複到相應寄存器中,而且将 next 程序的程序描述符 task_struct 位址存放在 sp_el0 中,用于通過 current 找到目前程序,這樣就完成了處理器的狀态切換

檔案系統

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

檔案系統的基本組成

檔案系統是作業系統中負責管理持久資料的子系統,說簡單點,就是負責把使用者的檔案存到磁盤硬體中,因為即使計算機斷電了,磁盤裡的資料并不會丢失,是以可以持久化的儲存檔案。

檔案系統的基本資料機關是檔案,它的目的是對磁盤上的檔案進行組織管理,那組織的方式不同,就會形成不同的檔案系統。

Linux 最經典的一句話是:「一切皆檔案」,不僅普通的檔案和目錄,就連塊裝置、管道、socket 等,也都是統一交給檔案系統管理的。

Linux 檔案系統會為每個檔案配置設定兩個資料結構:索引節點(index node)和目錄項(directory entry),它們主要用來記錄檔案的元資訊和目錄層次結構。

  • 索引節點,也就是 inode,用來記錄檔案的元資訊,比如 inode 編号、檔案大小、通路權限、建立時間、修改時間、資料在磁盤的位置等等。索引節點是檔案的唯一辨別,它們之間一一對應,也同樣都會被存儲在硬碟中,是以索引節點同樣占用磁盤空間。
  • 目錄項,也就是 dentry,用來記錄檔案的名字、索引節點指針以及與其他目錄項的層級關聯關系。多個目錄項關聯起來,就會形成目錄結構,但它與索引節點不同的是,目錄項是由核心維護的一個資料結構,不存放于磁盤,而是緩存在記憶體。

目錄項和目錄是一個東西嗎?

雖然名字很相近,但是它們不是一個東西,目錄是個檔案,持久化存儲在磁盤,而目錄項是核心一個資料結構,緩存在記憶體。

如果查詢目錄頻繁從磁盤讀,效率會很低,是以核心會把已經讀過的目錄用目錄項這個資料結構緩存在記憶體,下次再次讀到相同的目錄時,隻需從記憶體讀就可以,大大提高了檔案系統的效率。

注意,目錄項這個資料結構不隻是表示目錄,也是可以表示檔案的。

那檔案資料是如何存儲在磁盤的呢?

磁盤讀寫的最小機關是扇區,扇區的大小隻有 512B 大小,很明顯,如果每次讀寫都以這麼小為機關,那這讀寫的效率會非常低。

是以,檔案系統把多個扇區組成了一個邏輯塊,每次讀寫的最小機關就是邏輯塊(資料塊),Linux 中的邏輯塊大小為 4KB,也就是一次性讀寫 8 個扇區,這将大大提高了磁盤的讀寫的效率。

以上就是索引節點、目錄項以及檔案資料的關系,下面這個圖就很好的展示了它們之間的關系:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

索引節點是存儲在硬碟上的資料,那麼為了加速檔案的通路,通常會把索引節點加載到記憶體中。

另外,磁盤進行格式化的時候,會被分成三個存儲區域,分别是超級塊、索引節點區和資料塊區。 - 超級塊,用來存儲檔案系統的詳細資訊,比如塊個數、塊大小、空閑塊等等。 - 索引節點區,用來存儲索引節點; - 資料塊區,用來存儲檔案或目錄資料;

我們不可能把超級塊和索引節點區全部加載到記憶體,這樣記憶體肯定撐不住,是以隻有當需要使用的時候,才将其加載進記憶體,它們加載進記憶體的時機是不同的:

超級塊:當檔案系統挂載時進入記憶體;

索引節點區:當檔案被通路時進入記憶體;

虛拟檔案系統

檔案系統的種類衆多,而作業系統希望對使用者提供一個統一的接口,于是在使用者層與檔案系統層引入了中間層,這個中間層就稱為虛拟檔案系統(Virtual File System,VFS)。

VFS 定義了一組所有檔案系統都支援的資料結構和标準接口,這樣程式員不需要了解檔案系統的工作原理,隻需要了解 VFS 提供的統一接口即可。

在 Linux 檔案系統中,使用者空間、系統調用、虛拟機檔案系統、緩存、檔案系統以及存儲之間的關系如下圖:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

Linux 支援的檔案系統也不少,根據存儲位置的不同,可以把檔案系統分為三類:

  • 磁盤的檔案系統,它是直接把資料存儲在磁盤中,比如 Ext 2/3/4、XFS 等都是這類檔案系統。
  • 記憶體的檔案系統,這類檔案系統的資料不是存儲在硬碟的,而是占用記憶體空間,我們經常用到的

    /proc

    /sys

    檔案系統都屬于這一類,讀寫這類檔案,實際上是讀寫核心中相關的資料資料。
  • 網絡的檔案系統,用來通路其他計算機主機資料的檔案系統,比如 NFS、SMB 等等。

檔案系統首先要先挂載到某個目錄才可以正常使用,比如 Linux 系統在啟動時,會把檔案系統挂載到根目錄。

檔案的使用

我們從使用者角度來看檔案的話,就是我們要怎麼使用檔案?首先,我們得通過系統調用來打開一個檔案。

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧
fd = open(name, flag); # 打開檔案
  . . .
write(fd,...);         # 寫資料
 . . .
close(fd);             # 關閉檔案```

           

上面簡單的代碼是讀取一個檔案的過程: - 首先用 open 系統調用打開檔案,open 的參數中包含檔案的路徑名和檔案名。 - 使用 write 寫資料,其中 write 使用 open 所傳回的檔案描述符,并不使用檔案名作為參數。 - 使用完檔案後,要用 close 系統調用關閉檔案,避免資源的洩露。

我們打開了一個檔案後,作業系統會跟蹤程序打開的所有檔案,所謂的跟蹤呢,就是作業系統為每個程序維護一個打開檔案表,檔案表裡的每一項代表「檔案描述符」,是以說檔案描述符是打開檔案的辨別。

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

作業系統在打開檔案表中維護着打開檔案的狀态和資訊: - 檔案指針:系統跟蹤上次讀寫位置作為目前檔案位置指針,這種指針對打開檔案的某個程序來說是唯一的; - 檔案打開計數器:檔案關閉時,作業系統必須重用其打開檔案表條目,否則表内空間不夠用。因為多個程序可能打開同一個檔案,是以系統在删除打開檔案條目之前,必須等待最後一個程序關閉檔案,該計數器跟蹤打開和關閉的數量,當該計數為 0 時,系統關閉檔案,删除該條目; - 檔案磁盤位置:絕大多數檔案操作都要求系統修改檔案資料,該資訊儲存在記憶體中,以免每個操作都從磁盤中讀取; - 通路權限:每個程序打開檔案都需要有一個通路模式(建立、隻讀、讀寫、添加等),該資訊儲存在程序的打開檔案表中,以便作業系統能允許或拒絕之後的 I/O 請求;

在使用者視角裡,檔案就是一個持久化的資料結構,但作業系統并不會關心你想存在磁盤上的任何的資料結構,作業系統的視角是如何把檔案資料和磁盤塊對應起來。

是以,使用者和作業系統對檔案的讀寫操作是有差異的,使用者習慣以位元組的方式讀寫檔案,而作業系統則是以資料塊來讀寫檔案,那屏蔽掉這種差異的工作就是檔案系統了。

檔案 I/O

檔案的讀寫方式各有千秋,對于檔案的 I/O 分類也非常多,常見的有

緩沖與非緩沖 I/O

直接與非直接 I/O

阻塞與非阻塞 I/O VS 同步與異步 I/O

接下來,分别對這些分類讨論讨論。

緩沖與非緩沖 I/O

檔案操作的标準庫是可以實作資料的緩存,那麼根據「是否利用标準庫緩沖」,可以把檔案 I/O 分為緩沖 I/O 和非緩沖 I/O:

  • 緩沖 I/O,利用的是标準庫的緩存實作檔案的加速通路,而标準庫再通過系統調用通路檔案。
  • 非緩沖 I/O,直接通過系統調用通路檔案,不經過标準庫緩存。

這裡所說的「緩沖」特名額準庫内部實作的緩沖。

比方說,很多程式遇到換行時才真正輸出,而換行前的内容,其實就是被标準庫暫時緩存了起來,這樣做的目的是,減少系統調用的次數,畢竟系統調用是有 CPU 上下文切換的開銷的。

直接與非直接 I/O

我們都知道磁盤 I/O 是非常慢的,是以 Linux 核心為了減少磁盤 I/O 次數,在系統調用後,會把使用者資料拷貝到核心中緩存起來,這個核心緩存空間也就是「頁緩存」,隻有當緩存滿足某些條件的時候,才發起磁盤 I/O 的請求。

那麼,根據是「否利用作業系統的緩存」,可以把檔案 I/O 分為直接 I/O 與非直接 I/O:

  • 直接 I/O,不會發生核心緩存和使用者程式之間資料複制,而是直接經過檔案系統通路磁盤。
  • 非直接 I/O,讀操作時,資料從核心緩存中拷貝給使用者程式,寫操作時,資料從使用者程式拷貝給核心緩存,再由核心決定什麼時候寫入資料到磁盤。

如果你在使用檔案操作類的系統調用函數時,指定了 O_DIRECT 标志,則表示使用直接 I/O。如果沒有設定過,預設使用的是非直接 I/O。

如果用了非直接 I/O 進行寫資料操作,核心什麼情況下才會把緩存資料寫入到磁盤?

以下幾種場景會觸發核心緩存的資料寫入磁盤: - 在調用 write 的最後,當發現核心緩存的資料太多的時候,核心會把資料寫到磁盤上; - 使用者主動調用 sync,核心緩存會刷到磁盤上; - 當記憶體十分緊張,無法再配置設定頁面時,也會把核心緩存的資料刷到磁盤上; - 核心緩存的資料的緩存時間超過某個時間時,也會把資料刷到磁盤上;

阻塞與非阻塞 I/O VS 同步與異步 I/O

為什麼把阻塞 / 非阻塞與同步與異步放一起說的呢?因為它們确實非常相似,也非常容易混淆,不過它們之間的關系還是有點微妙的。

先來看看阻塞 I/O,當使用者程式執行 read ,線程會被阻塞,一直等到核心資料準備好,并把資料從核心緩沖區拷貝到應用程式的緩沖區中,當拷貝過程完成,read 才會傳回。

注意,阻塞等待的是「核心資料準備好」和「資料從核心态拷貝到使用者态」這兩個過程。過程如下圖:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

知道了阻塞 I/O ,來看看非阻塞 I/O,非阻塞的 read 請求在資料未準備好的情況下立即傳回,可以繼續往下執行,此時應用程式不斷輪詢核心,直到資料準備好,核心将資料拷貝到應用程式緩沖區,read 調用才可以擷取到結果。過程如下圖:

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

注意,這裡最後一次 read 調用,擷取資料的過程,是一個同步的過程,是需要等待的過程。這裡的同步指的是核心态的資料拷貝到使用者程式的緩存區這個過程。

Linux 的IO棧

分布式存儲 基礎知識儲備學習Linux記憶體管理TCP為什麼采用随機初始序列号擁塞控制TCP與UDP的差別Linux事件管理Linux磁盤Linux程式排程檔案系統虛拟檔案系統檔案的使用檔案 I/OLinux 的IO棧

應用程式:

這沒什麼好說的,通過相關系統調用(如open/read/write)發起IO請求,屬于IO請求的源頭;

檔案系統:

應用程式的請求直接到達檔案系統層。檔案系統又分為VFS和具體檔案系統(ext3、ext4等),VFS對應用層提供統一的通路接口,而ext3等檔案系統則具體實作了這些接口。另外,為了提供IO性能,在該層還實作了諸如page cache等功能。同時,使用者也可以選擇繞過page cache,而是直接使用direct模式進行IO(如資料庫)。

塊裝置層:

檔案系統将IO請求打包送出給塊裝置層,該層會對這些IO請求作合并、排序、排程等,然後以新的格式發往更底層。在該層次上實作了多種電梯排程算法,如cfq、deadline等。

SCSI層:

塊裝置層将請求發往SCSI層,SCSI就開始真實處理這些IO請求,但是SCSI層又對其内部按照功能劃分了不同層次:

  • SCSI高層:高層驅動負責管理disk,接收塊裝置層發出的IO請求,打包成SCSI層可識别的指令格式,繼續往下發;
  • SCSI中層:中層負責通用功能,如錯誤處理,逾時重試等;
  • SCSI低層:底層負責識别實體裝置,将其抽象提供給高層,同時接收高層派發的scsi指令,交給實體裝置處理。

繼續閱讀