VFS
缓冲区缓存Buffer Cache实现原理剖析
(By 詹荣开)
Copyright © 2002 by 詹荣开
E-mail:zhanrk@sohu.com
Linux- 2.4.0
Version 1.0.0, 2002-9-16
摘要:本文主要从内核实现的角度分析Linux 2.4.0内核虚拟文件系统(VFS)中的缓冲区缓存(Buffer Cache)的实现原理。本文是为那些想要深入分析Linux文件系统原理的读者而写的。
关键词:文件系统、虚拟文件系统、VFS、缓冲区缓存
申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文文件协议(GFDL)。
你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给:
The Free Software Foundation, Inc., 675 Mass Ave , Cambridge ,MA02139, USA
欢迎各位指出文档中的错误与疑问。
Table of Contents
Chapter 1 概述…...……………………………………………………………….…1
Chapter 2 Buffer Cache的数据结构…………………………………………….….2
2.1 缓冲区头部对象buffer_head……………………………………………………….2
2.2 buffer_head对象的SLAB分配器缓存……………………………………………..5
2.3 bcache中的缓冲区头部对象链表…………………………………………………..6
2.4 对buffer_head对象链表的操作……………………………………………..……12
Chapter 3 对缓冲区的操作………………………………………………………..19
3.1 缓冲区的分配………………………………………………………………..…….18
3.2 缓冲区访问接口getblk/brelse…………………………………………..……….22
3.3数据块读接口bread…………………………………………………….………….24
3.4 对inode的i_dirty_buffers链表中的脏缓冲区的操作………………..…………24
Chapter 4 bcache中脏缓冲区的同步机制………………………………….…….29
4.1 bcache中的脏缓冲区同步操作…………………………………………..………..29
4.2 缓冲区同步的系统调用………………………………………………..………….32
4.3 bdflush内核线程……………………………………………………….…………..33
4.4 kupdate内核线程…………………………………………………………………..37
Chapter 1 概述
我们都知道,UNIX操作系统通过在物理文件系统和块设备驱动程序之间引入了「缓冲区缓存」(Buffer Cache,以下简称bcache)这一软件cache机制,从而大大降低了操作系统内核对块设备的存取频率(实际上,包括Windows在内的大多数操作系统也是这么做的)。
由于bcache位于物理文件系统和块设备驱动程序之间,因此,但物理文件系统需要从块设备上读取数据时,它首先试图从bcache中去读。如果命中,则内核就不必在去访问慢速的块设备。否则如果命中失败,也即数据不在bcache中,则内核从块设备上读取相应的数据块,并将其在bcache中缓存起来,以备下次访问之用。
类似地,但物理文件系统需要向块设备上写数据时,也是先将数据写到相应的缓冲区中,并将这个缓冲区标记为脏(dirty),然后在将来的某些时侯将buffer cache中的数据真正地回写到块设备上,或者将该缓冲区直接丢弃。从而实现减少磁盘写操作的频率。
Chapter 2 Buffer Cache的数据结构
2.1 缓冲区头部对象buffer_head
我们都知道,每一个缓冲区都有一个缓冲区头部来唯一地标识与描述该缓冲区。Linux通过数据结构buffer_head来定义缓冲区头部。如下所示(include/linux/fs.h):
struct buffer_head {
/* First cache line: */
struct buffer_head *b_next; /* Hash queue list */
unsigned long b_blocknr; /* block number */
unsigned short b_size; /* block size */
unsigned short b_list; /* List that this buffer appears */
kdev_t b_dev; /* device (B_FREE = free) */
atomic_t b_count; /* users using this block */
kdev_t b_rdev; /* Real device */
unsigned long b_state; /* buffer state bitmap (see above) */
unsigned long b_flushtime; /* Time when (dirty) buffer should be written */
struct buffer_head *b_next_free;/* lru/free list linkage */
struct buffer_head *b_prev_free;/* doubly linked list of buffers */
struct buffer_head *b_this_page;/* circular list of buffers in one page */
struct buffer_head *b_reqnext; /* request queue */
struct buffer_head **b_pprev; /* doubly linked list of hash-queue */
char * b_data; /* pointer to data block (512 byte) */
struct page *b_page; /* the page this bh is mapped to */
void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O completion */
void *b_private; /* reserved for b_end_io */
unsigned long b_rsector; /* Real buffer location on disk */
wait_queue_head_t b_wait;
struct inode * b_inode;
struct list_head b_inode_buffers; /* doubly linked list of inode dirty buffers */
};
各字段的含义如下:
1.b_next指针:指向哈希链表中的下一个buffer_head对象。
2.b_blocknr:本缓冲区对应的块号(block number)。
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种:
/* bh state bits */
#define BH_Uptodate 0 /* 1 if the buffer contains valid data */
#define BH_Dirty 1 /* 1 if the buffer is dirty */
#define BH_Lock 2 /* 1 if the buffer is locked */
#define BH_Req 3 /* 0 if the buffer has been invalidated */
#define BH_Mapped 4 /* 1 if the buffer has a disk mapping */
#define BH_New 5 /* 1 if the buffer is new and not yet written out */
#define BH_Protected 6 /* 1 if the buffer is protected */
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.2 buffer_head对象的SLAB分配器缓存
缓冲区头部对象buffer_head本身有一个叫做bh__cachep的slab分配器缓存。因此对buffer_head对象的分配与销毁都要通过kmem_cache_alloc()函数和kmem_cache_free()函数来进行。
NOTE!不要把bh_cachep SLAB分配器缓存和缓冲区本身相混淆。前者只是buffer_head对象所使用的内存高速缓存,并不与块设备打交道,而仅仅是一种有效管理buffer_head对象所占用内存的方式。后者则是块设备中的数据块所使用的内存高速缓存。但是这二者又是相互关联的,也即缓冲区缓存的实现是以bh_cachep SLAB分配器缓存为基础的。而我们这里所说的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.3 bcache中的缓冲区头部对象链表
一个缓冲区头部对象buffer_head总是处于以下四种状态之一:
1. 未使用(unused)状态:该对象是可用的,但是其b_data指针为NULL,也即这个缓冲区头部没有和一个缓冲区相关联。
(By 詹荣开)
Copyright © 2002 by 詹荣开
E-mail:zhanrk@sohu.com
Linux- 2.4.0
Version 1.0.0, 2002-9-16
摘要:本文主要从内核实现的角度分析Linux 2.4.0内核虚拟文件系统(VFS)中的缓冲区缓存(Buffer Cache)的实现原理。本文是为那些想要深入分析Linux文件系统原理的读者而写的。
关键词:文件系统、虚拟文件系统、VFS、缓冲区缓存
申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文文件协议(GFDL)。
你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给:
The Free Software Foundation, Inc., 675 Mass Ave , Cambridge ,MA02139, USA
欢迎各位指出文档中的错误与疑问。
Table of Contents
Chapter 1 概述…...……………………………………………………………….…1
Chapter 2 Buffer Cache的数据结构…………………………………………….….2
2.1 缓冲区头部对象buffer_head……………………………………………………….2
2.2 buffer_head对象的SLAB分配器缓存……………………………………………..5
2.3 bcache中的缓冲区头部对象链表…………………………………………………..6
2.4 对buffer_head对象链表的操作……………………………………………..……12
Chapter 3 对缓冲区的操作………………………………………………………..19
3.1 缓冲区的分配………………………………………………………………..…….18
3.2 缓冲区访问接口getblk/brelse…………………………………………..……….22
3.3数据块读接口bread…………………………………………………….………….24
3.4 对inode的i_dirty_buffers链表中的脏缓冲区的操作………………..…………24
Chapter 4 bcache中脏缓冲区的同步机制………………………………….…….29
4.1 bcache中的脏缓冲区同步操作…………………………………………..………..29
4.2 缓冲区同步的系统调用………………………………………………..………….32
4.3 bdflush内核线程……………………………………………………….…………..33
4.4 kupdate内核线程…………………………………………………………………..37
Chapter 1 概述
我们都知道,UNIX操作系统通过在物理文件系统和块设备驱动程序之间引入了「缓冲区缓存」(Buffer Cache,以下简称bcache)这一软件cache机制,从而大大降低了操作系统内核对块设备的存取频率(实际上,包括Windows在内的大多数操作系统也是这么做的)。
由于bcache位于物理文件系统和块设备驱动程序之间,因此,但物理文件系统需要从块设备上读取数据时,它首先试图从bcache中去读。如果命中,则内核就不必在去访问慢速的块设备。否则如果命中失败,也即数据不在bcache中,则内核从块设备上读取相应的数据块,并将其在bcache中缓存起来,以备下次访问之用。
类似地,但物理文件系统需要向块设备上写数据时,也是先将数据写到相应的缓冲区中,并将这个缓冲区标记为脏(dirty),然后在将来的某些时侯将buffer cache中的数据真正地回写到块设备上,或者将该缓冲区直接丢弃。从而实现减少磁盘写操作的频率。
Chapter 2 Buffer Cache的数据结构
2.1 缓冲区头部对象buffer_head
我们都知道,每一个缓冲区都有一个缓冲区头部来唯一地标识与描述该缓冲区。Linux通过数据结构buffer_head来定义缓冲区头部。如下所示(include/linux/fs.h):
struct buffer_head {
/* First cache line: */
struct buffer_head *b_next; /* Hash queue list */
unsigned long b_blocknr; /* block number */
unsigned short b_size; /* block size */
unsigned short b_list; /* List that this buffer appears */
kdev_t b_dev; /* device (B_FREE = free) */
atomic_t b_count; /* users using this block */
kdev_t b_rdev; /* Real device */
unsigned long b_state; /* buffer state bitmap (see above) */
unsigned long b_flushtime; /* Time when (dirty) buffer should be written */
struct buffer_head *b_next_free;/* lru/free list linkage */
struct buffer_head *b_prev_free;/* doubly linked list of buffers */
struct buffer_head *b_this_page;/* circular list of buffers in one page */
struct buffer_head *b_reqnext; /* request queue */
struct buffer_head **b_pprev; /* doubly linked list of hash-queue */
char * b_data; /* pointer to data block (512 byte) */
struct page *b_page; /* the page this bh is mapped to */
void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O completion */
void *b_private; /* reserved for b_end_io */
unsigned long b_rsector; /* Real buffer location on disk */
wait_queue_head_t b_wait;
struct inode * b_inode;
struct list_head b_inode_buffers; /* doubly linked list of inode dirty buffers */
};
各字段的含义如下:
1.b_next指针:指向哈希链表中的下一个buffer_head对象。
2.b_blocknr:本缓冲区对应的块号(block number)。
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种:
/* bh state bits */
#define BH_Uptodate 0 /* 1 if the buffer contains valid data */
#define BH_Dirty 1 /* 1 if the buffer is dirty */
#define BH_Lock 2 /* 1 if the buffer is locked */
#define BH_Req 3 /* 0 if the buffer has been invalidated */
#define BH_Mapped 4 /* 1 if the buffer has a disk mapping */
#define BH_New 5 /* 1 if the buffer is new and not yet written out */
#define BH_Protected 6 /* 1 if the buffer is protected */
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.2 buffer_head对象的SLAB分配器缓存
缓冲区头部对象buffer_head本身有一个叫做bh__cachep的slab分配器缓存。因此对buffer_head对象的分配与销毁都要通过kmem_cache_alloc()函数和kmem_cache_free()函数来进行。
NOTE!不要把bh_cachep SLAB分配器缓存和缓冲区本身相混淆。前者只是buffer_head对象所使用的内存高速缓存,并不与块设备打交道,而仅仅是一种有效管理buffer_head对象所占用内存的方式。后者则是块设备中的数据块所使用的内存高速缓存。但是这二者又是相互关联的,也即缓冲区缓存的实现是以bh_cachep SLAB分配器缓存为基础的。而我们这里所说的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.3 bcache中的缓冲区头部对象链表
一个缓冲区头部对象buffer_head总是处于以下四种状态之一:
1. 未使用(unused)状态:该对象是可用的,但是其b_data指针为NULL,也即这个缓冲区头部没有和一个缓冲区相关联。