系統中能夠隨機(無序的)訪問固定大小數據片(chunk)的設備被稱作塊設備,這些數據片就稱爲塊。常見的塊設備是磁盤,軟盤驅動器,CD_ROM驅動器和閃存等。注意,它們都是以安裝文件系統的方式使用的---這也是塊設備通常的訪問方式。
另一種基本的設備類型是字符設備。字符設備按照字符流的方式被有序訪問。像串口和鍵盤都屬於字符設備。
這兩種類型的設備的根本區別在於它們是否可以被隨機訪問。
內核管理塊設備比管理字符設備要細緻得多。因爲字符設備僅僅需要控制一個位置(當前位置),而塊設備訪問的位置必須能夠在介質的不同區間前後移動。,因此對塊設備的管理需要一個專門的提供服務的子系統。
- 解剖一個塊設備
塊設備中最小的可尋址單元是扇區。扇區大小一般是2的整數倍,最常見的大小是512個字節(很多CD_ROM盤的扇區都是2K大小)。扇區的大小是設備的物理屬性,扇區是所有塊設備的基本單元,塊設備無法對比它還小的單元進行尋址和操作。
雖然各種軟件的用途不同,但是它們都會用到自己的最小邏輯可尋址單元---塊。塊是文件系統的一種抽象,只能基於塊來訪問文件系統。雖然物理磁盤尋址是按照扇區來級進行的,但是內核執行的所有磁盤操作都是按照塊進行的。由於扇區是設備的最小可尋址單元,所以塊不能比扇區還小,只能數倍於扇區大小。對有扇區的硬件設備,內核還要求塊大小是2的整數倍,且不能超過一個頁的長度。
對塊大小的要求最終如下:必須是扇區大小的2的整數倍,並且要小於頁面大小。
扇區,設備的最小尋址單元,或稱爲“硬扇區”“設備塊”。塊,文件系統的最小尋址單元,或稱爲“文件塊”“I/O塊”。
和硬盤相關的常見術語有:簇,柱面,磁頭等,這些都和具體的塊設備郵箱,用戶空間的軟件一般用不到這些概念。
扇區對內核的重要性在與所有設備的I/O操作都必須基於扇區來進行。
- 緩衝區和緩衝區頭
當一個塊被調入內存時,它要存儲在一個緩衝區中。每個緩衝區與一個塊對應,它相當於是磁盤塊在內存中的表示。一個塊小於一個頁,所以一頁可以容納一個或多個內存中的塊。
由於內核在處理數據時需要一些相關的控制信息,每個緩衝區都有一個對應的描述符。該描述符用buffer_head結構體表示,被稱作緩衝區頭,它包含了內核操作緩衝區所需的全部信息:
- 在<Buffer_head.h(include/linux)>中
- struct page;
- struct buffer_head;
- struct address_space;
- typedef void (bh_end_io_t)(struct buffer_head *bh, int uptodate);
- /*
- * Historically, a buffer_head was used to map a single block
- * within a page, and of course as the unit of I/O through the
- * filesystem and block layers. Nowadays the basic I/O unit
- * is the bio, and buffer_heads are used for extracting block
- * mappings (via a get_block_t call), for tracking state within
- * a page (via a page_mapping) and for wrapping bio submission
- * for backward compatibility reasons (e.g. submit_bh).
- */
- struct buffer_head {
- unsigned long b_state; /* buffer state bitmap (see above) */
- struct buffer_head *b_this_page;/* circular list of page's buffers */
- struct page *b_page; /* the page this bh is mapped to */表示與緩衝區對應的內存物理頁
- sector_t b_blocknr; /* start block number */與緩衝區對應的磁盤物理塊由該域索引,該值是b_bdev指明的塊設備中的邏輯塊號
- size_t b_size; /* size of mapping 以字節爲單位*/
- char *b_data; /* pointer to data within the page */直接指向相應的塊(它位於b_page域所指的頁面中的某個位置),塊大小爲b_size,塊在內存中的起始位置在b_data處,結束位置在b_data+b_size
- struct block_device *b_bdev; /*塊設備*/
- bh_end_io_t *b_end_io; /* I/O completion; io完成方法*/
- void *b_private; /* reserved for b_end_io */
- struct list_head b_assoc_buffers; /* associated with another mapping */
- struct address_space *b_assoc_map; /* mapping this buffer is
- associated with */
- atomic_t b_count; /* users using this buffer_head */
- };
緩衝區頭的目的在於描述磁盤塊和物理內存緩衝區(在特定頁面上的字節序列)之間的映射關係。這個結構體在內核中只扮演一個描述符的角色,說明從緩衝區到塊的映射關係。
b_state域中表示緩衝區的狀態。合法標誌存放在bh_state_bits枚舉中:
- enum bh_state_bits {
- BH_Uptodate, /* Contains valid data */該緩衝區包含可用數據
- BH_Dirty, /* Is dirty */該緩衝區是髒的(緩存中的內容比磁盤中的塊內容新所以緩衝區內容必須被寫回磁盤)
- BH_Lock, /* Is locked */該緩衝區正在被IO操作使用,被鎖定以防被併發訪問
- BH_Req, /* Has been submitted for I/O */該緩衝區有IO請求操作
- BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
- * IO completion of other buffers in the page
- */
- BH_Mapped, /* Has a disk mapping */該緩衝區是映射磁盤塊的可用緩衝區
- BH_New, /* Disk mapping was newly created by get_block */緩衝區是通過get_block()剛剛映射的,尚且不能訪問
- BH_Async_Read, /* Is under end_buffer_async_read I/O */該緩衝區正通過end_buffer_async_read()被異步IO讀操作使用
- BH_Async_Write, /* Is under end_buffer_async_write I/O */該緩衝區正通過end_buffer_async_write()被異步IO寫操作使用
- BH_Delay, /* Buffer is not yet allocated on disk */該緩衝區尚未和磁盤塊關聯
- BH_Boundary, /* Block is followed by a discontiguity */該緩衝區處於連續塊去的邊界,下一個塊不在連續
- BH_Write_EIO, /* I/O error on write */
- BH_Ordered, /* ordered write */
- BH_Eopnotsupp, /* operation not supported (barrier) */
- BH_Unwritten, /* Buffer is allocated on disk but not written */
- BH_PrivateStart,/* not a state bit, but the first bit available
- * for private allocation by other entities
- */該標誌不是可用狀態標誌,使用它是爲了指明可被其他代碼使用的起始位。塊IO層不會使用BH_PrivateStart或更高的位。
- };
某個驅動程序希望通過b_state域存儲信息時就可以安全的使用這些位。驅動程序可以在這些位中定義自己的狀態標誌,只要保證自己的狀態標誌不與塊IO層的專用位發生衝突就OK了。
b_count域表示緩衝區的使用計數,可以通過下面的函數進行增減:
- static inline void get_bh(struct buffer_head *bh)
- {
- atomic_inc(&bh->b_count);
- }
- static inline void put_bh(struct buffer_head *bh)
- {
- smp_mb__before_atomic_dec();
- atomic_dec(&bh->b_count);
- }
在操作緩衝區頭之前,應該先使用get_bh()函數增加緩衝區頭的引用計數確保該緩衝區頭不會再被分配出去;當完成對緩衝區頭的操作之後,還必須使用put_bh()函數減少引用計數。
在2.6之前,緩衝區頭的作用很重要,緩衝區頭作爲內核中IO操作單元,不僅僅描述了從哦哦那個磁盤塊到物理內存的映射,而且還是所有開IO操作的容器。 將緩衝區頭作爲IO操作單元的弊端如下:
1. 緩衝區頭是一個很大且不易控制的數據結構體(2.6之前),而且緩衝區頭對數據的操作既不方便也不清晰。在2.6版本中,許多IO操作都是通過內核直接對頁面或地址空間進行操作來完成,不再使用緩衝區頭。
2. 緩衝區頭僅能描述單個緩衝區,當作爲所有IO的容器使用時,緩衝區頭會迫使內核打斷對大塊數據的IO操作,使其成爲對多個buffer_head結構體進行操作。2.5版本以後的內核的主要目標就是爲塊IO操作引入一直新型。靈活並且輕量級的容器,bio結構體。
- bio結構體
目前內核中塊IO操作的基本容器由bio結構體表示。該結構體代表了正在現場的(活動的)以片段(segment)鏈表形式組織的塊IO操作。一個片段是一小塊連續的內存緩衝區。這樣就不需要保證單個緩衝區一定要連續。通過片段來描述緩衝區,即使一個緩衝區分散在內存的多個位置上,bio結構體也能對內核保證IO操作的執行。像這樣的向量IO就是所謂的聚散IO。
- 在<Bio.h(include/linux)>中
- struct bio_set;
- struct bio;
- typedef int (bio_end_io_t) (struct bio *, unsigned int, int);
- typedef void (bio_destructor_t) (struct bio *);
- /*
- * main unit of I/O for the block layer and lower layers (ie drivers and
- * stacking drivers)
- */
- struct bio {
- sector_t bi_sector; /* device address in 512 byte
- sectors */磁盤上相關的扇區
- struct bio *bi_next; /* request queue link */請求鏈表
- struct block_device *bi_bdev; /* 相關的塊設備信息 */
- unsigned long bi_flags; /* status, command, etc */
- unsigned long bi_rw; /* bottom bits READ/WRITE,
- * top bits priority
- */
- unsigned short bi_vcnt; /* how many bio_vec's */
- unsigned short bi_idx; /* current index into bvl_vec */
- /* Number of segments in this BIO after
- * physical address coalescing is performed.
- */結合後的片段數目
- unsigned short bi_phys_segments;
- /* Number of segments after physical and DMA remapping
- * hardware coalescing is performed.
- */
- unsigned short bi_hw_segments; /* 重映射後的片斷數目 */
- unsigned int bi_size; /* residual I/O count */
- /*
- * To keep track of the max hw size, we account for the
- * sizes of the first and last virtually mergeable segments
- * in this bio
- */
- unsigned int bi_hw_front_size; /* 第一個可合併的段大小 */
- unsigned int bi_hw_back_size; /* 最後一個可合併的段大小 */
- unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
- struct bio_vec *bi_io_vec; /* the actual vec list */
- bio_end_io_t *bi_end_io; /* IO完成方法 */
- atomic_t bi_cnt; /* pin count */使用計數
- void *bi_private; /*擁有者的私有方法*/
- bio_destructor_t *bi_destructor; /* destructor */
- };
使用bio結構體的目的主要是代表正在現場執行的IO操作,所以該結構體中的主要域都是用來管理相關信息的。其中最重要的幾個域是bi_io_vecs,bi_vcnt和bi_idx。
bi_io_vecs域指向一個bio_vec結構體數組,該結構體鏈表包含了一個特定的IO操作所需要使用到的所有片段。
在每個給定的塊IO操作中,bi_vcnt域用來描述bi_io_vec所指向的bio_vec數組中的向量數目。
當塊IO操作執行完了,bi_idx指向數組的當前索引。
每個bio_vec結構都是一個形式爲<page,offset,len>的向量,它描述的是一個特定的片斷:片段所在的物理頁、在物理頁中的偏移位置、從給定偏移量開始的塊長度。整個bio_io_vec結構體指向的bio_vec結構體數組表示了一個完整的緩衝區:
- /*
- * was unsigned short, but we might as well be ready for > 64kB I/O pages
- */
- struct bio_vec {
- struct page *bv_page;
- unsigned int bv_len;
- unsigned int bv_offset;
- };
總而言之,每一個塊IO請求都通過一個bio結構體表示。每個請求包括一個或多個塊,這些塊存儲在bio_vec結構體數組中。這些結構體描述了每個片段在物理頁中的實際位置,並且像向量一樣被組織在一起。IO操作的第一個片段由bio_io_vecs指針所指向,其他的片段在其後一次防止,共有bi_vcnt個片段。當塊IO層開始執行請求、需要使用各個片段時,bi_idx域會不斷更新,指向當前片段。塊IO層通過bi_idx可以跟蹤IO操作的完成進度。但該域更重要的作用在於分割bio結構體。
bi_cnt域記錄bio結構體的使用計數,如果爲0就銷燬該結構體,並釋放內存。通過下面的函數管理使用計數:
- 在<Bio.h(include/linux)>
- /*
- * get a reference to a bio, so it won't disappear. the intended use is
- * something like:
- *
- * bio_get(bio);
- * submit_bio(rw, bio);
- * if (bio->bi_flags ...)
- * do_something
- * bio_put(bio);
- *
- * without the bio_get(), it could potentially complete I/O before submit_bio
- * returns. and then bio would be freed memory when if (bio->bi_flags ...)
- * runs
- */
- #define bio_get(bio) atomic_inc(&(bio)->bi_cnt)
- 在<Bio.c(fs)>中
- /**
- * bio_put - release a reference to a bio
- * @bio: bio to release reference to
- *
- * Description:
- * Put a reference to a &struct bio, either one you have gotten with
- * bio_alloc or bio_get. The last put of a bio will free it.
- **/
- void bio_put(struct bio *bio)
- {
- BIO_BUG_ON(!atomic_read(&bio->bi_cnt));
- /*
- * last put frees it
- */
- if (atomic_dec_and_test(&bio->bi_cnt)) {
- bio->bi_next = NULL;
- bio->bi_destructor(bio);
- }
- }
在操作正在活動的bio結構體時,一定要首先增加它的使用計數,以免在操作過程中該bio結構體被釋放。
bi_private域,這是一個屬於擁有者(創建者)的私有域,只有創建了bio結構的擁有者可以讀寫該域。
- 新老方法的對比
緩衝區頭和新的bio結構體之間存在明顯的差別。
bio結構體代表的是IO操作,它可以包括內存中的一個或多個頁;buffer_head結構體代表的是一個緩衝區,它描述的僅僅是磁盤中的一個塊。因爲緩衝區頭關聯的是單獨頁中的單獨磁盤塊,所以它會引起不必要的分割,將請求按塊爲單位劃分,只能靠以後重新組合。bio結構體是輕量級的,它描述的塊可以不需要連續存儲區,且不需要分割IO操作。
bio代替buffer_head的好處如下:
1. bio很容易處理高度內存,因爲它處理的是物理頁而不是直接指針。
2. bio既可以代表普通頁IO,也可以代表直接IO(那些不通過頁高速緩存的IO操作)
3. bio便於執行分散--集中(向量化)塊IO操作,操作中的數據可取自多個物理頁面。
4. bio相比緩衝區頭屬於輕量級的結構體。它只需要包含塊IO操作所需的信息就行了,不用包含與緩衝區本身相關的不必要信息。
緩衝區頭負責描述磁盤塊到頁面的映射。bio結構體不包括任何和緩衝區相關的狀態信息,它僅僅是一個向量組,描述一個或多個單獨塊IO操作的數據片段和相關信息。
在當前設置中,當bio描述當前正在使用的IO操作時,buffer_head需要包含緩衝區信息。