最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

一文詳解Linux內(nèi)核塊設(shè)備層介紹之bio層

2023-02-07 14:47 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

像linux這樣的操作系統(tǒng), 一個(gè)非常有價(jià)值的東西就是提供了一套具體設(shè)備的抽象接口,比如我們常提到的字符設(shè)備, 塊設(shè)備,網(wǎng)絡(luò)設(shè)備,位圖顯示器等等。其中塊設(shè)備非常重要,尤其隨著持久化存儲(chǔ)的不斷發(fā)展以及未來(lái)持久化內(nèi)存的持續(xù)增長(zhǎng),塊設(shè)備抽象的應(yīng)用場(chǎng)景將越來(lái)越廣泛。所以今天就讓我們來(lái)剖析和解讀一下塊設(shè)備接口。

首先定義一下什么是“塊層”(block layer)。一般當(dāng)我們提到“塊層”時(shí),是指Linux內(nèi)核中應(yīng)用程序和文件系統(tǒng)用來(lái)訪問(wèn)多種不同的存儲(chǔ)設(shè)備的模塊接口。那么究竟哪些代碼構(gòu)成了塊層呢?一個(gè)不動(dòng)腦子的答案就是在linux kernel源代碼中block子目錄下的所有代碼都是塊層。 這一堆代碼可以看做提供了兩個(gè)抽象層,他們合作緊密,但是又有所不同。這兩層目前在社區(qū)并沒(méi)有統(tǒng)一的叫法,我們姑且叫他們bio層和request層。

塊層之上

在深入了解bio層之前,還是有必要了解一下塊設(shè)備之上的層。這里提到的”上“,是指離用戶(hù)態(tài)更近一些,而離硬件更遠(yuǎn)一些。下圖表述了塊層在內(nèi)核中的位置。

訪問(wèn)塊設(shè)備通常是通過(guò)/dev目錄下文件來(lái)實(shí)現(xiàn),例如/dev/sda這樣的就是塊設(shè)備,它們?cè)趦?nèi)核中被映射成S_IFBLK屬性的inodes。這些文件并不代表真正的塊設(shè)備,而更像是軟鏈接,我們可以通過(guò)它們代表的’major:minor’這樣的數(shù)字來(lái)找到真正的塊設(shè)備。在內(nèi)核的inode結(jié)構(gòu)體中,i_bdev這個(gè)成員被用來(lái)指向一個(gè)代表真實(shí)設(shè)備的結(jié)構(gòu)體struct block_device。而這個(gè)struct block_device中的bd_inode則指向了另外一個(gè)inode,這個(gè)inode才和這個(gè)塊設(shè)備的I/O真正相關(guān)。

當(dāng)設(shè)備沒(méi)有使用O_DIRECT打開(kāi)的時(shí)候, 這個(gè)bd_inode(實(shí)現(xiàn)在fs/block_dev.c, fs/buffer.c等)的主要角色是提供page cache。像一個(gè)正常被打開(kāi)的文件一樣,這個(gè)inode節(jié)點(diǎn)的page同意被用于對(duì)這個(gè)設(shè)備進(jìn)行緩沖讀,預(yù)讀,緩沖寫(xiě),延遲寫(xiě)等等。當(dāng)這個(gè)設(shè)備被以O(shè)_DIRECT的方式打開(kāi)的時(shí)候,讀寫(xiě)則會(huì)直接到塊設(shè)備。一般來(lái)說(shuō),當(dāng)一個(gè)文件系統(tǒng)掛載一個(gè)塊設(shè)備時(shí),文件系統(tǒng)的讀寫(xiě)操作通常都是直接訪問(wèn)塊設(shè)備。但是對(duì)于另外一些文件系統(tǒng)(比如我們經(jīng)常用到的ext *系列),它們則會(huì)用bd_inode的page cache來(lái)管理一些文件系統(tǒng)的元數(shù)據(jù)。

