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

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

深度剖析Linux塊設(shè)備IO子系統(tǒng)(一)_驅(qū)動模型(秒懂)

2022-04-01 17:41 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿
  • 塊設(shè)備是Linux三大設(shè)備之一,其驅(qū)動模型主要針對磁盤,F(xiàn)lash等存儲類設(shè)備,塊設(shè)備(blockdevice)是一種具有一定結(jié)構(gòu)的隨機(jī)存取設(shè)備,對這種設(shè)備的讀寫是按塊(所以叫塊設(shè)備)進(jìn)行的,他使用緩沖區(qū)來存放暫時的數(shù)據(jù),待條件成熟后,從緩存一次性寫入設(shè)備或者從設(shè)備一次性讀到緩沖區(qū)。作為存儲設(shè)備,塊設(shè)備驅(qū)動的核心問題就是哪些page->segment->block->sector與哪些sector有數(shù)據(jù)交互,本文以3.14為藍(lán)本,探討內(nèi)核中的塊設(shè)備驅(qū)動模型。

框架

  • 下圖是Linux中的塊設(shè)備模型示意圖,應(yīng)用層程序有兩種方式訪問一個塊設(shè)備:/dev和文件系統(tǒng)掛載點(diǎn),前者和字符設(shè)備一樣,通常用于配置,后者就是我們mount之后通過文件系統(tǒng)直接訪問一個塊設(shè)備了。

  1. read()系統(tǒng)調(diào)用最終會調(diào)用一個適當(dāng)?shù)腣FS函數(shù)(read()-->sys_read()-->vfs_read()),將文件描述符fd和文件內(nèi)的偏移量offset傳遞給它。

  2. VFS會判斷這個SCI的處理方式,如果訪問的內(nèi)容已經(jīng)被緩存在RAM中(磁盤高速緩存機(jī)制),就直接訪問,否則從磁盤中讀取

  3. 為了從物理磁盤中讀取,內(nèi)核依賴映射層mapping layer,即上圖中的磁盤文件系統(tǒng)

  4. 確定該文件所在文件系統(tǒng)的塊的大小,并根據(jù)文件塊的大小計(jì)算所請求數(shù)據(jù)的長度。本質(zhì)上,文件被拆成很多塊,因此內(nèi)核需要確定請求數(shù)據(jù)所在的塊

  5. 映射層調(diào)用一個具體的文件系統(tǒng)的函數(shù),這個層的函數(shù)會訪問文件的磁盤節(jié)點(diǎn),然后根據(jù)邏輯塊號確定所請求數(shù)據(jù)在磁盤上的位置。


  1. 內(nèi)核利用通用塊層(generic block layer)啟動IO操作來傳達(dá)所請求的數(shù)據(jù),通常,一個IO操作只針對磁盤上一組連續(xù)的塊。

  2. IO調(diào)度程序根據(jù)預(yù)先定義的內(nèi)核策略將待處理的IO進(jìn)行重排和合并

  3. 塊設(shè)備驅(qū)動程序向磁盤控制器硬件接口發(fā)送適當(dāng)?shù)闹噶?,進(jìn)行實(shí)際的數(shù)據(jù)操作



塊設(shè)備 VS 字符設(shè)備

  • 作為一種存儲設(shè)備,和字符設(shè)備相比,塊設(shè)備有以下幾種不同:



  • 此外,大多數(shù)情況下,磁盤控制器都是直接使用DMA方式進(jìn)行數(shù)據(jù)傳送。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)?



?


?

