VFS(映射层)和各种文件系统以叫做“块”的逻辑单位组织磁盘数据。在Linux内核的旧版本中,主要有两种不同的磁盘高速缓存:页高速缓存和缓冲区高速缓存,前者用来存放访问磁盘文件内容时生成的磁盘数据页,后者把通过VFS(管理磁盘文件系统)访问的块的内容保留在内存中。
从2.4.10的稳定版本开始,缓冲区高速缓存其实就不存在了。事实上,由于效率的原因,不再单独分配块缓冲区;相反,把它们存放在叫做“缓冲区页”的专门页中,而缓冲区页保存在页高速缓存中。
缓冲区页在形式上就是与称做“缓冲区头”的附加描述符相关的数据页,其主要目的是快速确定页中的一个块在磁盘中的地址。实际上,页高速缓存内的页中的一大块数据在磁盘上的地址不一定是相邻的。
1 块缓冲区和缓冲区头
每个块缓冲区都有buffer_head类型的缓冲区头描述符。该描述符包含内核必须了解的、有关如何处理块的所有信息。因此,在对所有块操作之前,内核检查缓冲区首部。缓冲区首部的字段位于/include/linux/Buffer_head.h:
struct buffer_head {
unsigned long b_state; /* 缓冲区状态标志 */
struct buffer_head *b_this_page; /* 指向缓冲区页的链表中的下一个元素的指针 */
struct page *b_page; /* 指向拥有该块的缓冲区页的描述符的指针 */
sector_t b_blocknr; /* 与块设备相关的块号(起始逻辑块号) */
size_t b_size; /* 块大小 */
char *b_data; /* 块在缓冲区页内的位置 */
struct block_device *b_bdev; /* 指向块设备描述符的指针 */
bh_end_io_t *b_end_io; /* I/O完成方法 */
void *b_private; /* 指向I/O完成方法数据的指针 */
struct list_head b_assoc_buffers; /* 为与某个索引节点相关的间接块的链表提供的指针 */
atomic_t b_count; /* 块使用计数器 */
};
缓冲区头的两个字段编码表示块的磁盘地址:b_bdev字段表示包含块的块设备,通常是磁盘或分区;而b_blocknr字段存放逻辑块号,即块在磁盘或分区中的编号。
b_data字段表示块缓冲区在缓冲区页中的位置。实际上,这个位置的编号依赖于页是否在高端内存。如果页在高端内存,则b_data字段存放的是块缓冲区相对于页的起始位置的偏移量,否则,b_data存放的就是是块缓冲区的线性地址。
b_state字段可以存放几个标志。其中一些标志是通用的,我们在下面把它们列出来了。每个文件系统还可以定义自己的私有缓冲区首部标志。
BH_Uptodate:缓冲区包含有效数据时被置位
BH_Dirty:如果缓冲区脏就置位(表示缓冲区中的数据必须写回块设备)
BH_Lock:如果缓冲区加锁就置位,通常发生在缓冲区进行磁盘传输时
BH_Req:如果已经为初始化缓冲区而请求数据传输就置位
BH_Mapped:如果缓冲区被映射到磁盘就置位,即:如果相应的缓冲区首部的b_bdev和b_blocknr是有效的就置位
BH_New:如果相应的块刚被分配而还没有被访问过就置位
BH_Async_Read:如果在异步地读缓冲区就置位
BH_Async_Write:如果在异步地写缓冲区就置位
BH_Delay:如果还没有在磁盘上分配缓冲区就置位
BH_Boundary:如果两个相邻的块在其中一个提交之后不再相邻就置位
BH_Write_EIO:如果写块时出现I/O错误就置位
BH_Ordered:如果必须严格地把块写到在它之前提交的块的后面就置位(用于日志文件系统)
BH_Eopnotsupp:如果块设备的驱动程序不支持所请求的操作就置位
缓冲区头有它们自己的slab分配器高速缓存,其描述符kmem_cache_s存在变量bh_cachep中。alloc_buffer_head()和free_buffer_head()函数分别用于获取和释放缓冲区首部。
struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)
{
struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags);
if (ret) {
get_cpu_var(bh_accounting).nr++;
recalc_bh_state();
put_cpu_var(bh_accounting);
}
return ret;
}
void free_buffer_head(struct buffer_head *bh)
{