通用塊層

概述

在塊設備上的操作,涉及內核中的多個組成部分,如圖1所示。假設一個進程使用系統
調用read()讀取磁盤上的文件。下面步驟是內核響應進程讀請求的步驟;

linux-kernel-bio.png
  • 系統調用read()會觸發相應的VFS(Virtual Filesystem Switch)函數,傳遞的參數
    有文件描述符和文件偏移量。
  • VFS確定請求的數據是否已經在內存緩沖區中;若數據不在內存中,確定如何執行讀操作。
  • 假設內核必須從塊設備上讀取數據,這樣內核就必須確定數據在物理設備上的位置。這由映射層(Mapping Layer)來完成,主要執行兩步
    1. 內核確定該文件所在的文件系統的塊大小,并根據文件塊的大小計算所請求數據的長度。本質上,文件被看作拆分成許多塊,因此內核確定請求數據所在的塊號(文件開始位置的相對索引)。
    2. 接下來,映射層調用一個具體的文件系統的函數,它訪問文件的磁盤節點,然后根據邏輯塊號確定所請求數據在磁盤上的位置。事實上,磁盤也被看作分成許多塊,因此內核必須確定存放所請求數據的塊對應的號(磁盤或分區開始位置的相對索引)。由于一個文件可能存儲在磁盤上的不連續塊中,因此存放在磁盤索引節點中的數據結構將每個文件塊號映射為一個邏輯塊號。
  • 內核可以對塊設備發出讀請求。內核利用通用塊層(generic block layer)啟動I/O操作來傳送所請求的數據。一般而言, 每個I/O操作只針對磁盤上一組連續的塊。由于請求的數據不必位于相鄰的塊中,所以通用塊層可能啟動幾次I/O操作。每次I/O操作是由一個“塊I/O”(簡單block io 即bio)的結構來描述,它收集底層組件需要的所有信息以滿足所發出的請求
    通用塊層為所有的塊設備提供了一個抽象的視圖,因而隱藏了硬件塊設備間的差異性。幾乎所有的塊設備都是磁盤。所以通用塊層也提供了一些數據結構來描述“磁盤”或"磁盤分區"
  • 通用塊層的下面“I/O調度程序”根據預定義的內核策略將待處理的I/O數據傳送請求進行歸類。調度程序的作用是把物理介質上相鄰的數據請求聚集在一起。
    -最后,塊設備驅動程序向磁盤控制器的三件接口發送適當的命令。從而進行實際的數據傳送。

對于(1)、(2)兩個步驟,在Linux虛擬文件系統中,我們討論了VFS(Virtual Filesystem Switch)主要數據結構和操作,結合相關系統調用(如sys_read()、sys_write()等) 的源碼,我們不難理解VFS層相關的操作和實現。

塊設備中的數據存儲涉及了許多內核的組件;每個組件采用不同長度的來管理磁盤數據:

  • 硬件塊設備控制器采用稱為扇區的固定長度的塊來傳遞數據。因此,I/O調度程序和塊驅動程序必須管理數據扇區
  • 虛擬文件系統、映射層和文件系統將磁盤數據存放在稱為的邏輯單元中。一個塊對應文件系統中一個最小磁盤存儲單元
  • 塊設備驅動程序應該能夠處理數據的:一個段就是一個內存頁或內存內的一部分,它們包括磁盤上物理相鄰的數據塊。
  • 磁盤高速緩存作用于磁盤數據的上,每頁正好裝在一個頁框中。
  • 通用塊層將所有的上層和下層的組件組合在一起,因此它了解數據的扇區、塊、段以及頁。
    注:但是,如果從原始塊設備文件進行讀訪問,映射層就不調用具體文件系統的方法,而是把塊設備文件中的偏移量轉換成磁盤或在對應該設備文件的磁盤分區中的位置。
    即使有許多不同的數據塊,它們通常也是共享相同的物理RAM單元。例如:圖2顯示了一個具有4K的頁的構造。上層內核組件將頁看成是由4個1K字節組成的塊緩沖區。塊設備驅動程序正在傳送頁中的后3個塊,因此這3塊被插入到涵蓋了后3K字節的段中。硬盤控制器將段看成由6個512字節的扇區組成。
    page-disk-layout.png