這里需要重點(diǎn)提到一個(gè)與塊設(shè)備特別相關(guān)的open標(biāo)志是O_EXCL。塊設(shè)備通過(guò)這個(gè)flag來(lái)確定每個(gè)塊設(shè)備最多可以有一個(gè)“持有人”。當(dāng)我們?cè)噲D持有一個(gè)塊設(shè)備(例如,在內(nèi)核中使用blkdev_get()或類(lèi)似的調(diào)用)的時(shí)候,如果在我們之前已經(jīng)有另外一個(gè)不同的持有者已經(jīng)擁有了該設(shè)備,那么我們的持有請(qǐng)求將會(huì)失敗。一般的文件系統(tǒng)試圖掛載設(shè)備的時(shí)候會(huì)使用這個(gè)open標(biāo)志,從而確保自己是獨(dú)占設(shè)備的。所以如果文件系統(tǒng)以O(shè)_EXCL的方式成功打開(kāi)了設(shè)備,那么從此以后它就會(huì)成為這個(gè)設(shè)備的持有者。如果這以后再有文件系統(tǒng)嘗試去mount的話(huà), 就會(huì)失敗。這里有一點(diǎn)比較有趣的是,使用O_EXCL并不會(huì)阻止在沒(méi)有O_EXCL的情況下打開(kāi)塊設(shè)備,因此它其實(shí)并不會(huì)阻止并發(fā)寫(xiě)入,而僅僅是阻止了其他文件系統(tǒng)以獨(dú)占方式打開(kāi)設(shè)備。

無(wú)論以哪種方式訪問(wèn)塊設(shè)備,有一點(diǎn)是一致的,都是bio層在向上提供的主要接口, 包括發(fā)送讀取或?qū)懭胝?qǐng)求,或者其他一些請(qǐng)求比如”discard”,并最終將答復(fù)返回給上層。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)? ? ?

bio層

linux上是用結(jié)構(gòu)體gendisk來(lái)代表塊設(shè)備, 這個(gè)結(jié)構(gòu)體并不包含很多實(shí)用的信息,而主要是作為上面的文件系統(tǒng)和下面的設(shè)備層之間的接口。在gendisk之上是一個(gè)或多個(gè)struct block_device,也就是前文提到的/dev中的inode鏈接。 當(dāng)一個(gè)gendisk有多個(gè)分區(qū)時(shí),它會(huì)和多個(gè)block_device結(jié)構(gòu)關(guān)聯(lián)。所以我們會(huì)有一個(gè)block_device代表整個(gè)gendisk(比如/dev/sda),也有可能還有一些其他的block_device代表gendisk中的分區(qū)(比如/dev/sda1, /dev/sda2…)。

在bio層里面還有一個(gè)結(jié)構(gòu)體叫做struct bio。

它代表來(lái)自block_device的讀取和寫(xiě)入請(qǐng)求, 以及其他一些控制請(qǐng)求。這些請(qǐng)求從block_device發(fā)出, 經(jīng)過(guò)gendisk再到設(shè)備驅(qū)動(dòng)。 一個(gè)bio結(jié)構(gòu)體里面主要包括具體的塊設(shè)備信息,塊設(shè)備中的偏移量,請(qǐng)求大小,請(qǐng)求類(lèi)型(讀或?qū)懀┮约胺胖脭?shù)據(jù)的內(nèi)存位置。在Linux 4.14之前,bio中通過(guò)指向struct block_device的指針來(lái)標(biāo)識(shí)目標(biāo)設(shè)備。4.14以后,struct block_device被替換成一個(gè)指向struct gendisk的指針以及一個(gè)可以由bio_set_dev()設(shè)置的分區(qū)號(hào)。考慮到gendisk結(jié)構(gòu)的核心作用,這樣的改動(dòng)更自然一些。

