專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計(jì)實(shí)例 >> 瀏覽文章

塊設(shè)備驅(qū)動(dòng)學(xué)習(xí)過程

作者:佚名   來源:本站原創(chuàng)   點(diǎn)擊數(shù):  更新時(shí)間:2014年08月18日   【字體:

塊設(shè)備傳輸數(shù)據(jù)的固定長度為一個(gè)sector,因此,輸入、輸出調(diào)度器和塊設(shè)備的驅(qū)動(dòng)必須管理扇區(qū)的數(shù)據(jù)。

虛擬文件系統(tǒng)->磁盤設(shè)備的中間映射層,它使用一種邏輯單元來管理磁盤數(shù)據(jù),這種邏輯單元被稱之為“Block”,一個(gè)塊相當(dāng)于一個(gè)文件系統(tǒng)里面的最小磁盤存儲(chǔ)單元。
塊設(shè)備驅(qū)動(dòng)能夠拷貝一個(gè)segment的數(shù)據(jù):每個(gè)段是一個(gè)內(nèi)存頁或者一個(gè)內(nèi)存頁的一個(gè)部分包含了磁盤上的在物理上相鄰的block【即segment由一個(gè)或多個(gè)block組成,而內(nèi)存page由一個(gè)或多個(gè)磁盤上相鄰的segment組成】;
【注意】這里的segment只是處理數(shù)據(jù)的單位,并不是操作系統(tǒng)中段頁式管理內(nèi)存中的那個(gè)“段的概念”
磁盤緩沖工作于磁盤數(shù)據(jù)的“頁page”上,每一頁適合于一頁的框架內(nèi)。
因?yàn)镚eneric block層連接上層和下層的所有組件,因此在這一層里定義了sectors blocks  segments 和數(shù)據(jù)頁

sector: 扇區(qū)   block:塊  segment:段  page:頁
sector:硬件(磁盤)上的最小的操作單位,是操作系統(tǒng)和塊設(shè)備(硬件、磁盤)之間傳送數(shù)據(jù)的單位
block由一個(gè)或多個(gè)sector組成,是軟件(OS、文件系統(tǒng))中最小的操作單位;操作系統(tǒng)的虛擬文件系統(tǒng)從硬件設(shè)備上讀取一個(gè)block,實(shí)際為從硬件設(shè)備讀取一個(gè)或多個(gè)sector.對(duì)于文件管理來說,每個(gè)文件對(duì)應(yīng)的多個(gè)block可能是不連續(xù)的;block最終要映射到sector上,所以block的大小一般是sector的整數(shù)倍。不同的文件系統(tǒng)block可使用不同的大小,操作系統(tǒng)會(huì)在內(nèi)存中開辟內(nèi)存,存放block到所謂的block buffer中。
segment由磁盤上的在物理上相鄰的一個(gè)或多個(gè)block組成(從硬盤中將block獨(dú)到內(nèi)存中)
在linux內(nèi)核中,內(nèi)存以4KB/頁 進(jìn)行管理
 
塊設(shè)備的工作流程如下所示:
 
block_device_operations:
與字符設(shè)備的file_operations類似,塊設(shè)備有一個(gè)block_device_operations結(jié)構(gòu)體;該block_device_operations結(jié)構(gòu)體主要綁定open、close、iocrl、compat_ioctl、media_changged、revalidate_disk、getgeo等函數(shù)。
gendisk:
在linux內(nèi)核中,使用gendisk結(jié)構(gòu)體來表示一個(gè)獨(dú)立的磁盤設(shè)備或者一個(gè)分區(qū)
同一個(gè)磁盤的各個(gè)分區(qū)共享一個(gè)主設(shè)備號(hào),而次設(shè)備號(hào)則不同。
在OS中,gendisk屬于通用塊設(shè)備層,格式化在該層進(jìn)行處理。
gendisk的struct disk_part_tbl part_tbl成員指向分區(qū)表
gendisk的struct hd_struct part0成員表示一個(gè)分區(qū)。
disk->part_tbl->part[0] = &disk->part0;
針對(duì)該結(jié)構(gòu)體的相關(guān)函數(shù)有:
    alloc_disk:分配gendisk
    add_disk:注冊(cè)gendisk
    del_gendisk:釋放gendisk
    getdisk、putdisk:引用計(jì)數(shù)