相關概念

系統中能夠隨機訪問固定大小數據片(chunk)的設備稱為塊設備,這些數據片就稱作 塊。最常見的塊設備是硬盤,除此之外,還有CD-ROM驅動器和SSD等。它們通常安裝文 件系統的方式使用。
內核管理塊設備要比管理字符設備復雜。因為字符設備僅僅需要控制一個位置-當前位 置,而塊設備訪問的位置必須能夠在介質的不同區間前后移動。所以內核不必提供一個專門 的子系統來管理字符設備,但是對于塊設備的管理卻必須要有一個專門提供服務的子系統。 不僅僅是因為塊設備的復雜性遠遠高于字符設備,更重要的原因是塊設備對執行性能要求很 高;對硬盤每多一分利用都會對系統的整體性能帶來提升,其效果要遠遠比鍵盤吞吐速度成 倍的提高大得多。另外,塊設備的復雜性會為這種優化留下很大的空間。

  • 扇區
    為了達到可接受的性能,硬盤和類似的設備快速傳送幾個相鄰字節的數據。塊設備的每次傳送操作都作用于一組稱為扇區的相鄰字節。我們假定字節按相鄰的方式記錄在磁盤表面,這樣一次搜索操作就可以訪問到它們。盡管磁盤的物理構造很復雜,但是硬盤控制器收到的命令將硬盤看成一個大組扇區
    在大部分磁盤設備中,扇區的大小是512字節但是一些設備使用更大的扇區(1K 2K)。注意,應該把扇區作為數據傳送的基本單元;不允許傳送少于一個扇區的數據,盡管大多數磁盤設備都可以同時傳送幾個相鄰的扇區。
    Linux中扇區的大小按慣例設為512字節;如果一個塊設備使用更大的扇區,那么相應的底層塊設備驅動程序將做些必要的變換。因此,對存放在塊設備中的一組數據是通過它們在磁盤上的位置來標識,即首個512字節扇區的下標以及扇區的數目。扇區的下標放在類型為sector_t的32或64位亦是中。

  • 扇區是硬件設備傳送數據的基本單位,而是VFS和文件系統傳送數據的基本單位。例如:內核訪問一個文件的內容時,它必須首先從磁盤上讀文件的磁盤索引節點所在塊。該塊對應磁盤上的一個或多個相鄰的扇區,而VFS將其看成是一個單一的數據單元
    在Linux中,塊大小必須是2的冪,而且不能超過一個頁框。此外,它必須是扇區大小的整數倍,因為每個塊必須包含整個扇區。因此在80x86體系結構中,它允許塊的大小為512 1024 2048 4096字節(linux 固定扇區大小為512)。

塊設備的塊大小不是唯一的。創建一個磁盤文件系統時,管理員可以選擇合適的塊大小。因此,同一個磁盤上的幾個分區可能使用不同的塊大小。此外,對塊設備文件的每次讀或寫操作是一種"原始"訪問,因此它繞過了磁盤的文件系統;內核通過使用最大的塊(4096)執行該操作。

每個塊都需要自己的塊緩沖區,它是內核用來存放塊內容的RAM內存區。當內核從磁盤讀出一個塊時,就用從硬件設備中獲得的值來填充相應的塊緩沖區;同樣,當內核向磁盤寫入一個塊時,就用相關塊緩沖的實際值來更新硬件設備上相應的一組相鄰字節。塊緩沖區的大小 通常要與相應的塊大小相匹配。
緩沖區首部是一個與每個緩沖區相關的buffer_head類型的描述符。它包含內核處理緩沖區需要了解的所有信息;因此,在對每個緩沖區進行操作之前,內核都要首先檢查其緩沖區首部。
其中b_page字段存放的是塊緩沖區所在頁框的頁描述符地址。如果頁框位于高端內存中,那么 b_data字段存放頁中塊緩沖區的偏移量;否則,b_data存放塊緩沖區本身的起始線性地址。b_blocknr字段存放的是邏輯塊號。最后,b_bdev字段標識使用緩沖區首部的塊設備。


  • 磁盤的每個IO操作就是在磁盤與一些RAM單元之間相互傳送一些相鄰扇區的內容。大多數情況下,磁盤控制器直接采用DMA方式進行數據傳送。塊設備驅動程序只要向磁盤控制器發送一些適當的命令就可以觸發一次數據傳送,一旦完成數據的傳送,控制器就會發出一個中斷通知塊設備驅動程序。
    DMA方式傳送的是磁盤上相鄰扇區的數據。這是一個物理約束:磁盤控制器允許DMA傳送不相鄰的扇區數據,但是這種方式的傳送速率很低,因為在磁盤表面上移動讀、寫磁頭是相當慢的。