一旦bio構(gòu)造完成,我們就可以通過(guò)調(diào)用generic_make_request或者submit_bio來(lái)發(fā)起bio請(qǐng)求。但是一般情況下我們并不會(huì)等待請(qǐng)求完成,而只是將其插入隊(duì)列并等待后續(xù)處理,所以整個(gè)過(guò)程是異步的。不過(guò)這里有一點(diǎn)需要注意,在一些場(chǎng)景下generic_make_request()仍然可能由于等待內(nèi)存可用(比如它可能會(huì)等待先前的請(qǐng)求在完成以后從隊(duì)列中摘除,從而從而騰出隊(duì)列中的空間)而在短時(shí)間內(nèi)阻塞。在這種場(chǎng)景下,如果在bi_opf字段中設(shè)置了REQ_NOWAIT標(biāo)志,那么generic_make_request()就不會(huì)等待,而是把bio設(shè)置為BLK_STS_AGAIN或者BLK_STS_NOTSUPP,然后直接返回。不過(guò)在撰寫(xiě)本文時(shí),這個(gè)功能的實(shí)現(xiàn)還有一些問(wèn)題。

bio層和request層之間的契約以及交互協(xié)議很簡(jiǎn)單,主要的動(dòng)作就是讓設(shè)備通過(guò)調(diào)用blk_queue_make_request()并傳入自己的make_request_fn函數(shù)。如果設(shè)備傳入了自己的make_request_fn,genric_make_request()會(huì)調(diào)用它從而完成bio的發(fā)送工作。當(dāng)一個(gè)bio代表的I/O請(qǐng)求完成以后,generic_make_request函數(shù)還會(huì)將對(duì)應(yīng)的bi_status字段設(shè)置為成功或失敗,并調(diào)用bio_endio()來(lái)結(jié)束這個(gè)bio。

除了上面提到的處理bio的讀寫(xiě)請(qǐng)求之外,bio層最值得展開(kāi)的兩件事情是避免遞歸的技巧以及隊(duì)列的插入和拔出,接下來(lái)讓我們分別闡述一下。

避免遞歸

在使用虛擬塊設(shè)備,比如md(軟raid),dm(lvm2)的時(shí)候,我們會(huì)將一個(gè)塊設(shè)備疊在另一個(gè)塊設(shè)備上面,在這種場(chǎng)景下一個(gè)塊設(shè)備的bio會(huì)被修改并發(fā)到下一層塊設(shè)備的bio里面去,實(shí)現(xiàn)起來(lái)很簡(jiǎn)單,但是運(yùn)行起來(lái)會(huì)給內(nèi)核棧的使用造成極大的負(fù)擔(dān)。在2.6.22之前,由于文件系統(tǒng)已經(jīng)使用了很大一部分內(nèi)核棧,內(nèi)核棧溢出可能會(huì)有很?chē)?yán)重的問(wèn)題。為了解決這個(gè)問(wèn)題,generic_make_request()會(huì)檢測(cè)是否被遞歸調(diào)用并作相應(yīng)的處理。在發(fā)生遞歸的時(shí)候它不將bio傳遞到下一層,而只是內(nèi)部(通過(guò)使用current->bio_list )對(duì)bio進(jìn)行排隊(duì)。只有當(dāng)父bio完成的時(shí)候,它才會(huì)提交這個(gè)請(qǐng)求。由于前面提到generic_make_request()一般不會(huì)等待bio完成才返回,所以不立即處理bio也沒(méi)啥問(wèn)題。

這個(gè)避免遞歸的方案在大部分場(chǎng)景下是可以工作的,但卻在一些特殊場(chǎng)景可能會(huì)導(dǎo)致死鎖。讓我們?cè)俅蚊枋鲆幌逻@個(gè)場(chǎng)景,在generic_make_request調(diào)用make_request_fn的時(shí)候,看到之前有bio提交,就等待這個(gè)先前提交的bio完成。那么如果等待的那個(gè)bio還在current->bio_list隊(duì)列上怎么辦?很明顯這兩個(gè)bio都存在問(wèn)題,就導(dǎo)致了死鎖。

