0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于塊層的組成“bio層”的詳細解析

Linux閱碼場 ? 2018-02-03 16:23 ? 次閱讀

操作系統(tǒng)比如Linux關鍵的價值之一,就是為具體的設備提供了抽象接口。雖然后來出現了各種其它抽象模型比如“網絡設備”和“位圖顯示(bitmap display)”,但是最初的“字符設備”和“塊設備”兩種類型的設備抽象依然地位顯赫。近幾年持久化內存(persistent memory)炙手可熱,[與非易失性存儲NVRAM概念不同, persistent memory強調以內存訪問方式讀寫持久存儲,完全不同與塊設備層], 但在將來很長一段時間內,塊設備接口仍然是持久存儲(persistent storage)的主角。這兩篇文章的目的就是去揭開這位主角的面紗。

術語“塊層”常指Linux內核中非常重要的一部分 - 這部分實現了應用程序和文件系統(tǒng)訪問存儲設備的接口。 塊層是由哪些代碼組成的呢? 這個問題沒有準確的答案。一個最簡單的答案是在"block"子目錄下的所有源碼。這些代碼又可被看作兩層,這兩層之間緊密聯系但有明顯的區(qū)別。我知道這兩個子層次還沒有公認的命名,因此這里就稱作“bio層”和“request 層”吧。本文將帶我們先了解"bio層",而在下一篇文章中討論“request層”。

塊層之上

在深挖bio層之前,很有必要先了解點背景知識,看看塊層之上的天地。這里“之上”意思是靠近用戶空間(the top),遠離硬件(the bottom),包括所有使用塊層服務的代碼。

基于塊層的組成“bio層”的詳細解析

通常,我們可以通過/dev目錄下的塊設備文件來訪問塊設備,在內核中塊設備文件會映射到一個有S_IFBLK標記的inode。這些inode有點像符號鏈接,本身不代表一個塊設備,而是一個指向塊設備的指針。更細地說,inode結構體的i_bdev域會指向一個代表目標設備的struct block_device對象。 struct block_device包含一個指向第二個inode的域:block_device->bd_inode, 這個inode會在塊設備IO中起作用,而/dev目錄下的inode只是一個指針而已。

第二個inode所起的主要作用(實現代碼主要在fs/block_dev.c, fs/buffer.c,等)就是提供page cache。如果設備文件打開時沒有加O_DIRECT標志,與inode關聯的page cache用來緩存預讀數據,或緩存寫數據直到回寫(writeback)過程將臟頁刷到塊設備上。如果用了O_DIRECT,讀和寫繞過page cache直接向塊設備發(fā)請求。相似地,當一個塊設備格式化并掛載成文件系統(tǒng)時,讀和寫操作通常會直接作用在塊設備上 [作者寫錯了?],盡管一些文件系統(tǒng)(尤其是ext*家族)能夠訪問相同的page cache(過去稱為buffer cache)來管理一些文件系統(tǒng)數據。

open()另一個與塊設備相關的標志是O_EXCL。塊設備有個簡單的勸告鎖(advisory-locking)模型,每個塊設備最多只能有個“持有者”(holder)。在激活一個塊設備時,[激活泛指驅動一個塊設備的過程,包括向內核添加代表塊設備的對象,注冊請求隊列等],可用blkdev_get()函數為塊設備指定一個"持有者"。[ blkdev_get()的原型: int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder), holder可以是一個文件系統(tǒng)的超級塊, 也可以是一個掛載點等]。一旦塊設備有了“持有者”,隨后再試圖激活該設備就會失敗。通常在掛載時,文件系統(tǒng)會為塊設備指定一個“持有者”,來保證互斥使用塊設備。當一個應用程序試圖以O_EXCL方式打開塊設備時,內核會新建一個struct file對象并把它作為塊設備的“持有者”,假如這個塊設備作為文件系統(tǒng)已經被掛載,打開操作就會失敗。如果open()操作成功并且還沒有關上,嘗試掛載操作就會阻塞。但是,如果塊設備不是以O_EXCL打開的,那么O_EXCL就不能阻止塊設備被同時打開,O_EXCL只是便于應用程序測試塊設備是否正在使用中。

