塊裝置之三
阿新 • • 發佈:2018-11-05
一、I/O排程器
1.1 資料結構
在將請求提交給塊裝置時,核心提供了各種排程策略,這些排程器用於重排和排程I/O請求以獲得最優的效能。I/O排程器在核心中被稱為elevator。核心使用瞭如下資料結構來實現和管理I/O排程器:struct elevator_type { /* managed by elevator core */ struct kmem_cache *icq_cache; /* fields provided by elevator implementation */ struct elevator_ops ops; size_t icq_size; /* see iocontext.h */ size_t icq_align; /* ditto */ struct elv_fs_entry *elevator_attrs; char elevator_name[ELV_NAME_MAX]; struct module *elevator_owner; /* managed by elevator core */ char icq_cache_name[ELV_NAME_MAX + 5]; /* elvname + "_io_cq" */ struct list_head list; };
- elevator_type用於表示I/O排程器,核心中所有的I/O排程器都被儲存在全域性連結串列elv_list中,list用做連結串列元素。
- icq_cache:icq快取,由I/O排程器核心建立、維護和使用
- ops:排程器的操作函式集
- icq_size:icq大小
- icq_align:icq對齊方式
- elevator_attrs:該I/O排程器在sys檔案系統中的屬性檔案
- elevator_name:該排程器的名字
- icq_cache_name:icq快取的名字
struct elevator_ops { elevator_merge_fn *elevator_merge_fn; elevator_merged_fn *elevator_merged_fn; elevator_merge_req_fn *elevator_merge_req_fn; elevator_allow_merge_fn *elevator_allow_merge_fn; elevator_bio_merged_fn *elevator_bio_merged_fn; elevator_dispatch_fn *elevator_dispatch_fn; elevator_add_req_fn *elevator_add_req_fn; elevator_activate_req_fn *elevator_activate_req_fn; elevator_deactivate_req_fn *elevator_deactivate_req_fn; elevator_completed_req_fn *elevator_completed_req_fn; elevator_request_list_fn *elevator_former_req_fn; elevator_request_list_fn *elevator_latter_req_fn; elevator_init_icq_fn *elevator_init_icq_fn; /* see iocontext.h */ elevator_exit_icq_fn *elevator_exit_icq_fn; /* ditto */ elevator_set_req_fn *elevator_set_req_fn; elevator_put_req_fn *elevator_put_req_fn; elevator_may_queue_fn *elevator_may_queue_fn; elevator_init_fn *elevator_init_fn; elevator_exit_fn *elevator_exit_fn; };
- elevator_merge_fn:返回bio應該被新增(合併)到指定的請求的什麼位置
- elevator_allow_merge_fn:判斷新的bio是否可以和指定的請求合併,即新增到一個已存的請求中,它和elevator_merge_fn的功能可能在一個函式中提供,具體的要看排程器的實現。
- elevator_merged_fn:在將兩個請求進行合併後呼叫,用於進行清理,以釋放一些管理資料結構
- elevator_merge_req_fn:將兩個請求合併為一個請求。當將一個bio合併到一個請求後,被合併到的那個請求可能就可以和它之前或者之後的請求進行合併(是之前還是之後,取決於新的bio新增到請求的方式)。因此在將一個新的bio合併到一個已存的請求之後,就要嘗試進行請求的合併。
- elevator_bio_merged_fn:如果提供了,則它在新的bio被新增到一個已存的請求後被呼叫。bio和請求的合併由bio_attempt_front_merge或者bio_attempt_back_merge處理,而請求的合併由attempt_front_merge或者attempt_back_merge處理
- elevator_dispatch_fn:從排程器取得下一個應該被處理的請求
- elevator_add_req_fn:向排程器新增新的請求
- elevator_activate_req_fn:啟用請求佇列上的請一個求
- elevator_deactivate_req_fn:deactivate請求佇列上的一個請求
- elevator_completed_req_fn:完成請求佇列上一個請求的處理時對該請求呼叫該函式
- elevator_former_req_fn:獲取請求佇列上在指定的請求之前的請求
- elevator_latter_req_fn:獲取請求佇列上在指定的請求之後的請求
- elevator_set_req_fn:建立新的請求時呼叫,相當於建構函式的一部分。get_request->blk_alloc_request
- elevator_put_req_fn:釋放請求時呼叫,相當於解構函式的一部分.blk_free_request->elv_put_request
- elevator_may_queue_fn:當從請求建立了一個新的請求時被呼叫,用於判斷建立是否能夠建立成功以及請求是否應該被排隊。排程器可以實現一些限制來控制請求佇列上的請求數目,這裡就是實現限制檢查的地方。
- elevator_init_fn:初始化排程器時呼叫,即排程器的建構函式
- elevator_exit_fn:刪除排程器時呼叫,即排程器的解構函式
1.2 核心I/O排程器
在初始化請求佇列時,核心會用elevator_init來初始化排程器及其關聯的排程佇列。隨後該核心實現了四種I/O排程器,分別為:
1.2.1 CFQ(Completely Fair Queuing, 完全公平排隊)
該排程器是預設的排程器。該排程器圍繞排程佇列進行,核心使用一個輪轉演算法來處理各個佇列,這確保了I/O頻寬以公平的方式在各個佇列之間進行分配。1.2.2 Deadline I/O排程器
該排程器適用於兩個目地:- 試圖最小化磁碟尋道時間:如果要保證這點就要分析請求,並按照最小尋道時間的要求重排請求。
- 儘可能保證請求在一定的時間內完成:如果要用於該目地,就要使用定時機制保證請求的到期時間
1.2.3 NOOP排程器
該排程器就是一個FIFO形式的排程器。請求被以先來先服務的形式入隊。請求可以合併但是無法重排。1.2.4 AS排程器(預測I/O排程程式)
它會對應用程式的讀寫進行預測以獲得最優效能。它假設應用程式的讀寫請求不是孤立的,簡單的說如果應用程式提交了一個讀請求,則它認為下一個請求也是讀,從而進行優化(當然這只是一個簡單的說明,實際的預測要複雜的多)。
1.3 修改排程器
如果要修改系統預設的排程器,可以通過設定啟動引數elevator=排程器名字來修改。如果只是要臨時修改某個裝置的排程器,可以通過sys檔案系統下該裝置目錄中的queue目錄中的scheduler檔案來實現,把自己希望使用的排程器的名字寫入該檔案即可。比如:
[email protected]:/sys/block/sda/queue# cat scheduler
noop [deadline] cfq
[email protected]:/sys/block/sda/queue# echo cfq > /sys/block/sda/queue/scheduler
[email protected]:/sys/block/sda/queue# cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
二、請求佇列管理
請求佇列的管理對於塊層是很重要的,其中涉及到很多管理請求佇列的API。做一下簡單的總結。2.1 建立和刪除佇列
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
void blk_cleanup_queue(struct request_queue *q)
這兩個API分別用來建立和刪除請求佇列
2.2 請求佇列中請求管理API
當驅動處理請求時自然的就需要和請求佇列互動以獲得請求。相關API包括:struct request *blk_fetch_request(struct request_queue *q);
用於獲取請求
void blk_complete_request(struct request *);
用於在處理完一個請求時呼叫。
void blk_abort_request(struct request *);
取消一個請求
void blk_requeue_request(struct request_queue *, struct request *);
重新將一個請求新增到請求佇列中
2.3 請求佇列的請求佇列管理API
void blk_start_queue(struct request_queue *q);
void blk_stop_queue(struct request_queue *q);
void blk_run_queue(struct request_queue *);
這三個API分別用於啟動請求佇列,停止請求佇列,執行一個請求佇列。
void blk_queue_segment_boundary(struct request_queue *q, unsigned long mask);
如果一個裝置無法處理跨越某個邊界的段,就用該函式設定其邊界,mask是個邊界掩碼,比如0xffffffff表示無法跨越4M邊界。
塊層提供了豐富的佇列相關的API,具體的可以參考檔案(include/linux/blkdev.h)。
三、檔案系統與塊裝置的關係
在linux中一切皆檔案,linux將所有裝置也都當做一個檔案來處理。這裡簡單梳理下二者的關係3.1 檔案系統的簡單概述
Linux檔案系統使用索引節點來記錄檔案資訊,索引節點是一個數據結構,它包含了一個檔案的檔名,位置,大小,建立或修改時間,訪問許可權,所屬關係等檔案控制資訊,一個檔案系統維護了一個索引節點的陣列,每個檔案或目錄都與索引結點陣列中的唯一一個元素對應,系統為每個索引結點分配了一個號碼,也就是該結點在陣列中的索引號,稱為索引結點號。Linux檔案系統將檔案索引結點號和檔名同時儲存在目錄中,所以目錄只是將檔案的名稱和它的索引結點號結合在一起的一張表。
3.2 磁碟塊的組織
在linux中磁碟塊由四個部分組成,分別為引導塊 、專用塊 、 i節點表塊 和資料儲存塊。Linux系統中的每個檔案都被賦予一個唯一的數值,這個數值稱做索引節點。索引節點儲存在一個稱作索引節點表< inode table>中,該表在磁碟格式化時被分配。每個實際的磁碟或分割槽都有其自己的索引節點表。一個索引節點包含檔案的所有資訊,包括磁碟上資料的地址和檔案型別。檔案型別包括如普通檔案、目錄和特殊檔案這樣的資訊。
linux硬碟組織方式為:引導區、超級塊(superblock),索引結點(inode),資料塊(datablock),目錄塊(diredtory block)。其中超級塊中包含了關於該硬碟或分割槽上的檔案系統的整體資訊,如檔案系統的大小等;超級塊後面的資料結構是索引結點,它包含了針對某一個具體檔案的幾乎全部資訊,如檔案的存取許可權、所有者、大小、建立時間以及對應的目錄塊和資料塊等;資料塊是真正儲存檔案內容的位置。但是索引結點中不包括檔案的名字,檔名是放在目錄塊裡的。目錄塊裡包含有檔案的名字以及此檔案的索引結點編號。