block_device:
類似于字符設(shè)備的描述結(jié)構(gòu)體cdev,塊設(shè)備(或它的分區(qū))也有一個(gè)塊設(shè)備描述結(jié)構(gòu)體【類似于gendisk既可以描述磁盤設(shè)備也可以描述分區(qū)】,但是不像字符設(shè)備驅(qū)動(dòng)需要注冊(cè)cdev,塊設(shè)置直接注冊(cè)gendisk即可。
struct block_device {
    dev_t     bd_dev; 
    struct inode *     bd_inode;
    ....
    struct block_device *   bd_contains;
    unsigned     bd_block_size;
    struct hd_struct *     bd_part;
    ....
    struct gendisk *     bd_disk;
    struct list_head     bd_list;
    ....
};
下面的圖片有一個(gè)磁盤disk,磁盤有個(gè)一分區(qū)partition; 磁盤的塊設(shè)備描述符block_device bd_contains成員指向自己,而
分區(qū)partition的bd_contains成員指向該分區(qū)所屬的磁盤設(shè)備即disk,以表示一種從屬的關(guān)系。
通過add_disk(struct gendisk *)就可以注冊(cè)一個(gè)設(shè)備【并不是注冊(cè)一個(gè)分區(qū)】,就一個(gè)設(shè)備與gendisk綁定;而在linux內(nèi)部分區(qū)時(shí)也用gendisk表示分區(qū),我們不用管。
圖中g(shù)endisk中的part是gendisk結(jié)構(gòu)體中的part_table成員,指向設(shè)備的分區(qū)表;在塊設(shè)備中用hd_struct結(jié)構(gòu)體表示一個(gè)分區(qū),所以圖中表示有四個(gè)分區(qū),其中partition分區(qū)的描述結(jié)構(gòu)體block_device中的bd_part指向第二分區(qū)處。
通過圖片分析知在程序中如果

request:
在linux內(nèi)核的塊設(shè)備中,使用request結(jié)構(gòu)體來表征等待進(jìn)行的IO請(qǐng)求。
request_queue:
表示IO請(qǐng)求隊(duì)列的結(jié)構(gòu)體,一個(gè)塊請(qǐng)求隊(duì)列是一個(gè)塊IO請(qǐng)求隊(duì)列。
 
Disk Caches,磁盤高速緩存。
在linux中,程序?qū)τ脖P設(shè)備的寫操作實(shí)際上是寫到硬盤在內(nèi)存中的高速緩存,內(nèi)黑中有pdflush守護(hù)進(jìn)程,什么時(shí)候高速緩存中的數(shù)據(jù)寫入到硬盤中由pdflush決定,可通過sync() fsync()進(jìn)行強(qiáng)制更新。
將磁盤上的數(shù)據(jù)緩存在內(nèi)存中,加速文件的讀寫。實(shí)際上,在一般情況下,read/write是只跟緩存打交道的。(當(dāng)然,存在特殊情況。下面會(huì)說到。)
read就直接從緩存讀數(shù)據(jù)。如果要讀的數(shù)據(jù)還不在緩存中,則觸發(fā)一次讀盤操作,然后等待磁盤上的數(shù)據(jù)被更新到磁盤高速緩存中;write也是直接寫到緩存里去,然后就不用管了。后續(xù)內(nèi)核會(huì)負(fù)責(zé)將數(shù)據(jù)寫回磁盤。

BIO :塊IO
通常一個(gè)bio對(duì)應(yīng)一個(gè) 上層傳遞給塊層的I/O請(qǐng)求,I/O請(qǐng)求算法可將連續(xù)的bio合并成一個(gè)request。request是bio經(jīng)由塊層進(jìn)行調(diào)整后的結(jié)果。

