linux1.0的磁盘缓存buffer cache

本文深入探讨Linux磁盘缓存机制,特别是早期版本中buffercache的实现细节,包括其数据结构、工作流程、同步控制及并发管理等方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


磁盘缓存
磁盘缓存和其他类型的缓存系统目的一样,都是为了提高系统的性能。磁盘缓存的方法是利用内存来保存部分磁盘数据,内存数据的读写速度远远快于磁盘读写,来提高系统性能。

缓存的位置
内核空间(kernel space):缓存在内核中实现。对应用程序来说是透明的。
用户空间(user space)缓存:由应用程序自己管理缓存,如C标准库中的stdio,就实现了缓存功能。用户空间程序更了解实际数据的使用情况,缓存策略也会更有针对性,比内核的缓存策略更加优化。如数据库系统一般会使用自己的缓存管理功能来代替内核的。

使用场景:
读磁盘块:如果要读的磁盘块已在内存中,直接使用内存中数据;否则给磁盘块分配内存空间,将磁盘块数据读入到内存。
写磁盘块:如果要写的磁盘块已在内存中,直接将数据写入内存中;否则给磁盘块分配内存空间,在将数据写入内存中。

内存和磁盘数据的同步
对于读操作来说,内存数据和磁盘数据一直一致,无需同步。
对于写操作来说,内存数据和磁盘数据会不一致,需要一定的同步方法。

Linux的磁盘缓存
Linux从诞生起就提供了磁盘缓存功能,磁盘缓存的实现也经历了几代:
1)buffer cache
从linux诞生到2.0版本,都采用这个方式。buffer cache以磁盘块为单位。
2) buffer cache和page cache
在linux 2.2和2.4中采用,存在两套缓存系统,page cache以page为单位实现缓存。
3) page cache
从linux 2.4.10开始采用,只有一套缓存page cache。原来的buffer cache的功能通过page cache来实现。

本文主要介绍linux 1.0中的buffer cache实现,即第一代实现。


接口
buffer cache的几个主要接口函数,基于这几个函数,可以实现磁盘读写。
extern struct buffer_head * getblk(dev_t dev, int block, int size);
extern void brelse(struct buffer_head * buf);
extern struct buffer_head * bread(dev_t dev, int block, int size);
extern struct buffer_head * breada(dev_t dev,int block,...);
extern unsigned long bread_page(unsigned long addr,dev_t dev,int b[],int size,int prot);


实现分析
1)数据结构
struct buffer_head {
 # 实际的数据
 char * b_data; /* pointer to data block (1024 bytes) */
 # 磁盘块大小,不同文件系统的block大小不同
 unsigned long b_size; /* block size */
 unsigned long b_blocknr; /* block number */
 dev_t b_dev; /* device (0 = free) */
 unsigned short b_count; /* users using this block */
 unsigned char b_uptodate;
 unsigned char b_dirt; /* 0-clean,1-dirty */
 unsigned char b_lock; /* 0 - ok, 1 -locked */
 unsigned char b_req; /* 0 if the buffer has been invalidated */
 struct wait_queue * b_wait;
 struct buffer_head * b_prev; /* doubly linked list of hash-queue */
 struct buffer_head * b_next;
 struct buffer_head * b_prev_free; /* doubly linked list of buffers */
 struct buffer_head * b_next_free;
 struct buffer_head * b_this_page; /* circular list of buffers in one page */
 struct buffer_head * b_reqnext; /* request queue */
};
buffer_head表示一个缓存快,和一个磁盘块对应。
(dev, blocknu)用来表示该缓存块分配给哪个块磁盘。
b_data用来存储实际的缓存数据。
b_count表示有多少进程(process)要使用该缓存快。如果b_count == 0,表示该缓存块是空闲(当前没有进程使用),可以分配给其他磁盘块。

hash_table结构
快来快速查找给定的磁盘块是否已经在缓存中。
已经分配的缓存块(buffer_head)全部保存在hash_table中,采用静态hash实现。通过(dev, blocknu)将缓存快散列到hash table中。具有相同hash value的缓存块组织成双向循环(circular)链表,通过b_prev和b_next实现。

free list结构
缓存块的基于LRU(least recently used)排序的列表。列表的第一项是LRU项。一个缓存块被使用后(调用getblk),放在该列表的最后。该列表中的缓存块不都是空闲的,需要根据b_count来决定。b_count==0表示空闲,可以分配给其他磁盘块。通过b_prev_free和b_this_page来实现。

2)同步控制
与同步有关的字段及初始化值:
b_dirt=0;
b_uptodate=0;
b_req=0;