老式的磁盤控制器僅僅支持"簡單"的DMA傳送方式;在這種傳送方式中,磁盤必須與RAM中的連續內存單元相互傳送數據。但是,新的磁盤控制器也支持所謂的分散-聚集(scatter-gether)DMA傳送方式:此種方式中,磁盤可以與一些非連續的內存區相互傳送數據。
啟動一次分散-聚集DMA傳送,塊設備驅動程序需要向磁盤控制器發送:

  1. 要傳送的起始磁盤扇區號和總的扇區數。
  2. 內存區的描述符鏈表,其中鏈表的每項包含一個地址和一個長度。
    磁盤控制器負責整個數據的傳送;例如,在讀操作中控制器從相鄰磁盤扇區中獲得數據,然后將它們存放到不同的內存區中。
    為了使用分散-聚集DMA方式傳送方式,塊設備驅動程序必須能夠處理稱為段的數據存儲單元。一個段就是一個內存頁或內存頁中的一部分,它們包含一些相鄰的磁盤扇區中的數據。因此一次分散-聚集DMA操作可以同時傳送幾個段。

注意:塊設備驅動程序不需要知道塊,塊大小以及塊緩沖區。因此,即使高層將段看成是由幾個塊緩沖區組成的頁,塊設備驅動程序也不用對此給予關注。

如果不中的段在RAM中相應的頁框正好是連續的并且在磁盤上相應的數據塊也是相鄰的,那么通用塊層可以合并它們。通過這種合并方式產生的更大的內存區就稱為 物理段
然后,在多數體系結構上還允許另一種合并方式:通過使用一個專門的總結電路[IO-MMU]來處理總結地址與物理地址間的映射。通過這種合并方式產生的內存區稱為硬件段。由于我們將注意力集中在80X86體系結構上,它的總結地址與物理地址之間不存在動態的映射,因此我們可以假定硬件段與物理段是對應的。

通用塊層

通用塊層是一個內核組件,它處理來自系統中的所有塊設備發出的請求。

  • BIO結構

通用塊層的核心數據結構是一個稱為BIO的描述符,它描述了塊設備的IO操作。每個bio結構都包含一個磁盤存儲區標識符(存儲區中的起始扇區和扇區數目)和一個或多個描述符 與IO操作相關的內存區的段。bio由struct bio 數據結構描述,源代碼如下:
struct bio
https://github.com/sparrowzoo/linux/blob/master/include/linux/blk_types.h

bio中的每個段是一個由bio_vec數據結構描述的
源代碼如下:
https://github.com/sparrowzoo/linux/blob/master/include/linux/bvec.h
在塊IO操作期間,bio描述符的內容一直保持更新。例如,果塊設備驅動程序在一次分散-聚集DMA操作中不能完成全部的數據傳送,那么bio中的bi_idx字段會不斷更新來指向待傳送的第一個段。

struct bvec_iter {
    sector_t        bi_sector;  /* device address in 512 byte
                           sectors */
    unsigned int        bi_size;    /* residual I/O count */

    unsigned int        bi_idx;     /* current index into bvl_vec */

    unsigned int            bi_bvec_done;   /* number of bytes completed in
                           current bvec */
};

為了從索引bi_idx指向的當前段開始不斷重復bio中的段,設備驅動程序可以執行宏bio_for_each_segment。
通用塊層啟動一次新的IO操作時,調用bio_alloc函數分配一個新的bio結構。通常,bio結構是由slab分配器分配的。但是,當內存不足時,內核也會使用一個備用的bio小內存池。內核也為bio_vec結構分配內存池。畢竟,分配一個bio結構而不能分配其中的段描述符也是沒有什么意義的。相應地bio_put函數減少bio中中引用計數器bi_cnt的值,如果該值小于0,則釋放bio結構以及相關的bio_vec結構。