在實(shí)際情況中,一個(gè)bio等待另一個(gè)bio的場(chǎng)景是非常微妙的,所以一般這種死鎖都是通過(guò)測(cè)試發(fā)現(xiàn)的, 而不是代碼檢查,我們?cè)谶@里舉一個(gè)可能導(dǎo)致死鎖的例子。當(dāng)一個(gè)bio提交的時(shí)候, 如果遇到了大小限制或者對(duì)齊要求,make_request_fn就會(huì)把這個(gè)bio分成2個(gè)(bio層通過(guò)bio_split,bio_chain來(lái)實(shí)現(xiàn)),但是這個(gè)操作需要為第二個(gè)bio分配內(nèi)存空間。怎么分配內(nèi)存呢?我們知道當(dāng)系統(tǒng)沒(méi)內(nèi)存的時(shí)候,申請(qǐng)內(nèi)存總是危險(xiǎn)的,linux通常的做法是寫(xiě)臟頁(yè)來(lái)釋放內(nèi)存, 但是如果寫(xiě)臟頁(yè)也需要內(nèi)存的話(huà),那么很可能就死鎖了。所以這里標(biāo)準(zhǔn)的做法是使用mempool預(yù)先分配一些內(nèi)存,然后我們直接從mempool中分配從而避免內(nèi)存分配的死鎖問(wèn)題。看上去不錯(cuò),是么?在bio這個(gè)場(chǎng)景下這個(gè)解法的問(wèn)題來(lái)了。由于bio從mempool分配可能會(huì)需要等待以前的用戶(hù)返回他們使用的mempool內(nèi)存,而這個(gè)等待的依賴(lài)關(guān)系又會(huì)是某些之前的bio,所以可能會(huì)再次導(dǎo)致generic_make_request()死鎖。我的天哪,內(nèi)核編程簡(jiǎn)直就是在和各種死鎖,各種內(nèi)存不足做斗爭(zhēng)中。。。

為了避免這個(gè)死鎖,內(nèi)核研發(fā)人員做了很多嘗試。其中一個(gè)想法就是大家在調(diào)用ps時(shí)看到的那些bioset進(jìn)程。該機(jī)制特別關(guān)注上述死鎖場(chǎng)景,它為每個(gè)用于bio分配的mempool分配一個(gè)“rescuer”線(xiàn)程。如果bio分配不成功,那么所有當(dāng)前進(jìn)程的current->bio_list中的來(lái)自同一個(gè)bioset的所有bios將會(huì)被交給bioset線(xiàn)程進(jìn)行處理。這種方法相當(dāng)丑陋,因?yàn)槲覀冃枰獎(jiǎng)?chuàng)建了一些幾乎從不被使用的線(xiàn)程,而這些線(xiàn)程在內(nèi)核中的存在僅僅是為了解決這個(gè)特定的死鎖場(chǎng)景。而其他大多數(shù)的死鎖情況也涉及將bios分成兩個(gè)或更多的部分,但是它們并不總是涉及到mempool的分配問(wèn)題。一股淡淡的憂(yōu)傷??!

不過(guò)一個(gè)好消息是最近的內(nèi)核已經(jīng)很少依賴(lài)這個(gè)特性了,并且已經(jīng)在盡量避免創(chuàng)建不需要的bioset線(xiàn)程。在Linux 4.11中,研發(fā)人員對(duì)generic_make_request()做了修改并引入了更通用的替代方案。這個(gè)方案系統(tǒng)運(yùn)行開(kāi)銷(xiāo)較少,只是對(duì)驅(qū)動(dòng)有一定的要求。具體來(lái)說(shuō),當(dāng)bio被拆分時(shí),其中一半應(yīng)該直接提交到generic_make_request()并被立刻處理,而另一半則可以以其他適當(dāng)?shù)姆绞竭M(jìn)行處理。這無(wú)疑給了generic_make_request()更多的控制權(quán),它可以根據(jù)所提交的塊設(shè)備堆棧深度對(duì)所有bio進(jìn)行排序,并優(yōu)先處理底層塊設(shè)備的bio。這個(gè)簡(jiǎn)單的做法解決了所有令人討厭的死鎖問(wèn)題。真是換個(gè)思路海闊天空??!