上圖中request的成員q指向自己所屬的等待隊(duì)列,request通過quelist成員組成request鏈表;
request的bio與bio_tail指向request包含的bio。
在IO調(diào)度器中,上層提交的bio被構(gòu)造成request結(jié)構(gòu),一個(gè)request結(jié)構(gòu)包含了一組順序的bio。而每個(gè)物理設(shè)備會(huì)對(duì)應(yīng)一個(gè)request_queue,里面順序存放著相關(guān)的request。
新的bio可能被合并到request_queue中已有的request結(jié)構(gòu)中(甚至合并到已有的bio中),也可能生成新的request結(jié)構(gòu)并插入到request_queue的適當(dāng)位置上。具體怎么合并、怎么插入,取決于設(shè)備驅(qū)動(dòng)程序選擇的IO調(diào)度算法。大體上可以把IO調(diào)度算法就想象成“電梯算法”,盡管實(shí)際的IO調(diào)度算法有所改進(jìn)。

bio結(jié)構(gòu)中通過bi_next組成bio鏈表。bio的核心是一個(gè)稱為bi_io_vec的數(shù)組,它由bio_vec結(jié)構(gòu)體組成。
bio結(jié)構(gòu)體中的成員bv_page是一個(gè)內(nèi)存的頁指針。

bio通過IO調(diào)度算法這一層層的排序、合并等,生產(chǎn)IO請(qǐng)求,IO請(qǐng)求存放到IO請(qǐng)求隊(duì)列中去。


為了實(shí)現(xiàn)這樣的緩存,每個(gè)文件的inode內(nèi)嵌了一個(gè)address_space結(jié)構(gòu),通過inode->i_mapping來訪問。address_space結(jié)構(gòu)中維護(hù)了一棵radix樹,用于磁盤高速緩存的內(nèi)存頁面就掛在這棵樹上。而既然磁盤高速緩存是跟文件的inode關(guān)聯(lián)上的,則打開這個(gè)文件的每個(gè)進(jìn)程都共用同一份緩存。
radix樹的具體實(shí)現(xiàn)細(xì)節(jié)這里可以不用關(guān)心,可以把它理解成一個(gè)數(shù)組。數(shù)組中的每個(gè)元素就是一個(gè)頁面,文件的內(nèi)容就順序存放在這些頁面中。

于是,通過要讀寫的文件pos,可以換算得到要讀寫的是第幾頁(pos是以字節(jié)為單位,只需要除以每個(gè)頁的字節(jié)數(shù)即可)。
inode被載入內(nèi)存的時(shí)候,對(duì)應(yīng)的磁盤高速緩存是空的(radix樹上沒有頁面)。隨著文件的讀寫,磁盤上的數(shù)據(jù)被載入內(nèi)存,相應(yīng)的內(nèi)存頁被掛到radix樹的相應(yīng)位置上。
如果文件被寫,則僅僅是對(duì)應(yīng)inode的radix樹上的對(duì)應(yīng)頁上的內(nèi)容被更新,并不會(huì)直接寫回磁盤。這樣被寫過,但還沒有更新到磁盤的頁稱為臟頁。
內(nèi)核線程pdflush定期將每個(gè)inode上的臟頁更新到磁盤,也會(huì)適時(shí)地將radix上的頁面回收,這些內(nèi)容都不在這里深入探討了。