b_dirt,表示缓存数据是否和磁盘数据一致,1表示数据不一致。在写缓存块操作中,需要将其设置为1。内核会按照一定策略将b_dirt == 1的缓存块内容写回到磁盘上。用户执行sync命令,也可以将缓存中所有不一致缓存块写回到磁盘上。驱动程序将数据写回磁盘后,会将b_dirt修改为0。

b_uptodate,是否同步了缓冲块和块设备的内容,或者说缓存块中的数据是否为最新。

针对读磁盘:当把内容从硬盘读入缓冲块后,b_uptodate被赋值为1。设备驱动程序会在数据读取完毕后设置该位。

针对写磁盘:当把数据被写入缓冲块时,需要将该位设置成1,b_dirt也设置为1。


3)并发控制
当驱动程序在读写磁盘块时,会将相应的缓存块上锁。此时缓存块中的数据处于不一致状态,进程不能使用。
b_count=1; 表示有多少个进程需要使用这个缓存块;
getblk,会增加b_count
brelse,会减小b_count

b_lock;锁,用来同步驱动程序进程和其他使用该缓存块的进程。1表示该缓存块上锁。
wait_on_buffer会检查该标志位,如果已经上锁,则该进程进入等待状态。
底层驱动程序会在读写磁盘块时,对相应缓存块上锁。ll_rw_block

b_wait;等待使用该缓存块的队列。

内存分配
缓存块中的b_data是系统根据需要动态分配的,每次分配一页内存(page)。每页内存可以容纳4块磁盘块(BLOCK_SIZE = 1024)。

getblk
getblk是最复杂的算法:处理一下情况
1) 磁盘块已经在内存中,直接返回该缓存块
2)不在内存中
2.1)有可供分配的缓存块(tmp->b_count==0),将该缓冲块分配给相应的磁盘块。
2.2)没有可供分配的缓存块
2.2.1)系统剩余内存足够,分配额外的缓冲块。
2.2.2)等待其他进程释放缓冲块。

参考书
The Design and Implementation of the UNIX Operating System (1986) by M Bach  
该书第三章对buffer cache的设计做了详尽的描述,尤其是getblk算法的实现。

buffer cache的对其他缓存系统的设计和实现有很大的参考价值。其中的基本原理具有普适性:如缓存块的管理,缓存和实际数据的同步,并发控制等。

### Linux 缓冲区缓存机制及其性能优化 Linux 的缓冲区缓存Buffer Cache)是一种用于提高文件 I/O 性能的内存管理技术。它通过将磁盘上的数据临时存储到内存中来减少频繁访问硬盘的需求,从而提升整体系统效率。 #### 缓冲区缓存的工作原理 缓冲区缓存主要用于处理块设备的数据读写操作。当应用程序请求从磁盘读取数据时,操作系统会先检查该数据是否已经存在于缓冲区缓存中。如果存在,则直接返回缓存中的数据而无需再次访问磁盘[^2]。这种行为显著减少了磁盘 I/O 开销并提高了响应速度。 对于写入操作而言,在某些情况下并不会立即将修改后的数据同步回磁盘;而是暂时保存于缓冲区内等待适当时间后再批量提交至永久储存介质上完成实际更新过程——这种方式被称为延迟写策略(deferred write strategy). #### 解决与缓冲区缓存相关的性能问题的方法 1. **合理利用缓存行大小** 考虑到 CPU 高速缓存是以固定长度 (通常是64字节) 作为基本单元进行加载和存储操作这一特性,在设计程序结构或者调整硬件配置参数时候应该尽量使连续访问地址范围内的有效信息量接近甚至刚好填满整个cache line以便充分利用有限资源降低miss rate进而达到加速目的. 2. **避免动态更改渲染目标** 移动端图形处理器(Mobile GPU) 对于render buffer的表现较差。因此不建议频繁地丢弃旧的目标然后再创建新的目标(discard old targets then create new ones),因为这可能会引起不必要的开销影响最终呈现效果的质量以及流畅度等方面表现不佳的情况发生 。相反有时候简单执行一次清除命令clear render buffers反而有助于改善状况因为它允许GPU快速标记这些区域为空闲状态同时还能更好地对其进行内部管理工作流程安排等等好处多多[^1]. 3. **代码层面的具体实践案例分析** 下面给出了一段来自linux kernel源码片段展示如何解析字符串形式表示的大整数值转换成相应类型的变量实例说明如下所示: ```c unsigned long long memparse(char *ptr, char **retptr); ``` 此函数接受两个指针类型参数分别为输入待解析字符序列起始位置以及输出剩余未匹配部分首址所在处所指向的位置经过一系列逻辑判断之后成功得到对应的结果值即以十进制数表达方式记录下来的物理内存容量大小单位通常为bytes等标准计量尺度之一[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值