設(shè)備隊(duì)列插入

Device queue plugging,這個(gè)詞一直沒(méi)想好怎么翻譯,就這樣吧!

通常情況下存儲(chǔ)設(shè)備對(duì)單次請(qǐng)求進(jìn)行操作的開(kāi)銷(xiāo)比較大,因此將一批請(qǐng)求集中在一起并作為一個(gè)單元提交它會(huì)更有效率。當(dāng)設(shè)備相對(duì)較慢時(shí),通常請(qǐng)求隊(duì)列中會(huì)有很多未處理的請(qǐng)求,這樣該隊(duì)列的存在也提供了很多機(jī)會(huì)來(lái)合并請(qǐng)求。反過(guò)來(lái)當(dāng)設(shè)備速度很快或者當(dāng)一個(gè)慢速設(shè)備空閑時(shí),找到合并請(qǐng)求并批量處理的機(jī)會(huì)就會(huì)少很多,那么無(wú)腦的嘗試合并則會(huì)很浪費(fèi)時(shí)間和精力。所以為了解決這個(gè)問(wèn)題,Linux塊層創(chuàng)造了一個(gè)“插入/拔出”(plug/unplug)的概念。

一開(kāi)始,隊(duì)列是空的并且是被插入(plug)的。所以在向空隊(duì)列提交請(qǐng)求的時(shí)候以及今后的一段時(shí)間內(nèi),不會(huì)有任何請(qǐng)求流入底層設(shè)備,這樣由文件系統(tǒng)提交的bio們就可以有充足的機(jī)會(huì)進(jìn)行合并。而當(dāng)文件系統(tǒng)提交了足夠的bio以后,它會(huì)顯式的進(jìn)行拔出操作(unlug),或者在一個(gè)很短的時(shí)間以后被默認(rèn)拔出,拔出以后IO開(kāi)始下發(fā)。Linux內(nèi)核的bio層就是通過(guò)這樣的plug/unplug方式來(lái)保證I/O請(qǐng)求能夠被批量下發(fā),并且希望找到一個(gè)合適的提交I/O請(qǐng)求的數(shù)量并達(dá)到最終的性能提升。在內(nèi)核研發(fā)早期,每個(gè)塊設(shè)備只有一個(gè)plug/unplug隊(duì)列,這樣導(dǎo)致多CPU場(chǎng)景下的隊(duì)列爭(zhēng)搶問(wèn)題非常嚴(yán)重。在Linux 2.6.39版本,Linux內(nèi)核合并進(jìn)了一個(gè)新的plug/unlug機(jī)制,這個(gè)機(jī)制允許每個(gè)進(jìn)程在自己的上下文中進(jìn)行隊(duì)列的插入工作,從而在CPU多核的擴(kuò)展性上得到了明顯的提升。具體的新機(jī)制是這樣工作的。

當(dāng)塊設(shè)備的文件系統(tǒng)或其他客戶(hù)端提交請(qǐng)求I/O時(shí),它通常在generic_make_request()前調(diào)用blk_start_plug(),結(jié)束之后再調(diào)用blk_finish_plug()。blk_start_plug主要的作用是初始化current-> plug,該數(shù)據(jù)結(jié)構(gòu)包含一個(gè)blk_plug_cb的隊(duì)列(還有一個(gè)結(jié)構(gòu)請(qǐng)求隊(duì)列,我們將在下一篇文章中詳細(xì)介紹)。由于這個(gè)隊(duì)列都是歸屬于進(jìn)程的,因此可以在無(wú)鎖環(huán)境下添加相應(yīng)的條目。 這樣make_request_fn就可以對(duì)傳過(guò)來(lái)的bio做靈活的處理,比如如果它認(rèn)為批處理請(qǐng)求更有優(yōu)勢(shì),那么它可以選擇將bio添加到隊(duì)列中。當(dāng)調(diào)用blk_finish_plug()時(shí),或者進(jìn)程調(diào)用schedule()時(shí)(例如等待互斥鎖或等待內(nèi)存分配時(shí)),current-> plug中存儲(chǔ)的每個(gè)bio將會(huì)被處理。