當(dāng)需要讀寫的文件內(nèi)容尚未載入到對(duì)應(yīng)的radix樹時(shí),read/write的執(zhí)行過程會(huì)向底層的“通用塊層”發(fā)起讀請(qǐng)求,以便將數(shù)據(jù)讀入。
而如果文件打開時(shí)指定了O_DIRECT選項(xiàng),則表示繞開磁盤高速緩存,直接與“通用塊層”打交道。
既然磁盤高速緩存提供了有利于提高讀寫效率的緩存機(jī)制,為什么又要使用O_DIRECT選項(xiàng)來繞開它呢?一般情況下,這樣做的應(yīng)用程序會(huì)自己在用戶態(tài)維護(hù)一套更利于應(yīng)用程序使用的專用的緩存機(jī)制,用以取代內(nèi)核提供的磁盤高速緩存這種通用的緩存機(jī)制。(數(shù)據(jù)庫程序通常就會(huì)這么干。)
既然使用O_DIRECT選項(xiàng)后,文件的緩存從內(nèi)核提供的磁盤高速緩存變成了用戶態(tài)的緩存,那么打開同一文件的不同進(jìn)程將無法共享這些緩存(除非這些進(jìn)程再創(chuàng)建一個(gè)共享內(nèi)存什么的)。而如果對(duì)于同一個(gè)文件,某些進(jìn)程使用了O_DIRECT選項(xiàng),而某些又沒有呢?沒有使用O_DIRECT選項(xiàng)的進(jìn)程讀寫這個(gè)文件時(shí),會(huì)在磁盤高速緩存中留下相應(yīng)的內(nèi)容;而使用了O_DIRECT選項(xiàng)的進(jìn)程讀寫這個(gè)文件時(shí),需要先將磁盤高速緩存里面對(duì)應(yīng)本次讀寫的臟數(shù)據(jù)寫回磁盤,然后再對(duì)磁盤進(jìn)行直接讀寫。
關(guān)于O_DIRECT選項(xiàng)帶來的direct_IO的具體實(shí)現(xiàn)細(xì)節(jié),說來話長,在這里就不做介紹了。

3、Generic Block Layer,通用塊層。
linux內(nèi)核為塊設(shè)備抽象了統(tǒng)一的模型,把塊設(shè)備看作是由若干個(gè)扇區(qū)組成的數(shù)組空間。扇區(qū)是磁盤設(shè)備讀寫的最小單位,通過扇區(qū)號(hào)可以指定要訪問的磁盤扇區(qū)。
上層的讀寫請(qǐng)求在通用塊層被構(gòu)造成一個(gè)或多個(gè)bio結(jié)構(gòu),這個(gè)結(jié)構(gòu)里面描述了一次請(qǐng)求--訪問的起始扇區(qū)號(hào)?訪問多少個(gè)扇區(qū)?是讀還是寫?相應(yīng)的內(nèi)存頁有哪些、頁偏移和數(shù)據(jù)長度是多少?等等……

這里面主要有兩個(gè)問題:要訪問的扇區(qū)號(hào)從哪里來?內(nèi)存是怎么組織的?
前面說過,上層的讀寫請(qǐng)求通過文件pos可以定位到要訪問的是相應(yīng)的磁盤高速緩存的第幾個(gè)頁,而通過這個(gè)頁index就可以知道要訪問的是文件的第幾個(gè)扇區(qū),得到扇區(qū)的index。
但是,文件的第幾個(gè)扇區(qū)并不等同于磁盤上的第幾個(gè)扇區(qū),得到的扇區(qū)index還需要由特定文件系統(tǒng)提供的函數(shù)來轉(zhuǎn)換成磁盤的扇區(qū)號(hào)。文件系統(tǒng)會(huì)記載當(dāng)前磁盤上的扇區(qū)使用情況,并且對(duì)于每一個(gè)inode,它依次使用了哪些扇區(qū)。
于是,通過文件系統(tǒng)提供的特定函數(shù),上層請(qǐng)求的文件pos最終被對(duì)應(yīng)到了磁盤上的扇區(qū)號(hào)。
可見,上層的一次請(qǐng)求可能跨多個(gè)扇區(qū),可能形成多個(gè)非連續(xù)的扇區(qū)段。對(duì)應(yīng)于每個(gè)扇區(qū)段,一個(gè)bio結(jié)構(gòu)被構(gòu)造出來。而由于塊設(shè)備一般都支持一次性訪問若干個(gè)連續(xù)的扇區(qū),所以一個(gè)扇區(qū)段(不止一個(gè)扇區(qū))可以包含在代表一次塊設(shè)備IO請(qǐng)求的一個(gè)bio結(jié)構(gòu)中。