無論以什么方式訪問塊設備,主要接口都是發(fā)送讀寫請求,或其它特殊請求比如discard操作, 最終接收處理結果。bio層就是要提供這樣的服務。

bio層

Linux中塊設備用struct gendisk表示,即 一個通用磁盤 (generic disk)。這個結構體也沒包含太多信息,主要起承上啟下的作用,上承文件系統(tǒng),下啟塊層。往上層走,一個gendisk對象會關聯到block_device對象,如我們上文所述,block_device對象被鏈接到/dev目錄下的inode中。如果一個物理塊設備包含多個分區(qū),也就說有個分區(qū)表,那么這個gendisk對象就對應多個block_device對象。其中,有一個block_device對象代表著整個物理磁盤gendisk,而其它block_device各代表gendisk中的一個分區(qū)。

struct bio是bio層一個重要的數據結構,用來表示來自block_device對象的讀寫請求,以及各種其它的控制類請求,然后把這些請求傳達到驅動層。一個bio對象包括的信息有目標設備,設備地址空間上的偏移量,請求類型(通常是讀或寫),讀寫大小,和用來存放數據的內存區(qū)域。在Linux 4.14之前,bio對象是用block_device來表示目標設備的。而現在bio對象包含一個指向gendisk結構體的指針和分區(qū)號,這些可通過bio_set_dev()函數設置。這樣做突出了gendisk結構體的核心地位,更自然一些。

一個bio一旦構造好,上層代碼就可以調用generic_make_request()或submit_bio()提交給bio層處理。[submit_bio()只是generic_make_request()的一個簡單封裝]。 通常,上層代碼不會等待請求處理完成,而是把請求放到塊設備隊列上就返回了。generic_make_request()有時可能阻塞一小會,比如在等待內存分配的時候,這樣想可能更容易理解,它也許要等待一些已經在隊列上的請求處理完成,然后騰出空間。如果bi_opf域上設置了REQ_NOWAIT標志,generic_make_request()在任何情況下都不應該阻塞,而應該把這個bio的返回狀態(tài)設置成BLK_STS_AGAIN或BLK_STS_NOTSUPP,然后立即返回。截至寫作時,這個功能還沒有完全實現。

bio層和request層間的接口需要設備驅動調用blk_queue_make_request()來注冊一個make_request_fn()函數,這樣generic_make_request()就可以通過回調這個函數來處理提交個這個塊設備的bio請求了。make_request_fn()函數負責如何處理bio請求,當IO請求完成時,調用bio_endio()設置bi_status域的狀態(tài)來表示請求是否處理成功,并回調保存在bio結構體里的bi_end_io函數。

除了上述對bio請求的簡單處理,bio層最有意思的兩個功能就是:避免遞歸調用(recursion avoidance)和隊列激活(queue plugging)。

避免遞歸(recursion avoidance)

在存儲方案里,經常用到"md" [mutiple device] (軟RAID就是md的一個實例)和"dm" [device mapper] (用于multipath和LVM2)這兩種虛擬設備,也常叫做棧式設備,由多個塊設備按樹的形式組織起來,它們會沿著設備樹往下一層一層對bio請求作修改和傳遞。如果采用遞歸的簡單的實現,在設備樹很深的情況下,會占用大量的內核??臻g。很久以前 (Linux 2.6.22),這個問題時不時會發(fā)生,在使用一些本身就因遞歸調用占用大量內核??臻g的文件系統(tǒng)時,情況更加糟糕。

為了避免遞歸,generic_make_request()會進行檢測,如果發(fā)現遞歸,就不會把bio請求發(fā)送到下一層設備上。這種情況下,generic_make_request()會把bio請求放到進程內部的一個隊列上(currect->bio_list, struct task_struct的一個域), 等到上一次的bio請求處理完以后,然后再提交這一層的請求。由于generic_make_request()不會阻塞以等待bio處理完成,即使延遲一會再處理請求都是沒問題的。