機(jī)制看上去很簡(jiǎn)單,但是這里有兩個(gè)有意思的設(shè)計(jì)值得大家思考。

第一個(gè),為什么在發(fā)生schedule()的時(shí)候需要處理plug隊(duì)列呢?因?yàn)槿绻M(jìn)程被阻塞,隊(duì)列就會(huì)被立刻處理,這樣可以防止其他進(jìn)程等待這個(gè)進(jìn)程正在準(zhǔn)備提交的bio,防止前面提到的死鎖。

第二個(gè),為什么在進(jìn)程級(jí)別維護(hù)這樣的隊(duì)列?因?yàn)橐粋€(gè)進(jìn)程提交的bio基本都是有關(guān)系的,而有關(guān)系的bio可以很容易得被檢測(cè)和合并在一起,另外一點(diǎn)就是相關(guān)操作都可以是無(wú)鎖的。想象一下如果這個(gè)不是進(jìn)程級(jí)別的話(huà),在操作bio的時(shí)候,肯定需要一個(gè)自旋鎖或者一個(gè)原子變量來(lái)保護(hù)隊(duì)列的操作。而通過(guò)每個(gè)進(jìn)程的自有隊(duì)列,我們可以無(wú)鎖的創(chuàng)建每個(gè)進(jìn)程自己的bio列表,然后只用一次spinlock將它們?nèi)亢喜⒌阶罱K的塊設(shè)備公共隊(duì)列中。

總結(jié)

總之,bio層是一個(gè)很薄的層,它的主要功能是以bio的形式接受I / O請(qǐng)求,并將它們直接傳遞給相應(yīng)的make_request_fn()函數(shù)。它提供了各種支持功能,以簡(jiǎn)化bio的分拆和調(diào)度,同時(shí)通過(guò)plug/unplug來(lái)優(yōu)化性能。它還執(zhí)行一些其他簡(jiǎn)單的任務(wù),例如更新/ proc / vmstat中的pgpgin和pgpgout統(tǒng)計(jì)信息等等。

當(dāng)然作為承上啟下的模塊,他另外一個(gè)重要工作是讓在它下面的模塊的工作能夠繼續(xù)下去,有時(shí)下一層是一個(gè)驅(qū)動(dòng)程序,例如drbd(分布式復(fù)制塊設(shè)備)或brd(基于RAM的塊設(shè)備);有時(shí)下一層是一個(gè)中間層,例如由md和dm提供的虛擬設(shè)備;還有一些可能是我們最常見(jiàn)的塊層的剩余部分,我們稱(chēng)之為“請(qǐng)求層”(request layer)。


原文作者:內(nèi)核月談





一文詳解Linux內(nèi)核塊設(shè)備層介紹之bio層的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
康保县| 泰兴市| 合川市| 北宁市| 嘉鱼县| 苏尼特右旗| 微山县| 兴安县| 外汇| 正镶白旗| 青铜峡市| 斗六市| 德清县| 庄浪县| 深泽县| 夹江县| 亚东县| 康马县| 应用必备| 柘荣县| 民丰县| 平利县| 仁怀市| 莎车县| 咸阳市| 宜兴市| 柳江县| 开封县| 平遥县| 鱼台县| 阿合奇县| 邢台市| 工布江达县| 延寿县| 张家界市| 都昌县| 容城县| 临沧市| 巴塘县| 宜章县| 新建县|