接下來談?wù)剝?nèi)存的組織。既然上層的一次讀寫請(qǐng)求可能跨多個(gè)扇區(qū),它也可能跨越磁盤高速緩存上的多個(gè)頁。于是,一個(gè)bio里面包含的扇區(qū)請(qǐng)求可能會(huì)對(duì)應(yīng)一組內(nèi)存頁。而這些頁是單獨(dú)分配的,內(nèi)存地址很可能不連續(xù)。
那么,既然bio描述的是一次塊設(shè)備請(qǐng)求,塊設(shè)備能夠一次性訪問一組連續(xù)的扇區(qū),但是能夠一次性對(duì)一組非連續(xù)的內(nèi)存地址進(jìn)行存取嗎?
塊設(shè)備一般是通過DMA,將塊設(shè)備上一組連續(xù)的扇區(qū)上的數(shù)據(jù)拷貝到一組連續(xù)的內(nèi)存頁面上(或?qū)⒁唤M連續(xù)的內(nèi)存頁面上的數(shù)據(jù)拷貝到塊設(shè)備上一組連續(xù)的扇區(qū)),DMA本身一般是不支持一次性訪問非連續(xù)的內(nèi)存頁面的。
但是某些體系結(jié)構(gòu)包含了io-mmu。就像通過mmu可以將一組非連續(xù)的物理頁面映射成連續(xù)的虛擬地址一樣,對(duì)io-mmu進(jìn)行編程,可以讓DMA將一組非連續(xù)的物理內(nèi)存看作連續(xù)的。所以,即使一個(gè)bio包含了非連續(xù)的多段內(nèi)存,它也是有可能可以在一次DMA中完成的。當(dāng)然,不是所有的體系結(jié)構(gòu)都支持io-mmu,所以一個(gè)bio也可能在后面的設(shè)備驅(qū)動(dòng)程序中被拆分成多個(gè)設(shè)備請(qǐng)求。

每個(gè)被構(gòu)造的bio結(jié)構(gòu)都會(huì)分別被提交,提交到底層的IO調(diào)度器中。

4、I/O Scheduler Layer,IO調(diào)度器。
我們知道,磁盤是通過磁頭來讀寫數(shù)據(jù)的,磁頭在定位扇區(qū)的過程中需要做機(jī)械的移動(dòng)。相比于電和磁的傳遞,機(jī)械運(yùn)動(dòng)是非常慢速的,這也就是磁盤為什么那么慢的主要原因。
IO調(diào)度器要做的事情就是在完成現(xiàn)有請(qǐng)求的前提下,讓磁頭盡可能少移動(dòng),從而提高磁盤的讀寫效率。最有名的就是“電梯算法”。
在IO調(diào)度器中,上層提交的bio被構(gòu)造成request結(jié)構(gòu),一個(gè)request結(jié)構(gòu)包含了一組順序的bio。而每個(gè)物理設(shè)備會(huì)對(duì)應(yīng)一個(gè)request_queue,里面順序存放著相關(guān)的request。
新的bio可能被合并到request_queue中已有的request結(jié)構(gòu)中(甚至合并到已有的bio中),也可能生成新的request結(jié)構(gòu)并插入到request_queue的適當(dāng)位置上。具體怎么合并、怎么插入,取決于設(shè)備驅(qū)動(dòng)程序選擇的IO調(diào)度算法。大體上可以把IO調(diào)度算法就想象成“電梯算法”,盡管實(shí)際的IO調(diào)度算法有所改進(jìn)。
除了類似“電梯算法”的IO調(diào)度算法,還有“none”算法,這實(shí)際上是沒有算法,也可以說是“先來先服務(wù)算法”。因?yàn)楝F(xiàn)在很多塊設(shè)備已經(jīng)能夠很好地支持隨機(jī)訪問了(比如固態(tài)磁盤、flash閃存),使用“電梯算法”對(duì)于它們沒有什么意義。

