1. 程式人生 > >Linux檔案系統(五)---三大緩衝區之buffer塊緩衝區

Linux檔案系統(五)---三大緩衝區之buffer塊緩衝區

在檔案系統中,有三大緩衝為了提升效率:inode緩衝區、dentry緩衝區、塊緩衝。

(核心:2.4.37)

二、塊buffer緩衝區

0、整體來說,Linux 檔案緩衝區分為page cache和buffer cache,每一個 page cache 包含若干 buffer cache。

》 記憶體管理系統和 VFS 只與 page cache 互動,記憶體管理系統負責維護每項 page cache 的分配和回收,同時在使用“記憶體對映”方式訪問時負責建立對映。

》 VFS 負責 page cache 與使用者空間的資料交換。

》 而具體檔案系統則一般只與 buffer cache 互動,它們負責在儲存裝置和 buffer cache 之間交換資料,具體的檔案系統直接操作的就是disk部分,而具體的怎麼被包裝被使用者使用是VFS的責任(VFS將buffer cache包裝成page給使用者

)。

》 每一個page有N個buffer cache,struct buffer_head結構體中一個欄位b_this_page就是將一個page中的buffer cache連線起來的結構

     看一下這個結構:/include/linux/mm.h,對於struct buffer_head下面再看。

     看到下面167行程式碼就懂了~

typedef struct page {
156         struct list_head list;          /* ->mapping has some page lists. */
157         struct address_space *mapping;  /* The inode (or ...) we belong to. */
158         unsigned long index;            /* Our offset within mapping. */
159         struct page *next_hash;         /* Next page sharing our hash bucket in
160                                            the pagecache hash table. */
161         atomic_t count;                 /* Usage count, see below. */
162         unsigned long flags;            /* atomic flags, some possibly
163                                            updated asynchronously */
164         struct list_head lru;           /* Pageout list, eg. active_list;
165                                            protected by pagemap_lru_lock !! */
166         struct page **pprev_hash;       /* Complement to *next_hash. */
167         struct buffer_head * buffers;   /* Buffer maps us to a disk block. */
168 
169         /*
170          * On machines where all RAM is mapped into kernel address space,
171          * we can simply calculate the virtual address. On machines with
172          * highmem some memory is mapped into kernel virtual memory
173          * dynamically, so we need a place to store that address.
174          * Note that this field could be 16 bits on x86 ... ;)
175          *
176          * Architectures with slow multiplication can define
177          * WANT_PAGE_VIRTUAL in asm/page.h
178          */
179 #if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)
180         void *virtual;                  /* Kernel virtual address (NULL if
181                                            not kmapped, ie. highmem) */
182 #endif /* CONFIG_HIGMEM || WANT_PAGE_VIRTUAL */
183 } mem_map_t;

關係圖如下:

                   

1、對於具體的Linux檔案系統,會以block(磁碟塊)的形式組織檔案,為了減少對物理塊裝置的訪問,在檔案以塊的形式調入記憶體後,使用塊快取記憶體進行管理。每個緩衝區由兩部分組成,第一部分稱為緩衝區首部,用資料結構buffer_head表示,第二部分是真正的儲存的資料。由於緩衝區首部不與資料區域相連,資料區域獨立儲存。因而在緩衝區首部中,有一個指向資料的指標和一個緩衝區長度的欄位。

   Ps:核心同樣有幾種不同的連結串列來管理buffer cache,在fs/buffer.c中定義:

   static struct buffer_head **hash_table;

   static struct buffer_head *lru_list[NR_LIST];

   static struct buffer_head * unused_list;

     下面我們具體看看這個結構體struct buffer_head,這個版本中這個結構體在fs.h中,後面的一些版本,在buffer_head.h中。