IO調(diào)度

  • 就是電梯算法。我們知道,磁盤是的讀寫是通過機(jī)械性的移動磁頭來實(shí)現(xiàn)讀寫的,理論上磁盤設(shè)備滿足塊設(shè)備的隨機(jī)讀寫的要求,但是出于節(jié)約磁盤,提高效率的考慮,我們希望當(dāng)磁頭處于某一個位置的時候,一起將最近需要寫在附近的數(shù)據(jù)寫入,而不是這寫一下,那寫一下然后再回來,IO調(diào)度就是將上層發(fā)下來的IO請求的順序進(jìn)行重新排序以及對多個請求進(jìn)行合并,這樣就可以實(shí)現(xiàn)上述的提高效率、節(jié)約磁盤的目的。這種解決問題的思路使用電梯算法,一個運(yùn)行中的電梯,一個人20樓->1樓,另外一個人15->5樓,電梯不會先將第一個人送到1樓再去15樓接第二個人將其送到5樓,而是從20樓下來,到15樓的時候停下接人,到5樓將第二個放下,最后到達(dá)1樓,一句話,電梯算法最終服務(wù)的優(yōu)先順序并不按照按按鈕的先后順序。Linux內(nèi)核中提供了下面的幾種電梯算法來實(shí)現(xiàn)IO調(diào)度:

  1. No-op I/O scheduler只實(shí)現(xiàn)了簡單的FIFO的,只進(jìn)行最簡單的合并,比較適合基于Flash的存儲

  2. Anticipatory I/O scheduler推遲IO請求(大約幾個微秒),以期能對他們進(jìn)行排序,獲得更高效率

  3. Deadline I/O scheduler試圖把每次請求的延遲降到最低,同時也會對BIO重新排序,特別適用于讀取較多的場合,比如數(shù)據(jù)庫

  4. CFQ I/O scheduler為系統(tǒng)內(nèi)所有的任務(wù)分配均勻的IO帶寬,提供一個公平的工作環(huán)境,在多媒體環(huán)境中,能保證音視頻及時從磁盤中讀取數(shù)據(jù),是當(dāng)前內(nèi)核默認(rèn)的調(diào)度器

  • 我們可以通過內(nèi)核傳參的方式指定使用的調(diào)度算法

  • 或者,使用如下命令改變內(nèi)核調(diào)度算法

  • Page->Segment->Block->Sector VS Sector

  • VS左面的是數(shù)據(jù)交互中的內(nèi)存部分,Page就是內(nèi)存映射的最小單位; Segment就是一個Page中我們要操作的一部分,由若干個相鄰的塊組成; Block是邏輯上的進(jìn)行數(shù)據(jù)存取的最小單位,是文件系統(tǒng)的抽象,邏輯塊的大小是在格式化的時候確定的, 一個 Block 最多僅能容納一個文件(即不存在多個文件同一個block的情況)。如果一個文件比block小,他也會占用一個block,因而block中空余的空間會浪費(fèi)掉。而一個大文件,可以占多個甚至數(shù)十個成百上千萬的block。Linux內(nèi)核要求 Block_Size = Sector_Size * (2的n次方),并且Block_Size <= 內(nèi)存的Page_Size(頁大小), 如ext2 fs的block缺省是4k。若block太大,則存取小文件時,有空間浪費(fèi)的問題;若block太小,則硬盤的 Block 數(shù)目會大增,而造成 inode 在指向 block 的時候的一些搜尋時間的增加,又會造成大文件讀寫方面的效率較差,block是VFS和文件系統(tǒng)傳送數(shù)據(jù)的基本單位。block對應(yīng)磁盤上的一個或多個相鄰的扇區(qū),而VFS將其看成是一個單一的數(shù)據(jù)單元,塊設(shè)備的block的大小不是唯一的,創(chuàng)建一個磁盤文件系統(tǒng)時,管理員可以選擇合適的扇區(qū)的大小,同一個磁盤的幾個分區(qū)可以使用不同的塊大小。此外,對塊設(shè)備文件的每次讀或?qū)懖僮魇且环N"原始"訪問,因?yàn)樗@過了磁盤文件系統(tǒng),內(nèi)核通過使用最大的塊(4096)執(zhí)行該操作。Linux對內(nèi)存中的block會被進(jìn)一步劃分為Sector,Sector是硬件設(shè)備傳送數(shù)據(jù)的基本單位,這個Sector就是512byte,和物理設(shè)備上的概念不一樣,如果實(shí)際的設(shè)備的sector不是512byte,而是4096byte(eg SSD),那么只需要將多個內(nèi)核sector對應(yīng)一個設(shè)備sector即可


  • VS右邊是物理上的概念,磁盤中一個Sector是512Byte,SSD中一個Sector是4K

  • 核心結(jié)構(gòu)與方法簡述

  • 核心結(jié)構(gòu)

  • gendisk是一個物理磁盤或分區(qū)在內(nèi)核中的描述

  • block_device_operations描述磁盤的操作方法集,block_device_operations之于gendisk,類似于file_operations之于cdev

  • request_queue對象表示針對一個gendisk對象的所有請求的隊(duì)列,是相應(yīng)gendisk對象的一個域

  • request表示經(jīng)過IO調(diào)度之后的針對一個gendisk(磁盤)的一個"請求",是request_queue的一個節(jié)點(diǎn)。多個request構(gòu)成了一個request_queue

  • bio表示應(yīng)用程序?qū)σ粋€gendisk(磁盤)原始的訪問請求,一個bio由多個bio_vec,多個bio經(jīng)過IO調(diào)度和合并之后可以形成一個request。

  • bio_vec描述的應(yīng)用層準(zhǔn)備讀寫一個gendisk(磁盤)時需要使用的內(nèi)存頁page的一部分,即上文中的"段",多個bio_vec和bio_iter形成一個bio

  • bvec_iter描述一個bio_vec中的一個sector信息


  • 核心方法

  • set_capacity()設(shè)置gendisk對應(yīng)的磁盤的物理參數(shù)

  • blk_init_queue()分配+初始化+綁定一個有IO調(diào)度的gendisk的requst_queue,處理函數(shù)是void (request_fn_proc) (struct request_queue *q);類型

  • blk_alloc_queue() 分配+初始化一個沒有IO調(diào)度的gendisk的request_queue,

  • blk_queue_make_request()綁定處理函數(shù)到一個沒有IO調(diào)度的request_queue,處理函數(shù)函數(shù)是void (make_request_fn) (struct request_queue *q, struct bio *bio);類型

  • __rq_for_each_bio()遍歷一個request中的所有的bio

  • bio_for_each_segment()遍歷一個bio中所有的segment

  • rq_for_each_segment()遍歷一個request中的所有的bio中的所有的segment 最后三個遍歷算法都是用在request_queue綁定的處理函數(shù)中,這個函數(shù)負(fù)責(zé)對上層請求的處理。

  • 核心結(jié)構(gòu)與方法詳述

  • gendisk

  • 同樣是面向?qū)ο蟮脑O(shè)計(jì)方法,Linux內(nèi)核使用gendisk對象描述一個系統(tǒng)的中的塊設(shè)備,類似于Windows系統(tǒng)中的磁盤分區(qū)和物理磁盤的關(guān)系,OS眼中的磁盤都是邏輯磁盤,也就是一個磁盤分區(qū),一個物理磁盤可以對應(yīng)多個磁盤分區(qū),在Linux中,這個gendisk就是用來描述一個邏輯磁盤,也就是一個磁盤分區(qū)。

  • struct gendisk

  • --169-->驅(qū)動的主設(shè)備號

  • --170-->第一個次設(shè)備號

  • --171-->次設(shè)備號的數(shù)量,即允許的最大分區(qū)的數(shù)量,1表示不允許分區(qū)

  • --174-->設(shè)備名稱

  • --185-->分區(qū)表數(shù)組首地址

  • --186-->第一個分區(qū),相當(dāng)于part_tbl->part[0]

  • --188-->操作方法集指針

  • --189-->請求對象指針

  • --190-->私有數(shù)據(jù)指針

  • --193-->表示這是一個設(shè)備