IO調(diào)度器除了改變請(qǐng)求的順序,還可能延遲觸發(fā)對(duì)請(qǐng)求的處理。因?yàn)橹挥挟?dāng)請(qǐng)求隊(duì)列有一定數(shù)目的請(qǐng)求時(shí),“電梯算法”才能發(fā)揮其功效,否則極端情況下它將退化成“先來先服務(wù)算法”。
這是通過對(duì)request_queue的plug/unplug來實(shí)現(xiàn)的,plug相當(dāng)于停用,unplug相當(dāng)于恢復(fù)。請(qǐng)求少時(shí)將request_queue停用,當(dāng)請(qǐng)求達(dá)到一定數(shù)目,或者request_queue里最“老”的請(qǐng)求已經(jīng)等待很長一段時(shí)間了,這時(shí)候才將request_queue恢復(fù)。
在request_queue恢復(fù)的時(shí)候,驅(qū)動(dòng)程序提供的回調(diào)函數(shù)將被調(diào)用,于是驅(qū)動(dòng)程序開始處理request_queue。
一般來說,read/write系統(tǒng)調(diào)用到這里就返回了。返回之后可能等待(同步)或是繼續(xù)干其他事(異步)。而返回之前會(huì)在任務(wù)隊(duì)列里面添加一個(gè)任務(wù),而處理該任務(wù)隊(duì)列的內(nèi)核線程將來會(huì)執(zhí)行request_queue的unplug操作,以觸發(fā)驅(qū)動(dòng)程序處理請(qǐng)求。

5、Device Driver,設(shè)備驅(qū)動(dòng)程序。
到了這里,設(shè)備驅(qū)動(dòng)程序要做的事情就是從request_queue里面取出請(qǐng)求,然后操作硬件設(shè)備,逐個(gè)去執(zhí)行這些請(qǐng)求。

除了處理請(qǐng)求,設(shè)備驅(qū)動(dòng)程序還要選擇IO調(diào)度算法,因?yàn)樵O(shè)備驅(qū)動(dòng)程序最知道設(shè)備的屬性,知道用什么樣的IO調(diào)度算法最合適。甚至于,設(shè)備驅(qū)動(dòng)程序可以將IO調(diào)度器屏蔽掉,而直接對(duì)上層的bio進(jìn)行處理。(當(dāng)然,設(shè)備驅(qū)動(dòng)程序也可實(shí)現(xiàn)自己的IO調(diào)度算法。)
可以說,IO調(diào)度器是內(nèi)核提供給設(shè)備驅(qū)動(dòng)程序的一組方法。用與不用、使用怎樣的方法,選擇權(quán)在于設(shè)備驅(qū)動(dòng)程序。

于是,對(duì)于支持隨機(jī)訪問的塊設(shè)備,驅(qū)動(dòng)程序除了選擇“none”算法,還有一種更直接的做法,就是注冊(cè)自己的bio提交函數(shù)。這樣,bio生成后,并不會(huì)使用通用的提交函數(shù),被提交到IO調(diào)度器,而是直接被驅(qū)動(dòng)程序處理。
但是,如果設(shè)備比較慢的話,bio的提交可能會(huì)阻塞較長時(shí)間。所以這種做法一般被基于內(nèi)存的“塊設(shè)備”驅(qū)動(dòng)使用(當(dāng)然,這樣的塊設(shè)備是由驅(qū)動(dòng)程序虛擬的)。



