1. 程式人生 > >塊裝置之三

塊裝置之三

一、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快取的名字
I/O排程器的操作函式集定義如下:
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)。其中超級塊中包含了關於該硬碟或分割槽上的檔案系統的整體資訊,如檔案系統的大小等;超級塊後面的資料結構是索引結點,它包含了針對某一個具體檔案的幾乎全部資訊,如檔案的存取許可權、所有者、大小、建立時間以及對應的目錄塊和資料塊等;資料塊是真正儲存檔案內容的位置。但是索引結點中不包括檔案的名字,檔名是放在目錄塊裡的。目錄塊裡包含有檔案的名字以及此檔案的索引結點編號。