通常,這個避免遞歸的方法都工作得很完美,但有時候可能發(fā)生死鎖。理解死鎖如何發(fā)生的關鍵就是上文我們對bio提交方式的觀察: 當遞歸發(fā)生時,bio要排隊等待之前已經提交的bio處理完成。如果要等的bio一直在current->bio_list隊列上而得不到處理,它就會一直等下去。

引起bio互相等待而產生死鎖的原因,不太容易發(fā)現,通常都是在測試中發(fā)現的,而不是分析代碼發(fā)現的。以bio拆分 (bio split)為例,當一個bio的目標設備在大小或對齊上有限制時,make_request_fn()可能會把bio拆成兩部分,然后再分別處理。bio層提供了兩個函數(bio_split()和bio_chain()),使得bio拆分很容易,但是bio拆分需要給第二個bio結構體分配空間。在塊層代碼里分配內存要特別小心,尤其當內存緊張時,Linux在回收內存時,需要把臟頁通過塊層寫出去。如果在內存寫出的時候,又需要分配內存,那就麻煩了。一個標準的機制就是使用mempool,為一個某種關鍵目的預留一些內存。從mempool分配內存需要等待其它mempool的使用者歸還一些內存,而不用等待整個內存回收算法完成。當使用mempool分配bio內存時,這種等待可能會導致generic_make_request()死鎖。

社區(qū)已經有多次嘗試提供一個簡單的方式來避免死鎖。一個是引入了"bioset" 進程,你可以用ps命令在電腦上查看。這個機制主要關注的就是解決上面描述的死鎖問題,為每一個分配bio的"mempool"分配一個"rescuer"線程。如果發(fā)現bio分配不出來,所有在currect->bio_list的bio就會被取下來,交個相應的bioset線程來處理。這個方法相當復雜,導致創(chuàng)建了很多bioset線程,但是大多時候派不上用場,只是為了解決一個特殊的死鎖情況,代價太高了。通常,死鎖跟bio拆分有關系,但是它們不總是要等待mempool分配。[最后這句話,有些突兀]

最新的內核通常不會創(chuàng)建bioset線程了,而只是在幾種個別情況下才會創(chuàng)建。Linux 4.11內核,引入了另一個解決方案,對generic_make_request()做了改動,好處是更通用,代價小,但是卻對驅動程序提出了一點要求。主要的要求是在發(fā)生bio拆分時,其中一個bio要直接提交給generic_make_request()來安排最合適的時間處理,另一個bio可以用任何合適的方式處理,這樣generic_make_request()就有了更強的控制力。 根據bio在提交時在設備棧中的深度,對bio進行排序后,總是先處理更低層設備的bio, 再處理較高層設備的bio。這個簡單的策略避免了所有惱人的死鎖問題。

塊隊列激活(queue plugging)

存儲設備處理單個IO請求的代價通常挺高的,因此提高處理效率的一個辦法就是把多個請求聚集起來,然后做一次批量提交。對于慢速設備來說,隊列上積攢的請求通常會多一些,那么做批處理的機會就多。但是,對于快速設備,或經常處于空閑狀態(tài)的慢速設備來說,做批處理的機會就顯然少了很多。為了解決這個問題,Linux塊層提出了一個機制叫"plugging"。[plugging, 即堵上塞子,隊列就像水池,請求就像水,堵上塞子就可以蓄水了]

原來,plugging僅僅在隊列為空的時候才使用。在向一個空隊列提交請求前,這個隊列就會被“堵塞”上一會時間,好讓請求積蓄起來,暫時不往底層設備提交。文件系統(tǒng)提交的bio就會排起隊來,以便做批處理。文件系統(tǒng)可以主動請求,或著定時器周期性超時,來拔開塞子。我們預期的是在一定時間內聚集一批請求,然后在一點延遲后就開始真正處理IO,而不是一直聚積特別多的請求。從Linux 2.6.30開始,有了一個新的plugging機制,把積蓄請求的對象,從面向每個設備,改成了面向每個進程。這個改進在多處理器上擴張性很好。