gendisk是一個動態(tài)分配的結(jié)構(gòu)體,所以不要自己手動來分配,而是使用內(nèi)核相應(yīng)的API來分配,其中會做一些初始化的工作

上面幾個API是一個塊設(shè)備驅(qū)動中必不可少的部分,下面的兩個主要是用來內(nèi)核對于設(shè)備管理用的,通常不要驅(qū)動來實(shí)現(xiàn)

  • 這兩個API最終回調(diào)用kobject *get_disk() 和kobject_put()來實(shí)現(xiàn)對設(shè)備的引用計(jì)數(shù)

block_device_operations

  • 和字符設(shè)備一樣,如果使用/dev接口訪問塊設(shè)備,最終就會回調(diào)這個操作方法集的注冊函數(shù)

struct block_device_operations

--1559-->當(dāng)應(yīng)用層打開一個塊設(shè)備的時候被回調(diào)?

--1560-->當(dāng)應(yīng)用層關(guān)閉一個塊設(shè)備的時候被回調(diào)?

--1562-->相當(dāng)于file_operations里的compat_ioctl,不過塊設(shè)備的ioctl包含大量的標(biāo)準(zhǔn)操作,所以在這個接口實(shí)現(xiàn)的操作很少?

--1567-->在移動塊設(shè)備中測試介質(zhì)是否改變的方法,已經(jīng)過時,同樣的功能被check_event()實(shí)現(xiàn)?

--1571-->即get geometry,獲取驅(qū)動器的幾何信息,獲取到的信息會被填充在一個hd_geometry結(jié)構(gòu)中?

--1574-->模塊所屬,通常填THIS_MODULE

request_queue

  • 每一個gendisk對象都有一個request_queue對象,前文說過,塊設(shè)備有兩種訪問接口,一種是/dev下,一種是通過文件系統(tǒng),后者經(jīng)過IO調(diào)度在這個gendisk->request_queue上增加請求,最終回調(diào)與request_queue綁定的處理函數(shù),將這些請求向下變成具體的硬件操作

struct request_queue

--298-->請求隊(duì)列的鏈表頭?

