VFS缓冲区缓存BufferCache实现原理剖析Ⅰ文档格式.docx
- 文档编号:16699515
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:31
- 大小:39.40KB
VFS缓冲区缓存BufferCache实现原理剖析Ⅰ文档格式.docx
《VFS缓冲区缓存BufferCache实现原理剖析Ⅰ文档格式.docx》由会员分享,可在线阅读,更多相关《VFS缓冲区缓存BufferCache实现原理剖析Ⅰ文档格式.docx(31页珍藏版)》请在冰豆网上搜索。
Chapter1
概述…...……………………………………………………………….…1
Chapter2BufferCache的数据结构…………………………………………….….2
2.1
缓冲区头部对象buffer_head……………………………………………………….2
2.2buffer_head对象的SLAB分配器缓存……………………………………………..5
2.3bcache中的缓冲区头部对象链表…………………………………………………..6
2.4
对buffer_head对象链表的操作……………………………………………..……12
Chapter3
对缓冲区的操作………………………………………………………..19
3.1
缓冲区的分配………………………………………………………………..…….18
3.2
缓冲区访问接口getblk/brelse…………………………………………..……….22
3.3数据块读接口bread…………………………………………………….………….24
3.4
对inode的i_dirty_buffers链表中的脏缓冲区的操作………………..…………24
Chapter4bcache中脏缓冲区的同步机制………………………………….…….29
4.1bcache中的脏缓冲区同步操作…………………………………………..………..29
4.2
缓冲区同步的系统调用………………………………………………..………….32
4.3bdflush内核线程……………………………………………………….…………..33
4.4kupdate内核线程…………………………………………………………………..37
概述
我们都知道,UNIX操作系统通过在物理文件系统和块设备驱动程序之间引入了「缓冲区缓存」(BufferCache,以下简称bcache)这一软件cache机制,从而大大降低了操作系统内核对块设备的存取频率(实际上,包括Windows在内的大多数操作系统也是这么做的)。
由于bcache位于物理文件系统和块设备驱动程序之间,因此,但物理文件系统需要从块设备上读取数据时,它首先试图从bcache中去读。
如果命中,则内核就不必在去访问慢速的块设备。
否则如果命中失败,也即数据不在bcache中,则内核从块设备上读取相应的数据块,并将其在bcache中缓存起来,以备下次访问之用。
类似地,但物理文件系统需要向块设备上写数据时,也是先将数据写到相应的缓冲区中,并将这个缓冲区标记为脏(dirty),然后在将来的某些时侯将buffercache中的数据真正地回写到块设备上,或者将该缓冲区直接丢弃。
从而实现减少磁盘写操作的频率。
Chapter2BufferCache的数据结构
缓冲区头部对象buffer_head
我们都知道,每一个缓冲区都有一个缓冲区头部来唯一地标识与描述该缓冲区。
Linux通过数据结构buffer_head来定义缓冲区头部。
如下所示(include/linux/fs.h):
structbuffer_head{
/*Firstcacheline:
*/
structbuffer_head*b_next;
/*Hashqueuelist*/
unsignedlongb_blocknr;
/*blocknumber*/
unsignedshortb_size;
/*blocksize*/
unsignedshortb_list;
/*Listthatthisbufferappears*/
kdev_tb_dev;
/*device(B_FREE=free)*/
atomic_tb_count;
/*usersusingthisblock*/
kdev_tb_rdev;
/*Realdevice*/
unsignedlongb_state;
/*bufferstatebitmap(seeabove)*/
unsignedlongb_flushtime;
/*Timewhen(dirty)buffershouldbewritten*/
structbuffer_head*b_next_free;
/*lru/freelistlinkage*/
structbuffer_head*b_prev_free;
/*doublylinkedlistofbuffers*/
structbuffer_head*b_this_page;
/*circularlistofbuffersinonepage*/
structbuffer_head*b_reqnext;
/*requestqueue*/
structbuffer_head**b_pprev;
/*doublylinkedlistofhash-queue*/
char*b_data;
/*pointertodatablock(512byte)*/
structpage*b_page;
/*thepagethisbhismappedto*/
void(*b_end_io)(structbuffer_head*bh,intuptodate);
/*I/Ocompletion*/
void*b_private;
/*reservedforb_end_io*/
unsignedlongb_rsector;
/*Realbufferlocationondisk*/
wait_queue_head_tb_wait;
structinode*b_inode;
structlist_headb_inode_buffers;
/*doublylinkedlistofinodedirtybuffers*/
};
各字段的含义如下:
1.b_next指针:
指向哈希链表中的下一个buffer_head对象。
2.b_blocknr:
本缓冲区对应的块号(blocknumber)。
3.b_size:
以字节计掉的块长度。
合法值为:
512、1024、2048、4096、8192、16384和32768。
4.b_list:
记录这个缓冲区应该出现在哪个链表上。
5.d_dev:
缓冲区对应的块所在的块设备标识符(对于位于free_list链表中的缓冲区,b_dev=B_FREE)。
6.b_count:
本缓冲区的引用计数。
7.b_rdev:
缓冲区对应的块所在的块设备的「真实」标识符。
8.b_state:
缓冲区的状态,共有6种:
/*bhstatebits*/
#defineBH_Uptodate0/*1ifthebuffercontainsvaliddata*/
#defineBH_Dirty1/*1ifthebufferisdirty*/
#defineBH_Lock2/*1ifthebufferislocked*/
#defineBH_Req3/*0ifthebufferhasbeeninvalidated*/
#defineBH_Mapped4/*1ifthebufferhasadiskmapping*/
#defineBH_New5/*1ifthebufferisnewandnotyetwrittenout*/
#defineBH_Protected6/*1ifthebufferisprotected*/
9.b_flushtime:
脏缓冲区必须被回写到磁盘的最后期限值。
10.b_next_free指针:
指向lru/free/unused链表中的下一个缓冲区头部对象。
⑾b_prev_free指针:
指向lru/free/unused链表中的前一个缓冲区头部对象。
⑿b_this_page指针:
指向同属一个物理页帧的下一个缓冲区的相应缓冲区头部对象。
同属一个物理页帧的所有缓冲区通过这个指针成员链接成一个单向循环链表。
⒀b_reqnext指针:
用于块设备驱动程序的请求链表。
⒁b_pprev:
哈希链表的后向指针。
⒂b_data指针:
指向缓冲区数据块的指针。
⒃b_page指针:
指向缓冲区所在物理页帧的page结构。
⒄b_rsector:
实际设备中原始扇区的个数。
⒅b_wait:
等待这个缓冲区的等待队列。
⒆b_inode指针:
如果缓冲区属于某个索引节点,则这个指针指向所属的inode对象。
⒇b_inode_buffers指针:
如果缓冲区为脏,且又属于某个索引节点,那么就通过这个指针链入inode的i_dirty_buffers链表中。
缓冲区头部对象buffer_head可以被看作是缓冲区的描述符,因此,对bcache中的缓冲区的管理就集中在如何高效地组织处于各种状态下的buffer_head对象上。
2.2buffer_head对象的SLAB分配器缓存
缓冲区头部对象buffer_head本身有一个叫做bh__cachep的slab分配器缓存。
因此对buffer_head对象的分配与销毁都要通过kmem_cache_alloc()函数和kmem_cache_free()函数来进行。
NOTE!
不要把bh_cachepSLAB分配器缓存和缓冲区本身相混淆。
前者只是buffer_head对象所使用的内存高速缓存,并不与块设备打交道,而仅仅是一种有效管理buffer_head对象所占用内存的方式。
后者则是块设备中的数据块所使用的内存高速缓存。
但是这二者又是相互关联的,也即缓冲区缓存的实现是以bh_cachepSLAB分配器缓存为基础的。
而我们这里所说的bcache机制包括缓冲区头部和缓冲区本身这两个方面的概念。
bh_cachep定义在fs/dcache.c文件中,并在函数vfs_caches_init()中被初始化,也即通过调用kmem_cache_create()函数来创建bh_cachep这个SLAB分配器缓存。
注:
函数vfs_caches_init()的工作就是调用kmem_cache_create()函数来为VFS创建各种SLAB分配器缓存,包括:
names_cachep、filp_cachep、dquot_cachep和bh_cachep等四个SLAB分配器缓存。
2.3bcache中的缓冲区头部对象链表
一个缓冲区头部对象buffer_head总是处于以下四种状态之一:
1.
未使用(unused)状态:
该对象是可用的,但是其b_data指针为NULL,也即这个缓冲区头部没有和一个缓冲区相关联。
2.
空闲(free)状态:
其b_data指针指向一个空闲状态下的缓冲区(也即该缓冲区没有具体对应块设备中哪个数据块);
而b_dev域值为B_FREE(值为0xffff)。
3.
正在使用(inuse)状态:
其b_data指针指向一个有效的、正在使用中的缓冲区,而b_dev域则指明了相应的块设备标识符,b_blocknr域则指明了缓冲区所对应的块号。
4.
异步(async)状态:
其b_data域指向一个用来实现pageI/O操作的临时缓冲区。
为了有效地管理处于上述这些不同状态下的缓冲区头部对象,bcache机制采用了各种链表来组
织这些对象(这一点,bcache机制与VFS的其它cache机制是相同的):
哈希链表:
所有buffer_head对象都通过其b_next与b_pprev两个指针域链入哈希链表中,从而可以加快对buffer_head对象的查找(lookup)。
最近最少使用链表lru_list:
每个处在inuse状态下的buffer_head对象都通过b_next_free和b_prev_free这两个指针链入某一个lru_list链表中。
空闲链表free_list:
每一个处于free状态下的buffer_head对象都根据它所关联的空闲缓冲区的大小链入某个free_list链表中(也是通过b_next_free和b_prev_free这两个指针)。
未使用链表unused_list:
所有处于unused状态下的buffer_head对象都通过指针域b_next_free和b_prev_free链入unused_list链表中。
5.inode对象的脏缓冲区链表i_dirty_buffers:
如果一个脏缓冲区有相关联的inode对象的话,那么他就通过其b_inode_buffers指针域链入其所属的inode对象的i_dirty_buffers链表中。
下面,我们分别详细阐述上述这些链表。
2.3.1
哈希链表
内核对buffer_head对象的查找是相当频繁的,因此为了加快查找速度,bcache机制使用哈希链表来管理bcache中的每一个buffer_head对象。
每一个buffer_head对象都根据其中的设备标识符b_dev和块号b_blocknr来确定他所属的哈希链表,并通过b_next和b_pprev这两个指针域链入他应该所属的哈希链表中。
每个哈希链表表头都是一个buffer_head类型的指针,所有的表头指针放在一起就组成一个哈希链表表头指针数组,该数组的首地址由变量hash_table定义。
有关哈希链表的定义如下(Buffer.c):
staticunsignedintbh_hash_mask;
staticunsignedintbh_hash_shift;
staticstructbuffer_head**hash_table;
staticrwlock_thash_table_lock=RW_LOCK_UNLOCKED;
其中,bh_hash_mask和bh_hash_shift变量的含义与icache机制中inode哈希链表的i_hash_mask和i_hash_shift的含义相同。
而读写锁hash_table_lock则用来对buffer_head哈希链表进行访问(读、写)保护,以实现对哈希链表的互斥访问。
哈希链表表头数组hash_table的初始化是在buffer_init()函数中完成的。
该函数实现整个bcache机制的初始化工作。
n
哈希函数(也称为「散列」函数)
二元组(设备标识符,块号)唯一地确定bcache中的一个buffer_head对象。
宏_hashfn()被定义为用来计算一个二元组(dev,block)所对应的散列值(buffer.c):
#define_hashfn(dev,block)/
((((dev)<
<
(bh_hash_shift-6))^((dev)<
(bh_hash_shift-9)))^/
(((block)<
(bh_hash_shift-6))^((block)>
>
13)^/
((block)<
(bh_hash_shift-12))))
#definehash(dev,block)hash_table[(_hashfn(HASHDEV(dev),block)&
bh_hash_mask)]
而宏hash()则根据宏_hashfn()生成的散列值来索引hash_table数组,从而得到二元组(dev,block)所确定的buffer_head对象所属哈希链表的表头指针。
2.3.2
未使用的buffer_head对象链表unused_list
所有处于unused状态下的buffer_head对象都通过b_next_free和b_prev_free指针链入未使用链表unused_list中。
变量unused_list定义了未使用链表的表头指针,如下所示(buffer.c):
staticstructbuffer_head*unused_list;
staticintnr_unused_buffer_heads;
staticspinlock_tunused_list_lock=SPIN_LOCK_UNLOCKED;
staticDECLARE_WAIT_QUEUE_HEAD(buffer_wait);
其中,变量nr_unused_buffer_heads表示unused_list链表中buffer_head对象的个数,而自旋锁unused_list_lock则是unused_list链表的访问保护锁,用以实现对unused_list链表的互斥访问。
宏MAX_UNUSED_BUFFERS(通常值为36)定义了unused_list链表中buffer_head对象的最大个数。
而宏NR_RESERVED(通常为16)则定义了unused_list链表中buffer_head对象的最少个数。
当缓冲区首部对象不再被使用时,如果unused_list链表元素个数小于MAX_UNUSED_BUFFERS值时,就将其插入到unused_list链表中;
否则就将其直接释放给bh_cachep这个SLAB分配器缓存。
而当需要一个buffer_head对象时,只要unused_list链表元素个数不小于NR_RESERVED,那就应该首先从unused_list链表中进行分配;
只有在unused_list链表元素个数小于NR_RESERVED时,才从bh_cachep这个SLAB分配器缓存中分配。
unused_list链表中的NR_RESERVED个元素被保留给页I/O操作,内核使用这个子集来避免由于缺乏空闲缓冲区头部而产生死锁。
综上所述,unused_list链表可以被看作是buffer_head对象的SLAB分配器缓存bh_cachep和bcache机制之间的一个中间辅助缓存。
从unused_list链表中获取一个buffer_head对象
Linux在buffer.c文件中实现了函数get_unused_buffer_head(),用来从unused_list链表中得到一个未使用的buffer_head对象。
该函数实际上是一个基于kmem_cache_alloc()函数的高层分配接口,如下所示:
/*
*ReserveNR_RESERVEDbufferheadsforasyncIOrequeststoavoid
*no-buffer-headdeadlock.ReturnNULLonfailure;
waitingfor
*bufferheadsisnowhandledincreate_buffers().
*/
staticstructbuffer_head*
get_unused_buffer_head(intasync)
{
structbuffer_head*bh;
spin_lock(&
unused_list_lock);
if(nr_unused_buffer_heads>
NR_RESERVED){
bh=unused_list;
unused_list=bh->
b_next_free;
nr_unused_buffer_heads--;
spin_unlock(&
returnbh;
}
/*Thisiscritical.Wecan'
tswapoutpagestoget
*morebufferheads,becausetheswap-outmayneed
*morebuffer-headsitself.ThusSLAB_BUFFER.
if((bh=kmem_cache_alloc(bh_cachep,SLAB_BUFFER))!
=NULL){
memset(bh,0,sizeof(*bh));
init_waitqueue_head(&
bh->
b_wait);
*Ifweneedanasyncbuffer,usethereservedbufferheads.
if(async){
if(unused_list){
#if0
*(Pendingfurtheranalysis...)
*Ordinary(non-async)requestscanuseadifferentmemorypriority
*tofreeuppages.Anyswappingthusgeneratedwilluseasync
*bufferheads.
if(!
async&
&
(bh=kmem_cache_al
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VFS 缓冲区 缓存 BufferCache 实现 原理 剖析