塊IO層[1]

 

   系統中能夠隨機(無序的)訪問固定大小數據片(chunk)的設備被稱作塊設備,這些數據片就稱爲塊。常見的塊設備是磁盤,軟盤驅動器,CD_ROM驅動器和閃存等。注意,它們都是以安裝文件系統的方式使用的---這也是塊設備通常的訪問方式。

  另一種基本的設備類型是字符設備。字符設備按照字符流的方式被有序訪問。像串口和鍵盤都屬於字符設備。

  這兩種類型的設備的根本區別在於它們是否可以被隨機訪問。

  內核管理塊設備比管理字符設備要細緻得多。因爲字符設備僅僅需要控制一個位置(當前位置),而塊設備訪問的位置必須能夠在介質的不同區間前後移動。,因此對塊設備的管理需要一個專門的提供服務的子系統。

  • 解剖一個塊設備

  塊設備中最小的可尋址單元是扇區。扇區大小一般是2的整數倍,最常見的大小是512個字節(很多CD_ROM盤的扇區都是2K大小)。扇區的大小是設備的物理屬性,扇區是所有塊設備的基本單元,塊設備無法對比它還小的單元進行尋址和操作。

  雖然各種軟件的用途不同,但是它們都會用到自己的最小邏輯可尋址單元---塊。塊是文件系統的一種抽象,只能基於塊來訪問文件系統。雖然物理磁盤尋址是按照扇區來級進行的,但是內核執行的所有磁盤操作都是按照塊進行的。由於扇區是設備的最小可尋址單元,所以塊不能比扇區還小,只能數倍於扇區大小。對有扇區的硬件設備,內核還要求塊大小是2的整數倍,且不能超過一個頁的長度。

  對塊大小的要求最終如下:必須是扇區大小的2的整數倍,並且要小於頁面大小。

  扇區,設備的最小尋址單元,或稱爲“硬扇區”“設備塊”。塊,文件系統的最小尋址單元,或稱爲“文件塊”“I/O塊”。

  和硬盤相關的常見術語有:簇,柱面,磁頭等,這些都和具體的塊設備郵箱,用戶空間的軟件一般用不到這些概念。

  扇區對內核的重要性在與所有設備的I/O操作都必須基於扇區來進行。

  • 緩衝區和緩衝區頭

  當一個塊被調入內存時,它要存儲在一個緩衝區中。每個緩衝區與一個塊對應,它相當於是磁盤塊在內存中的表示。一個塊小於一個頁,所以一頁可以容納一個或多個內存中的塊。

  由於內核在處理數據時需要一些相關的控制信息,每個緩衝區都有一個對應的描述符。該描述符用buffer_head結構體表示,被稱作緩衝區頭,它包含了內核操作緩衝區所需的全部信息:

 

  1. 在<Buffer_head.h(include/linux)>中
  2. struct page;
  3. struct buffer_head;
  4. struct address_space;
  5. typedef void (bh_end_io_t)(struct buffer_head *bh, int uptodate);
  6. /*
  7.  * Historically, a buffer_head was used to map a single block
  8.  * within a page, and of course as the unit of I/O through the
  9.  * filesystem and block layers.  Nowadays the basic I/O unit
  10.  * is the bio, and buffer_heads are used for extracting block
  11.  * mappings (via a get_block_t call), for tracking state within
  12.  * a page (via a page_mapping) and for wrapping bio submission
  13.  * for backward compatibility reasons (e.g. submit_bh).
  14.  */
  15. struct buffer_head {
  16.     unsigned long b_state;      /* buffer state bitmap (see above) */
  17.     struct buffer_head *b_this_page;/* circular list of page's buffers */
  18.     struct page *b_page;        /* the page this bh is mapped to */表示與緩衝區對應的內存物理頁
  19.     sector_t b_blocknr;     /* start block number */與緩衝區對應的磁盤物理塊由該域索引,該值是b_bdev指明的塊設備中的邏輯塊號
  20.     size_t b_size;          /* size of mapping 以字節爲單位*/
  21.     char *b_data;           /* pointer to data within the page */直接指向相應的塊(它位於b_page域所指的頁面中的某個位置),塊大小爲b_size,塊在內存中的起始位置在b_data處,結束位置在b_data+b_size
  22.     struct block_device *b_bdev;  /*塊設備*/
  23.     bh_end_io_t *b_end_io;      /* I/O completion; io完成方法*/
  24.     void *b_private;        /* reserved for b_end_io */
  25.     struct list_head b_assoc_buffers; /* associated with another mapping */
  26.     struct address_space *b_assoc_map;  /* mapping this buffer is
  27.                            associated with */
  28.     atomic_t b_count;       /* users using this buffer_head */
  29. };

  緩衝區頭的目的在於描述磁盤塊和物理內存緩衝區(在特定頁面上的字節序列)之間的映射關係。這個結構體在內核中只扮演一個描述符的角色,說明從緩衝區到塊的映射關係。

  b_state域中表示緩衝區的狀態。合法標誌存放在bh_state_bits枚舉中:

  1. enum bh_state_bits {
  2.     BH_Uptodate,    /* Contains valid data */該緩衝區包含可用數據
  3.     BH_Dirty,   /* Is dirty */該緩衝區是髒的(緩存中的內容比磁盤中的塊內容新所以緩衝區內容必須被寫回磁盤)
  4.     BH_Lock,    /* Is locked */該緩衝區正在被IO操作使用,被鎖定以防被併發訪問
  5.     BH_Req,     /* Has been submitted for I/O */該緩衝區有IO請求操作
  6.     BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
  7.               * IO completion of other buffers in the page
  8.               */
  9.     BH_Mapped,  /* Has a disk mapping */該緩衝區是映射磁盤塊的可用緩衝區
  10.     BH_New,     /* Disk mapping was newly created by get_block */緩衝區是通過get_block()剛剛映射的,尚且不能訪問
  11.     BH_Async_Read,  /* Is under end_buffer_async_read I/O */該緩衝區正通過end_buffer_async_read()被異步IO讀操作使用
  12.     BH_Async_Write, /* Is under end_buffer_async_write I/O */該緩衝區正通過end_buffer_async_write()被異步IO寫操作使用
  13.     BH_Delay,   /* Buffer is not yet allocated on disk */該緩衝區尚未和磁盤塊關聯
  14.     BH_Boundary,    /* Block is followed by a discontiguity */該緩衝區處於連續塊去的邊界,下一個塊不在連續
  15.     BH_Write_EIO,   /* I/O error on write */
  16.     BH_Ordered, /* ordered write */
  17.     BH_Eopnotsupp,  /* operation not supported (barrier) */
  18.     BH_Unwritten,   /* Buffer is allocated on disk but not written */
  19.     BH_PrivateStart,/* not a state bit, but the first bit available
  20.              * for private allocation by other entities
  21.              */該標誌不是可用狀態標誌,使用它是爲了指明可被其他代碼使用的起始位。塊IO層不會使用BH_PrivateStart或更高的位。
  22. };

  某個驅動程序希望通過b_state域存儲信息時就可以安全的使用這些位。驅動程序可以在這些位中定義自己的狀態標誌,只要保證自己的狀態標誌不與塊IO層的專用位發生衝突就OK了。

  b_count域表示緩衝區的使用計數,可以通過下面的函數進行增減:

 

  1. static inline void get_bh(struct buffer_head *bh)
  2. {
  3.         atomic_inc(&bh->b_count);
  4. }
  5. static inline void put_bh(struct buffer_head *bh)
  6. {
  7.         smp_mb__before_atomic_dec();
  8.         atomic_dec(&bh->b_count);
  9. }

  在操作緩衝區頭之前,應該先使用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。

  1. 在<Bio.h(include/linux)>中
  2. struct bio_set;
  3. struct bio;
  4. typedef int (bio_end_io_t) (struct bio *, unsigned intint);
  5. typedef void (bio_destructor_t) (struct bio *);
  6. /*
  7.  * main unit of I/O for the block layer and lower layers (ie drivers and
  8.  * stacking drivers)
  9.  */
  10. struct bio {
  11.     sector_t        bi_sector;  /* device address in 512 byte
  12.                            sectors */磁盤上相關的扇區
  13.     struct bio      *bi_next;   /* request queue link */請求鏈表
  14.     struct block_device *bi_bdev;   /* 相關的塊設備信息 */
  15.     unsigned long       bi_flags;   /* status, command, etc */
  16.     unsigned long       bi_rw;      /* bottom bits READ/WRITE,
  17.                          * top bits priority
  18.                          */
  19.     unsigned short      bi_vcnt;    /* how many bio_vec's */
  20.     unsigned short      bi_idx;     /* current index into bvl_vec */
  21.     /* Number of segments in this BIO after
  22.      * physical address coalescing is performed.
  23.      */結合後的片段數目
  24.     unsigned short      bi_phys_segments;
  25.     /* Number of segments after physical and DMA remapping
  26.      * hardware coalescing is performed.
  27.      */
  28.     unsigned short      bi_hw_segments;  /* 重映射後的片斷數目 */
  29.     unsigned int        bi_size;    /* residual I/O count */
  30.     /*
  31.      * To keep track of the max hw size, we account for the
  32.      * sizes of the first and last virtually mergeable segments
  33.      * in this bio
  34.      */
  35.     unsigned int        bi_hw_front_size;  /* 第一個可合併的段大小 */
  36.     unsigned int        bi_hw_back_size;  /* 最後一個可合併的段大小 */
  37.     unsigned int        bi_max_vecs;    /* max bvl_vecs we can hold */
  38.     struct bio_vec      *bi_io_vec; /* the actual vec list */
  39.     bio_end_io_t        *bi_end_io;  /* IO完成方法 */
  40.     atomic_t        bi_cnt;     /* pin count */使用計數
  41.     void            *bi_private;  /*擁有者的私有方法*/
  42.     bio_destructor_t    *bi_destructor; /* destructor */
  43. };

  使用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結構體數組表示了一個完整的緩衝區:

  1. /*
  2.  * was unsigned short, but we might as well be ready for > 64kB I/O pages
  3.  */
  4. struct bio_vec {
  5.     struct page *bv_page;
  6.     unsigned int    bv_len;
  7.     unsigned int    bv_offset;
  8. };

  總而言之,每一個塊IO請求都通過一個bio結構體表示。每個請求包括一個或多個塊,這些塊存儲在bio_vec結構體數組中。這些結構體描述了每個片段在物理頁中的實際位置,並且像向量一樣被組織在一起。IO操作的第一個片段由bio_io_vecs指針所指向,其他的片段在其後一次防止,共有bi_vcnt個片段。當塊IO層開始執行請求、需要使用各個片段時,bi_idx域會不斷更新,指向當前片段。塊IO層通過bi_idx可以跟蹤IO操作的完成進度。但該域更重要的作用在於分割bio結構體。

  bi_cnt域記錄bio結構體的使用計數,如果爲0就銷燬該結構體,並釋放內存。通過下面的函數管理使用計數:

 

  1. 在<Bio.h(include/linux)>
  2. /*
  3.  * get a reference to a bio, so it won't disappear. the intended use is
  4.  * something like:
  5.  *
  6.  * bio_get(bio);
  7.  * submit_bio(rw, bio);
  8.  * if (bio->bi_flags ...)
  9.  *  do_something
  10.  * bio_put(bio);
  11.  *
  12.  * without the bio_get(), it could potentially complete I/O before submit_bio
  13.  * returns. and then bio would be freed memory when if (bio->bi_flags ...)
  14.  * runs
  15.  */
  16. #define bio_get(bio)    atomic_inc(&(bio)->bi_cnt)
  17. 在<Bio.c(fs)>中
  18. /**
  19.  * bio_put - release a reference to a bio
  20.  * @bio:   bio to release reference to
  21.  *
  22.  * Description:
  23.  *   Put a reference to a &struct bio, either one you have gotten with
  24.  *   bio_alloc or bio_get. The last put of a bio will free it.
  25.  **/
  26. void bio_put(struct bio *bio)
  27. {
  28.     BIO_BUG_ON(!atomic_read(&bio->bi_cnt));
  29.     /*
  30.      * last put frees it
  31.      */
  32.     if (atomic_dec_and_test(&bio->bi_cnt)) {
  33.         bio->bi_next = NULL;
  34.         bio->bi_destructor(bio);
  35.     }
  36. }

  在操作正在活動的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需要包含緩衝區信息。

 

 
發佈了79 篇原創文章 · 獲贊 6 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章