--300-->請求隊(duì)列使用的IO調(diào)度算法, 通過內(nèi)核啟動參數(shù)來選擇: kernel elevator=deadline request_queue_t和gendisk一樣需要使用內(nèi)核API來分配并初始化,里面大量的成員不要直接操作, 此外, 請求隊(duì)列如果要正常工作還需要綁定到一個處理函數(shù)中, 當(dāng)請求隊(duì)列不為空時, 處理函數(shù)會被回調(diào), 這就是塊設(shè)備驅(qū)動中處理請求的核心部分!

  • 從驅(qū)動模型的角度來說, 塊設(shè)備主要分為兩類需要IO調(diào)度的和不需要IO調(diào)度的, 前者包括磁盤, 光盤等, 后者包括Flash, SD卡等, 為了保證模型的統(tǒng)一性 , Linux中對這兩種使用同樣的模型但是通過不同的API來完成上述的初始化和綁定

有IO調(diào)度類設(shè)備API

無IO調(diào)度類設(shè)備API

共用API

  • 針對請求隊(duì)列的操作是塊設(shè)備的一個核心任務(wù), 其實(shí)質(zhì)就是對請求隊(duì)列操作函數(shù)的編寫, 這個函數(shù)的主要功能就是從請求隊(duì)列中獲取請求并根據(jù)請求進(jìn)行相應(yīng)的操作 內(nèi)核中已經(jīng)提供了大量的API供該函數(shù)使用

request

struct request

--98-->將這個request掛接到鏈表的節(jié)點(diǎn)?

--104-->這個request從屬的request_queue?

--117-->組成這個request的bio鏈表的頭指針?

--118-->組成這個request的bio鏈表的尾指針?

--120-->內(nèi)核hash表頭指針

bio

  • bio用來描述單一的I/O請求,它記錄了一次I/O操作所必需的相關(guān)信息,如用于I/O操作的數(shù)據(jù)緩存位置,,I/O操作的塊設(shè)備起始扇區(qū),是讀操作還是寫操作等等

  • struct bio

  • --47-->指向鏈表中下一個bio的指針bi_next --50-->bi_rw低位表示讀寫READ/WRITE, 高位表示優(yōu)先級

  • --90-->bio對象包含bio_vec對象的數(shù)目

  • --91-->這個bio能承載的最大的io_vec的數(shù)目

  • --95-->該bio描述的第一個io_vec --104-->表示這個bio包含的bio_vec變量的數(shù)組,即這個bio對應(yīng)的某一個page中的一"段"內(nèi)存

  • bio_vec

  • 描述指定page中的一塊連續(xù)的區(qū)域,在bio中描述的就是一個page中的一個"段"(segment)

  • struct bio_vec

  • --26-->描述的page

  • --27-->描述的長度

  • --28-->描述的起始地址偏移量

  • bio_iter

  • 用于記錄當(dāng)前bvec被處理的情況,用于遍歷bio

  • __rq_for_each_bio()

  • 遍歷一個request中的每一個bio

bio_for_each_segment()

  • 遍歷一個bio中的每一個segment

rq_for_each_segment()

  • 遍歷一個request中的每一個segment

小結(jié)

  • 遍歷request_queue,綁定函數(shù)的一個必要的工作就是將request_queue中的數(shù)據(jù)取出, 所以遍歷是必不可少的, 針對有IO調(diào)度的設(shè)備, 我們需要從中提取請求再繼續(xù)操作, 對于沒有IO調(diào)度的設(shè)備, 我們可以直接從request_queue中提取bio進(jìn)行操作, 這兩種處理函數(shù)的接口就不一樣,下面的例子是對LDD3中的代碼進(jìn)行了修剪而來的,相應(yīng)的API使用的是3.14版本,可以看出這兩種模式的使用方法的不同。


深度剖析Linux塊設(shè)備IO子系統(tǒng)(一)_驅(qū)動模型(秒懂)的評論 (共 條)

分享到微博請遵守國家法律
长岛县| 黔南| 龙江县| 印江| 延边| 股票| 江源县| 宁津县| 民权县| 婺源县| 磐石市| 江西省| 宁都县| 五寨县| 萝北县| 阿图什市| 河北区| 西乌珠穆沁旗| 卫辉市| 泽州县| 庆安县| 班玛县| 甘洛县| 堆龙德庆县| 隆回县| 周宁县| 蓬莱市| 宁乡县| 沧州市| 安泽县| 涿鹿县| 于田县| 贞丰县| 汪清县| 汉川市| 新和县| 雷州市| 汉阴县| 兴安盟| 江川县| 澜沧|