下面大致介紹一下read/write的執(zhí)行流程:
sys_read。通過fd得到對(duì)應(yīng)的file結(jié)構(gòu),然后調(diào)用vfs_read;
vfs_read。各種權(quán)限及文件鎖的檢查,然后調(diào)用file->f_op->read(若不存在則調(diào)用do_sync_read)。file->f_op是從對(duì)應(yīng)的inode->i_fop而來,而inode->i_fop是由對(duì)應(yīng)的文件系統(tǒng)類型在生成這個(gè)inode時(shí)賦予的。file->f_op->read很可能就等同于do_sync_read;
do_sync_read。f_op->read是完成一次同步讀,而f_op->aio_read完成一次異步讀。do_sync_read則是利用f_op->aio_read這個(gè)異步讀操作來完成同步讀,也就是在發(fā)起一次異步讀之后,如果返回值是-EIOCBQUEUED,則進(jìn)程睡眠,直到讀完成即可。但實(shí)際上對(duì)于磁盤文件的讀,f_op->aio_read一般不會(huì)返回-EIOCBQUEUED,除非是設(shè)置了O_DIRECT標(biāo)志aio_read,或者是對(duì)于一些特殊的文件系統(tǒng)(如nfs這樣的網(wǎng)絡(luò)文件系統(tǒng));
f_op->aio_read。這個(gè)函數(shù)通常是由generic_file_aio_read或者其封裝來實(shí)現(xiàn)的;
generic_file_aio_read。一次異步讀可能包含多個(gè)讀操作(對(duì)應(yīng)于readv系統(tǒng)調(diào)用),對(duì)于其中的每一個(gè),調(diào)用do_generic_file_read;
do_generic_file_read。主要流程是在radix樹里面查找是否存在對(duì)應(yīng)的page,且該頁可用。是則從page里面讀出所需的數(shù)據(jù),然后返回,否則通過file->f_mapping->a_ops->readpage去讀這個(gè)頁;
file->f_mapping是從對(duì)應(yīng)inode->i_mapping而來,inode->i_mapping->a_ops是由對(duì)應(yīng)的文件系統(tǒng)類型在生成這個(gè)inode時(shí)賦予的。而各個(gè)文件系統(tǒng)類型提供的a_ops->readpage函數(shù)一般是mpage_readpage函數(shù)的封裝;
mpage_readpage。調(diào)用do_mpage_readpage構(gòu)造一個(gè)bio,再調(diào)用mpage_bio_submit將其提交;
do_mpage_readpage。根據(jù)page->index確定需要讀的磁盤扇區(qū)號(hào),然后構(gòu)造一組bio。其中需要使用文件系統(tǒng)類型提供的get_block函數(shù)來對(duì)應(yīng)需要讀取的磁盤扇區(qū)號(hào);
mpage_bio_submit。設(shè)置bio的結(jié)束回調(diào)bio->bi_end_io為mpage_end_io_read,然后調(diào)用submit_bio提交這組bio;
submit_bio。調(diào)用generic_make_request將bio提交到磁盤驅(qū)動(dòng)維護(hù)的請(qǐng)求隊(duì)列中;
generic_make_request。一個(gè)包裝函數(shù),對(duì)于每一個(gè)bio,調(diào)用__generic_make_request;
__generic_make_request。獲取bio對(duì)應(yīng)的塊設(shè)備文件對(duì)應(yīng)的磁盤對(duì)象的請(qǐng)求隊(duì)列bio->bi_bdev->bd_disk->queue,調(diào)用q->make_request_fn將bio添加到隊(duì)列;
q->make_request_fn。設(shè)備驅(qū)動(dòng)程序在其初始化時(shí)會(huì)初始化這個(gè)request_queue結(jié)構(gòu),并且設(shè)置q->make_request_fn和q->request_fn(這個(gè)下面就會(huì)用到)。前者用于將一個(gè)bio組裝成request添加到request_queue,后者用于處理request_queue中的請(qǐng)求。一般情況下,設(shè)備驅(qū)動(dòng)通過調(diào)用blk_init_queue來初始化request_queue,q->request_fn需要給定,而q->make_request_fn使用了默認(rèn)的__make_request;
__make_request。會(huì)根據(jù)不同的調(diào)度算法來決定如何添加bio,生成對(duì)應(yīng)的request結(jié)構(gòu)加入request_queue結(jié)構(gòu)中,并且決定是否調(diào)用q->request_fn,或是在kblockd_workqueue任務(wù)隊(duì)列里面添加一個(gè)任務(wù),等kblockd內(nèi)核線程來調(diào)用q->request_fn;
q->request_fn。由驅(qū)動(dòng)程序定義的函數(shù),負(fù)責(zé)從request_queue里面取出request進(jìn)行處理。從添加bio到request被取出,若干的請(qǐng)求已經(jīng)被IO調(diào)度算法整理過了。驅(qū)動(dòng)程序負(fù)責(zé)根據(jù)request結(jié)構(gòu)里面的描述,將實(shí)際物理設(shè)備里面的數(shù)據(jù)讀到內(nèi)存中。當(dāng)驅(qū)動(dòng)程序完成一個(gè)request時(shí),會(huì)調(diào)用end_request(或類似)函數(shù),以結(jié)束這個(gè)request;
end_request。完成request的收尾工作,并且會(huì)調(diào)用對(duì)應(yīng)的bio的的結(jié)束方法bio->bi_end_io,即前面設(shè)置的mpage_end_io_read;
mpage_end_io_read。如果page已更新則設(shè)置其up-to-date標(biāo)記,并為page解鎖,喚醒等待page解鎖的進(jìn)程。最后釋放bio對(duì)象;