通常硬盤被劃分成幾個邏輯分區。每塊塊設備文件要么代表整個磁盤,要么代表磁盤中的某一個分區。例如,一個主設備號為3、次設備號為0的設備文件/dev/had代表的可能是一個主IDE磁盤;該磁盤中的前兩個分區分別由設備文件/dev/hda1和/dev/hda2代表,它們的主設備號都是3,而次設備號分別為1和2。一般而言,磁盤中的分區是由連續的次設備號來區分的。
如果將一個磁盤分成了幾個分區,那么其分區表保存在hd_struct結構數組中,該數的地址存放在gendisk對象的part (struct disk_part_tbl __rcu *part_tbl; 源碼版本不一致)字段中。通過磁盤內分區的相對索引對該數組進行索引。hd_struct數據結構如下:

struct disk_part_tbl {
    struct rcu_head rcu_head;
    int len;
    struct hd_struct __rcu *last_lookup;
    struct hd_struct __rcu *part[];
};
struct hd_struct {
    sector_t start_sect;
    /*
     * nr_sects is protected by sequence counter. One might extend a
     * partition while IO is happening to it and update of nr_sects
     * can be non-atomic on 32bit machines with 64bit sector_t.
     */
    sector_t nr_sects;
    seqcount_t nr_sects_seq;
    sector_t alignment_offset;
    unsigned int discard_alignment;
    struct device __dev;
    struct kobject *holder_dir;
    int policy, partno;
    struct partition_meta_info *info;
#ifdef CONFIG_FAIL_MAKE_REQUEST
    int make_it_fail;
#endif
    unsigned long stamp;
    atomic_t in_flight[2];
#ifdef  CONFIG_SMP
    struct disk_stats __percpu *dkstats;
#else
    struct disk_stats dkstats;
#endif
    struct percpu_ref ref;
    struct rcu_head rcu_head;
};

當內核發現系統中一個新的磁盤時(在啟動階段,或將一個可移動介質插入到一個驅動器中時,或在運行期附加一個外置磁盤時),就調用alloc_disk()函數,該函數分配并初始化一個新的gendisk對象。如果新磁盤被分成了幾個分區,那么alloc_disk還會分配并初始化一個適當的hd_struct類型的數組。然后,內核調用add_disk()函數將gendisk對象插入到通用塊層的數據結構中。

  • 提交請求
    我們介紹一下當向通用塊層提交一個IO操作請求時,內核所執行的步驟順序。我們假定(因為上文提到一個IO,如果數據不相鄰會被拆成多個請求)被請求的數據塊在磁盤上是相鄰的,并且內核已經知道了它們的物理位置。
  1. 第一步是執行bio_alloc函數分配一個新的bio描述符。然后通過設置一些字段值來初始化bio描述符(bi_sector\bi_size\bi_bdev\bi_io_vec\bi_rw\bi_end_io)
    一旦bio描述符被進行了適當的初始化,內核就調用generaic_make_request函數,該函數是通用塊層的主要入口點。

    1. 獲取與塊設備相關的請求隊列
    2. 調用blk_partition_remap()函數

    至此,能用塊層 IO調度程序以及設備驅動程序將忘記磁盤分區的存在,直接作用于整個磁盤。

    1. 調用q_make_request_fn方法將bio請求插入到請求隊列中。
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • feisky云計算、虛擬化與Linux技術筆記posts - 1014, comments - 298, trac...
    不排版閱讀 3,882評論 0 5
  • 1. 基礎知識 1.1、 基本概念、 功能 馮諾伊曼體系結構1、計算機處理的數據和指令一律用二進制數表示2、順序執...
    yunpiao閱讀 5,361評論 1 22
  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數據革命閱讀 12,193評論 2 33
  • 從這一章開始,我們探討的話題就會與前面的話題稍有不同——我們將開始探討操作系統對于設備的管理以及它為用戶程序提供服...
    夏威夷的芒果閱讀 1,424評論 0 0
  • 文|莫曉妖 圖|網絡 我是一個正兒八經的農村女孩,不是富二代,也不是官二代,我只是一個名副其實的“負”二代。 一次...
    莫曉妖閱讀 541評論 6 4