246 struct buffer_head {
247         /* First cache line: */
248         struct buffer_head *b_next;     /* Hash queue list */
249         unsigned long b_blocknr;        /* block number */
250         unsigned short b_size;          /* block size */
251         unsigned short b_list;          /* List that this buffer appears */
252         kdev_t b_dev;                   /* device (B_FREE = free) */
253 
254         atomic_t b_count;               /* users using this block */
255         kdev_t b_rdev;                  /* Real device */
256         unsigned long b_state;          /* buffer state bitmap (see above) */
257         unsigned long b_flushtime;      /* Time when (dirty) buffer should be written */
258 
259         struct buffer_head *b_next_free;/* lru/free list linkage */
260         struct buffer_head *b_prev_free;/* doubly linked list of buffers */
261         struct buffer_head *b_this_page;/* circular list of buffers in one page */
262         struct buffer_head *b_reqnext;  /* request queue */
263 
264         struct buffer_head **b_pprev;   /* doubly linked list of hash-queue */
265         char * b_data;                  /* pointer to data block */
266         struct page *b_page;            /* the page this bh is mapped to */
267         void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O completion */
268         void *b_private;                /* reserved for b_end_io */
269 
270         unsigned long b_rsector;        /* Real buffer location on disk */
271         wait_queue_head_t b_wait;
272 
273         struct list_head     b_inode_buffers;   /* doubly linked list of inode dirty buffers */
274 };
解釋一些上面的欄位:

b_next:用於連結到塊緩衝區的hash表

b_blocknr:本block的塊號

b_size:block的大小

b_list:表示當前的這個buffer在那個連結串列中

b_dev:虛擬裝置標識

b_count:引用計數(幾個人在使用這個buffer)

b_rdev:真實裝置標識

b_state:狀態點陣圖,如下:

212 /* bh state bits */
213 enum bh_state_bits {
214         BH_Uptodate,    /* 如果緩衝區包含有效資料則置1 */
215         BH_Dirty,       /* 如果buffer髒(存在資料被修改情況),那麼置1 */
216         BH_Lock,        /* 如果緩衝區被鎖定,那麼就置1 */
217         BH_Req,         /* 如果緩衝區無效就置0 */
218         BH_Mapped,      /* 如果緩衝區有一個磁碟對映就置1 */
219         BH_New,         /* 如果緩衝區是新的,而且沒有被寫出去,那麼置1 */
220         BH_Async,       /* 如果緩衝區是進行end_buffer_io_async I/O 同步則置1 */
221         BH_Wait_IO,     /* 如果要將這個buffer寫回,那麼置1 */
222         BH_Launder,     /* 如果需要重置這個buffer,那麼置1 */
223         BH_Attached,    /* 1 if b_inode_buffers is linked into a list */
224         BH_JBD,         /* 如果和 journal_head 關聯置1 */
225         BH_Sync,        /* 如果buffer是同步讀取置1 */
226         BH_Delay,       /* 如果buffer空間是延遲分配置1 */
227 
228         BH_PrivateStart,/* not a state bit, but the first bit available
229                          * for private allocation by other entities
230                          */
231 };
232
b_flushtime:髒buffer需要被寫入的時間

b_next_free:指向lru連結串列中next元素

b_prev_free:指向連結串列上一個元素

b_this_page:連線到同一個page中的那個連結串列

b_reqnext:請求佇列

b_pprev:hash佇列雙向連結串列
data:指向資料塊的指標

b_page:這個buffer對映的頁面

b_end_io:IO結束時候執行函式

b_private:保留

b_rsector:緩衝區在磁碟上的實際位置

b_inode_buffers:inode髒緩衝區迴圈連結串列

3、關於VFS怎麼去管理幾個buffer cache的連結串列,如下:

hash表:用於管理包含有效資料的buffer,在定位buffer的時候很快捷。雜湊索引值由資料塊號以及其所在的裝置標識號計算(雜湊)得到。

關於這段hash程式碼如下:

<span style="font-size:14px;">539 /* After several hours of tedious analysis, the following hash
540  * function won.  Do not mess with it... -DaveM
541  */
542 #define _hashfn(dev,block)      \
543         ((((dev)<<(bh_hash_shift - 6)) ^ ((dev)<<(bh_hash_shift - 9))) ^ \
544          (((block)<<(bh_hash_shift - 6)) ^ ((block) >> 13) ^ \
545           ((block) << (bh_hash_shift - 12))))</span>
下面簡單的看一下流程:

當我們在一個具有的檔案系統中,當我們需要讀取一塊資料的時候,需要呼叫bread函式(麵包?ヾ(。`Д´。),應該是buffer read的縮寫吧。。。)。

如下:

1181 /**
1182  *      bread() - reads a specified block and returns the bh
1183  *      @block: number of block        塊號
1184  *      @size: size (in bytes) to read 需要讀取的size
1185  * 
1186  *      Reads a specified block, and returns buffer head that
1187  *      contains it. It returns NULL if the block was unreadable.  返回一個包含這個block的buffer
1188  */ 
1189 struct buffer_head * bread(kdev_t dev, int block, int size)
1190 {
1191         struct buffer_head * bh;
1192 
1193         bh = getblk(dev, block, size);    /* 找到這個buffer */
1194         if (buffer_uptodate(bh))  /* 判斷是否存在有效資料,如果存在那麼直接返回即可 */
1195                 return bh;
1196         set_bit(BH_Sync, &bh->b_state); /* 如果不存在有效資料,將這個buffer設定成同步狀態 */
1197         ll_rw_block(READ, 1, &bh); /* 如果沒有,那麼需要從磁碟中讀取這個block到buffer中,這個是一個底層的讀取操作 */
1198         wait_on_buffer(bh);  /* 等待buffer的鎖開啟 */
1199         if (buffer_uptodate(bh)) /* 理論上這個時候應該是存在有效資料的了,直接返回就可以 */
1200                 return bh;
1201         brelse(bh);
1202         return NULL;
1203 }
對於上面函式的分析,基本上分成兩個步驟,

第一:通過dev號+block號找到相應的buffer,使用函式getblk,如下:

1013 struct buffer_head * getblk(kdev_t dev, int block, int size)
1014 {
1015         for (;;) {
1016                 struct buffer_head * bh;
1017 
1018                 bh = get_hash_table(dev, block, size);   /* 關鍵函式,得到hash表中的buffer */
1019                 if (bh) {
1020                         touch_buffer(bh);
1021                         return bh;     /* 返回這個buffer */
1022                 }
1023                 /* 如果沒有找到對應的buffer,那麼試著去增加一個buffer,就是使用下面的grow_buffers函式 */
1024                 if (!grow_buffers(dev, block, size))
1025                         free_more_memory();
1026         }
1027 }

簡單看一下這個查詢buffer函式:get_hash_table
628 struct buffer_head * get_hash_table(kdev_t dev, int block, int size)
629 {
630         struct buffer_head *bh, **p = &hash(dev, block);   /* 首先通過hash值得到對應的位置,這個函式h很easy */
631 /* 其實就是 #define hash(dev,block) hash_table[(_hashfn(HASHDEV(dev),block) & bh_hash_mask)]。hashfn函式上面已經說過了,就是通過hash值得到buffer*/
632         read_lock(&hash_table_lock);
633 
634         for (;;) {           /* 下面就是判斷這個得到的buffer陣列中有沒有我們需要的buffer */
635                 bh = *p;
636                 if (!bh)
637                         break;
638                 p = &bh->b_next;
639                 if (bh->b_blocknr != block)
640                         continue;
641                 if (bh->b_size != size)
642                         continue;
643                 if (bh->b_dev != dev)
644                         continue;
645                 get_bh(bh);       /* 如果有那麼直接執行這個函式,這個函式很easy,其實就是增加一個使用計數器而已: atomic_inc(&(bh)->b_count);*/
646                 break;
647         }
648 
649         read_unlock(&hash_table_lock);
650         return bh;
651 }

如果沒找到對應的buffer,那麼使用grow_buffers函式增加一個新的buffer,看函式:
2596 /*
2597  * Try to increase the number of buffers available: the size argument
2598  * is used to determine what kind of buffers we want.
2599  */
2600 static int grow_buffers(kdev_t dev, unsigned long block, int size)
2601 {
2602         struct page * page;
2603         struct block_device *bdev;
2604         unsigned long index;
2605         int sizebits;
2606 
2607         /* Size must be multiple of hard sectorsize */
2608         if (size & (get_hardsect_size(dev)-1))
2609                 BUG();
2610         /* Size must be within 512 bytes and PAGE_SIZE */
2611         if (size < 512 || size > PAGE_SIZE)
2612                 BUG();
2613 
2614         sizebits = -1;
2615         do {
2616                 sizebits++;
2617         } while ((size << sizebits) < PAGE_SIZE);
2618 
2619         index = block >> sizebits;
2620         block = index << sizebits;
2621 
2622         bdev = bdget(kdev_t_to_nr(dev));
2623         if (!bdev) {
2624                 printk("No block device for %s\n", kdevname(dev));
2625                 BUG();
2626         }
2627 
2628         /* Create a page with the proper size buffers.. */
2629         page = grow_dev_page(bdev, index, size);
2630 
2631         /* This is "wrong" - talk to Al Viro */
2632         atomic_dec(&bdev->bd_count);
2633         if (!page)
2634                 return 0;
2635 
2636         /* Hash in the buffers on the hash list */
2637         hash_page_buffers(page, dev, block, size);
2638         UnlockPage(page);
2639         page_cache_release(page);
2640 
2641         /* We hashed up this page, so increment buffermem */
2642         atomic_inc(&buffermem_pages);
2643         return 1;
2644 }
2645 
這個函式就是增加一個新的buffer,首先由grow_dev_page建立一個緩衝區包含這個block,然後將這個buffer連結到這個全域性的hash緩衝區中使用函式hash_page_buffers。具體的程式碼很簡單,不單看了。

第二:如果沒有找到需要的buffer,那麼執行底層讀取函式ll_rw_block將資料從磁碟去讀進來,這個函式在/source/drivers/block/ll_rw_blk.c中,具體的程式碼不看了。

OK,至此,尋找一個我們需要的buffer就結束了。


LRU連結串列

對於每一種不同緩衝區都會使用一個LRU來管理未使用的有效緩衝區

Ps:緩衝區型別如下:

1152 #define BUF_CLEAN       0
1153 #define BUF_LOCKED      1       /* Buffers scheduled for write */
1154 #define BUF_DIRTY       2       /* Dirty buffers, not yet scheduled for write */
分別是:未使用的乾淨的緩衝區;正在等待寫入的緩衝區;髒緩衝區,還沒有被寫回磁碟。

這個三種連結串列怎麼得到的呢,看程式碼也知道是吻合的,看LRU的宣告:static struct buffer_head *lru_list[NR_LIST];

再看:#define NR_LIST         3,OK

當我們需要尋找一塊buffer的時候,如果發現buffer在緩衝區中,且在LRU連結串列中,那麼從LRU表中刪除。

結合上面的一個hash連結串列,基本過程就是:

首先呢在hash表中尋找,如果找到,那就OK,如果沒有找到,那麼需要分配新的buffer,如果分到,那麼載入資料進來,繼續...如果沒有足夠的空間分配,那麼, 需要將LRU中一個取出(LRU鏈首元素),先看是否置了“髒”位,如已置,則將它的內容寫回磁碟。然後清空內容,將它分配給新的資料塊。

在緩衝區使用完了後,將它的b_count域減1,如果b_count變為0,則將它放在某個LRU鏈尾,表示該緩衝區已可以重新利用。

unused_list 用於輔助就不多說了~~~