當文件系統(tǒng),或其它塊設備的使用者在提交請求時,通常會在調用generic_make_request()前后加上blk_start_plug()和blk_finish_plug()。 blk_start_plug()會初始化一個struct blk_plug結構體,讓current->plug指向它,這個結構體里面包含一個請求列表(我們會在下一篇文章細說這個)。因為這個請求列表是每個進程就有一個,所以在往列表里添加請求時不用上鎖。如果可以更高效率的處理請求,make_request_fn()就會把bio添加到這個列表上。

當blk_finish_plug()被調用時,或調用schedule()進行進程切換時(比如,等待mutex鎖,等待內存分配等),保存在current->plug列表上的所有請求就要往底層設備提交,就是說進程不能身負IO請求去睡覺。

調用schedule()進行進程切換時,積蓄的bio會被全部處理,這個事實意為著bio處理的延遲只會發(fā)生在新的bio請求不斷產生期間。假如進程因等待要進入睡眠,那么積蓄起來的bio就會被立即處理。這樣可以避免出現循環(huán)等待的問題,試想一個進程在等待一個bio請求處理完成而進入睡眠,但是這個bio請求還在plug列表上并沒有下發(fā)給底層設備。

像這樣進程級別的plugging機制,主要的好處一是相關性最強的bio會更容易聚集起來,以便批量處理,二是這樣很大程度上減少了隊列鎖的競爭。如果沒有進程級別的plugging處理,那么每一個bio請求到來時,都要進行一次spinlock或原子操作。有了這樣的機制,每一個進程就有一個bio列表,把進程bio列表往設備隊列里合并時,只需要上一次鎖就夠了。

bio層及以下(bio layer and below)

總之,bio層不是很復雜,它將IO請求以bio結構體的方式直接傳遞給相應的make_request_fn() [具體的實現有通用塊層的blk_queue_bio(), DM設備的dm_make_request(), MD設備的md_make_request()]。bio層實現了各種通用的函數,來幫助設備驅動層處理bio拆分,scheduling the sub-bios [不會翻譯這個,意思應該是安排拆分后的bio如何處理], "plugging"請求等。 bio層也會做一些簡單操作,比如更新/proc/vmstat中的pgpgin和pgpgout的計數,然后把IO請求的大部分操作交給下一層處理 [request層]。

有時候,bio層的下一層就是最終的驅動,比如說DRBD(The Distributed Replicated Block Device)或 BRD (a RAM based block device). 更常見的下一層有MD和DM提供這種虛擬設備的中間層。不可或缺的一層,就是除bio層之外剩下的部分了,我稱之為"request 層",這將是我們在下一篇討論的話題。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11212

    瀏覽量

    208721