sys_write。跟sys_read一樣,對(duì)應(yīng)的vfs_write、do_sync_writef_op->aio_write、generic_file_aio_write被順序調(diào)用;
generic_file_aio_write。調(diào)用__generic_file_aio_write_nolock來進(jìn)行寫的處理,將數(shù)據(jù)寫到磁盤高速緩存中。寫完成之后,判斷如果文件打開時(shí)使用了O_SYNC標(biāo)記,則再調(diào)用sync_page_range將寫入到磁盤高速緩存中的數(shù)據(jù)同步到磁盤(只同步文件頭信息);
__generic_file_aio_write_nolock。進(jìn)行一些檢查之后,調(diào)用generic_file_buffered_write;
generic_file_buffered_write。調(diào)用generic_perform_write執(zhí)行寫,寫完成之后,判斷如果文件打開時(shí)使用了O_SYNC標(biāo)記,則再調(diào)用generic_osync_inode將寫入到磁盤高速緩存中的數(shù)據(jù)同步到磁盤(同步文件頭信息和文件內(nèi)容);
generic_perform_write。一次異步寫可能包含多個(gè)寫操作(對(duì)應(yīng)于writev系統(tǒng)調(diào)用),對(duì)于其中牽涉的每一個(gè)page,調(diào)用file->f_mapping->a_ops->write_begin準(zhǔn)備好需要寫的磁盤高速緩存頁面,然后將需要寫的數(shù)據(jù)拷入其中,最后調(diào)用file->f_mapping->a_ops->write_end完成寫;
file->f_mapping是從對(duì)應(yīng)inode->i_mapping而來,inode->i_mapping->a_ops是由對(duì)應(yīng)的文件系統(tǒng)類型在生成這個(gè)inode時(shí)賦予的。而各個(gè)文件系統(tǒng)類型提供的file->f_mapping->a_ops->write_begin函數(shù)一般是block_write_begin函數(shù)的封裝、file->f_mapping->a_ops->write_end函數(shù)一般是generic_write_end函數(shù)的封裝;
block_write_begin。調(diào)用grab_cache_page_write_begin在radix樹里面查找要被寫的page,如果不存在則創(chuàng)建一個(gè)。調(diào)用__block_prepare_write為這個(gè)page準(zhǔn)備一組buffer_head結(jié)構(gòu),用于描述組成這個(gè)page的數(shù)據(jù)塊(利用其中的信息,可以生成對(duì)應(yīng)的bio結(jié)構(gòu));
generic_write_end。調(diào)用block_write_end提交寫請(qǐng)求,然后設(shè)置page的dirty標(biāo)記;
block_write_end。調(diào)用__block_commit_write為page中的每一個(gè)buffer_head結(jié)構(gòu)設(shè)置dirty標(biāo)記;
至此,write調(diào)用就要返回了。如果文件打開時(shí)使用了O_SYNC標(biāo)記,sync_page_range或generic_osync_inode將被調(diào)用。否則write就結(jié)束了,等待pdflush內(nèi)核線程發(fā)現(xiàn)radix樹上的臟頁,并最終調(diào)用到do_writepages寫回這些臟頁;
sync_page_range也是調(diào)用generic_osync_inode來實(shí)現(xiàn)的,而generic_osync_inode最終也會(huì)調(diào)用到do_writepages;
do_writepages。調(diào)用inode->i_mapping->a_ops->writepages,而后者一般是mpage_writepages函數(shù)的包裝;
mpage_writepages。檢查radix樹中需要寫回的page,對(duì)每一個(gè)page調(diào)用__mpage_writepage;
__mpage_writepage。這里也是構(gòu)造bio,然后調(diào)用mpage_bio_submit來進(jìn)行提交;
后面的流程跟read幾乎就一樣了……
關(guān)閉窗口