一、綜述
圖1是塊裝置操作的乙個分層實現圖。當乙個程序呼叫read讀取乙個檔案時,核心執行如下乙個過程:首先,它通過vfs層去讀取要到的檔案塊有沒有已經被cache了,這個cache由乙個buffer_head結構讀取。如果要讀取的檔案塊還沒有被cache,則就要從檔案系統中去讀取了,這就是檔案系統的對映層,它通過乙個address_space結構來引用,然後呼叫檔案系統讀函式(readpage)去讀取乙個頁面大小的資料,這個讀函式對於不同的檔案系統來說,是不一樣的。當它從磁碟中讀出資料時,它會將資料頁鏈入cache中,當下次再讀取時,就不需要再次從磁碟出去讀了。readpage()函式並不是直接去操作磁碟,而只是將請求初始化成乙個bio結構,並提交給通用塊層(generic block layer)。
它就通過submit_bio()去完成的。通用塊層再呼叫相應裝置的io排程器,通過這個排程器的排程演算法,將這個bio或合併到已存在的request中,或建立乙個新的request,並將這個新建立的request插入到裝置的請求佇列中去。這就完成了io排程層的工作。最後就是塊裝置驅動所做的工作了。io排程器傳遞給塊驅動的是乙個請求佇列,塊驅動就是要處理這個佇列中的請求,直到這個隊列為空為止。
二、通用塊層(generic block layer)
通用塊層操作的是乙個bio結構,這個結構主要的資料域是,
unsigned short bi_vcnt;
struct bio_vec *bi_io_vec; /* the actual vec list */
這個就是要讀寫的資料向量,且每個struct bio_vec 為乙個segment。
//這個函式主要是呼叫generic_make_request()去完成工作:
void submit_bio(int rw, struct bio *bio)
//這個函式的主要作用是將bio傳遞給驅動去處理
void generic_make_request(struct bio *bio)
while (ret); }
//這要函式的主要作用就是呼叫io排程演算法將bio合併,或插入到佇列中合適的位置中去
static int __make_request(request_queue_t *q, struct bio *bio)
get_rq:
rw_flags = bio_data_dir(bio);
if (sync)
rw_flags |= req_rw_sync;
//新創乙個request
req = get_request_wait(q, rw_flags, bio);
//初始化這個request。
init_request_from_bio(req, bio);
spin_lock_irq(q->queue_lock);
if (elv_queue_empty(q)) //空佇列的處理
blk_plug_device(q);
add_request(q, req); //將新請求加入佇列中去
out:
if (sync) //如果需要同步,立即處理請求
__generic_unplug_device(q);
spin_unlock_irq(q->queue_lock);
return 0;
end_io:
bio_endio(bio, nr_sectors << 9, err);
return 0; }
//觸發塊裝置驅動進行真正的io操作
void __generic_unplug_device(request_queue_t *q)
linux的塊裝置層
ll rw block 是塊裝置驅動層,向上提供按block讀寫塊裝置到某個記憶體位址的 是以page為目標單位 方法 bread 是塊裝置緩衝層,磁碟上的block用頁快取 先從這個快取裡找,找不到再呼叫ll rw block 讀進來 各個檔案系統,要向vfs層提供read inode 這樣的介面...
塊裝置驅動1 塊裝置基礎知識
塊裝置與字元裝置 1.訪問方式的不同 字元裝置按照位元組進行讀取,塊裝置按塊進行讀取 2.緩衝區 塊裝置本身驅動層支援緩衝區 軟體實現的緩衝區 所以塊裝置驅動最適合儲存裝置 儲存裝置按塊進行讀寫的特點決定 而字元裝置驅動層沒有緩衝 3.訪問的順序 塊裝置可以實現隨機的訪問,不連續塊的訪問,而字元裝置...
LINUX塊裝置驅動 1
編寫塊裝置驅動的關鍵步驟 1 呼叫register blkdev申請或註冊主裝置號及裝置名稱,詳見核心原始碼中該函式的注釋。不過下面這篇文章裡並未用到這一步 2 呼叫blk init queue函式建立並初始化乙個 request queue 結構,該函式需要乙個用來處理請求的do request函...