原文標題:塊層介紹 第一篇: bio層

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    4板手動布線問題

    各位老師,一已用過一段時間的4板( 別人畫的),現想用手動布線的方式做一些小的修改,但在布線時,有一些新放置的過孔和焊盤,不知如何使它從頂層連接的底層?或頂層連接到電源?或連接到地線
    發(fā)表于 09-24 21:04

    AUTOSAR基礎軟件是由哪些部分組成

    基礎軟件主要是用于提供基礎軟件服務,包括標準化的系統(tǒng)功能以及功能接口,并且由一系列的基礎服務軟件組成,包括系統(tǒng)服務、內存服務、通信服務等。一、基礎軟件模塊按照類型可以分為驅動模塊、接口模塊、處理模塊以及管理器。驅動模塊:包含
    發(fā)表于 02-17 08:00

    EPA功能及用戶技術研究

    EPA功能及用戶技術研究 Research on EPA Functional Block and User Layer Technology
    發(fā)表于 03-17 09:14 ?16次下載

    交換技術解析

    交換技術解析 簡單地說,三交換技術就是:二交換技術+三轉發(fā)技術。它解決了局域網中網段劃分之
    發(fā)表于 08-01 11:59 ?638次閱讀

    深度剖析基于組成“request

    現在,"request"并存著兩種模型:單隊列(single-queue) 和 多隊列(multi-queue)。多隊列的出現也就是近幾年的事情,也許總有一天會完全取代單隊列的,但是目前來看兩者
    的頭像 發(fā)表于 02-03 16:29 ?4280次閱讀
    深度剖析基于<b class='flag-5'>塊</b><b class='flag-5'>層</b>的<b class='flag-5'>組成</b>“request<b class='flag-5'>層</b>”

    電纜外護的結構組成是怎樣的,它的作用是什么

    外護是包覆在電纜護套(內護)外面的保護覆蓋層,主要起機械加強和防腐蝕的作用。常用電纜有內護為金屬護的外護和內護
    發(fā)表于 07-27 09:48 ?4621次閱讀

    pcb板各層畫什么?絲印 機械 阻焊 助焊 信號 鉆孔數據作用詳解

    pcb板在畫圖的時候大家都知道,電路板會有很多層,那么首先我們要知道都是PCB板子的哪些。通過對PCB的各個圖層的詳細解答,希望能夠對大家進一步了解一PCB的組成與設計有幫助。下面
    的頭像 發(fā)表于 08-17 11:25 ?2.1w次閱讀

    設備的數據結構 page/request和bio的關系

    主要內容:從應用程序發(fā)起一次IO行為,最終怎么到磁盤,以及在這個路徑上有什么trace的方法和 配置。每次應用程序寫磁盤,都是到pagecache 。三進三出 講解 bio的一生,都是在pagecache以下。
    發(fā)表于 05-16 09:23 ?3945次閱讀
    <b class='flag-5'>塊</b>設備<b class='flag-5'>層</b>的數據結構 page/request和<b class='flag-5'>bio</b>的關系

    Linux架構介紹 IO流程與IO調度器詳解

    之前一直跟大家聊文件系統(tǒng),文件系統(tǒng)提供一文件到物理的映射轉換。這邏輯可能非常復雜,依賴于文件系統(tǒng)的實現。今天則跟大家聊聊
    的頭像 發(fā)表于 05-16 12:12 ?2297次閱讀

    交換機的特點

    工作在網絡和傳輸之間:四交換機工作在OSI模型的第三和第四之間,可以支持TCP/IP協(xié)議的傳輸,既可以對IP報文頭進行
    發(fā)表于 05-05 15:48 ?537次閱讀

    PCB阻焊與助焊的區(qū)別

    標準的印刷電路板 (PCB) 通常需要兩種不同類型的,即“罩 (mask)”。
    發(fā)表于 06-01 16:58 ?2713次閱讀
    PCB阻焊<b class='flag-5'>層</b>與助焊<b class='flag-5'>層</b>的區(qū)別

    技術資訊 I PCB 阻焊與助焊的區(qū)別

    本文要點PCB阻焊的基礎知識。了解焊膏在PCB上的作用。針對阻焊和助焊設置PCBCAD系統(tǒng)。一標準的印刷電路板(PCB)通常需要兩種不同類型的
    的頭像 發(fā)表于 07-18 17:34 ?2655次閱讀
    技術資訊 I PCB 阻焊<b class='flag-5'>層</b>與助焊<b class='flag-5'>層</b>的區(qū)別

    板如何設置板層

    板是一種常用于電子產品中的印制電路板(PCB),具有四個層次或層面。在設計四板時,需要合理設置板層,以優(yōu)化電路性能和信號傳輸。本文將詳細介紹四板的板層設置方法。 四
    的頭像 發(fā)表于 12-21 11:26 ?1882次閱讀

    卷積神經網絡中池化的作用

    。其中,池化(Pooling Layer)作為CNN的重要組成部分,在降低模型復雜度、提高計算效率以及增強模型的不變性和魯棒性方面發(fā)揮著關鍵作用。本文將從多個方面深入探討池化的作用,力求全面
    的頭像 發(fā)表于 07-03 15:58 ?977次閱讀

    神經網絡中的卷積、池化與全連接

    在深度學習中,卷積神經網絡(Convolutional Neural Network, CNN)是一種特別適用于處理圖像數據的神經網絡結構。它通過卷積、池化和全連接的組合,實現了對圖像特征的自動提取和分類。本文將
    的頭像 發(fā)表于 07-11 14:18 ?4217次閱讀