linux 高速缓冲区 buffer cache

时间:2021-09-04 14:28:52
/*
* linux/fs/buffer.c
*
* (C) 1991 Linus Torvalds
*/

/*
* 'buffer.c' implements the buffer-cache functions. Race-conditions have
* been avoided by NEVER letting a interrupt change a buffer (except for the
* data, of course), but instead letting the caller do it. NOTE! As interrupts
* can wake up a caller, some cli-sti sequences are needed to check for
* sleep-on-calls. These should be extremely quick, though (I hope).
*/

/*
* NOTE! There is one discordant note here: checking floppies for
* disk change. This is where it fits best, I think, as it should
* invalidate changed floppy-disk-caches.
*/

#include <stdarg.h>

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/io.h>

extern int end;//由连接程序ld 生成的表明内核码的末端,也就是内核模块末端的位置,内核模块占用(0---end),end之后的地址则为高速缓冲区的空间

struct buffer_head * start_buffer = (struct buffer_head *) &end;//高速缓冲区的开始位置

struct buffer_head * hash_table[NR_HASH];//HASH表,307项

static struct buffer_head * free_list;//空闲缓冲块链表头指针

static struct task_struct * buffer_wait = NULL;//等待空闲块而睡眠的任务队列

int NR_BUFFERS = 0;//系统含有缓冲块的个数


static inline void wait_on_buffer(struct buffer_head * bh)// 等待指定缓冲区解锁

{
cli();//关中断

while (bh->b_lock)//当前缓冲块上锁

sleep_on(&bh->b_wait);//将当前任务置为不可中断的等待状态,并将此放入睡眼队列bh->b_wait中,在sleep_on函数中又调用了schedule函数

sti();//开中断

}

int sys_sync(void)//设备数据同步,同步设备和内存高速缓冲区的内 容

{
int i;
struct buffer_head * bh;

sync_inodes(); /* write out inodes into buffers *///调用inodes同步函数,把i节电表中的所修改过的i节电写入高速缓冲区

bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {// 扫描所有高速缓冲区,对于已被修改的缓冲块产生写盘请求,将缓冲中数据与设备中同步。

wait_on_buffer(bh);// 等待指定缓冲区解锁。

if (bh->b_dirt)//修改标志0为未修改,1为修改

ll_rw_block(WRITE,bh);//产生写设备请求,底层读写数据块函数,主要创建读写设备的请示谋项,并插入到请求链表中去

}
return 0;
}

int sync_dev(int dev)//,原理同前,对指定设备进行高速缓冲数据与设备上数据的同步操作

{
int i;
struct buffer_head * bh;

bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {//先对高速缓冲区指定设备的数据进行同步,这一步同时可以将许多“脏块”变干净,为i节电同步提高效率

if (bh->b_dev != dev)
continue;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_dirt)
ll_rw_block(WRITE,bh);
}
sync_inodes();//调用inodes同步函数,把i节电表中的所修改过的i节电写入高速缓冲区

bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {//再对高速缓冲区指定设备的数据进行同步

if (bh->b_dev != dev)
continue;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_dirt)
ll_rw_block(WRITE,bh);
}
return 0;
}

void inline invalidate_buffers(int dev)//使指定设备在高速缓冲区中的数据无效,

{//扫描高速缓冲中的所有缓冲块,对于指定设备的缓冲区,复位其有效(更新)标志和已修改标志

int i;
struct buffer_head * bh;

bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) {
if (bh->b_dev != dev)
continue;
wait_on_buffer(bh);// 等待指定缓冲区解锁

if (bh->b_dev == dev)
bh->b_uptodate = bh->b_dirt = 0;//b_dirt, 0-clean,1-dirty 修改标志0为未修改,1为修改,,,,,,,

}//b_uptodate,更新标志,缓冲区上的数据是否为块上的数据同步,1为与硬盘上的数据同步一样

}

/*
* This routine checks whether a floppy has been changed, and
* invalidates all buffer-cache-entries in that case. This
* is a relatively slow routine, so we have to try to minimize using
* it. Thus it is called only upon a 'mount' or 'open'. This
* is the best way of combining speed and utility, I think.
* People changing diskettes in the middle of an operation deserve
* to loose :-)
*
* NOTE! Although currently this is only for floppies, the idea is
* that any additional removable block-device will use this routine,
* and that mount/open needn't know that floppies/whatever are
* special.本版本只支持软件盘移动设备,所以只判断了软盘
*///注意!尽管目前该子程序仅用于软盘,以后任何可移动介质的块设备都将使用该程序,mount/open 操作是不需要知道是否是软盘或其它什么特殊介质的

void check_disk_change(int dev)//检查磁盘是否更换,如果已更换就使对应高速缓冲区无效

{
int i;

if (MAJOR(dev) != 2)
return;
if (!floppy_change(dev & 0x03))
return;
for (i=0 ; i<NR_SUPER ; i++)//释放对应设备的i 节点位图和逻辑块位图所占的高速缓冲区

if (super_block[i].s_dev == dev)
put_super(super_block[i].s_dev);
invalidate_inodes(dev);//并使该设备的 i节电无效

invalidate_buffers(dev);//使指定设备在高速缓冲区中的数据无效,

}

#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)//hash 函数宏定义,采用关键字除留余数法

#define hash(dev,block) hash_table[_hashfn(dev,block)]//hash 表项的计算宏定义


static inline void remove_from_queues(struct buffer_head * bh)//从hash 队列和空闲缓冲队列中移走指定的缓冲块

{//hash队列是双向链表结构,空闲链表队列是双向循环链表结构

/* remove from hash-queue */
if (bh->b_next)
bh->b_next->b_prev = bh->b_prev;
if (bh->b_prev)
bh->b_prev->b_next = bh->b_next;
if (hash(bh->b_dev,bh->b_blocknr) == bh)// 这样判断正确吗?,如果该缓冲区是该队列的头一个块,则让hash 表的对应项指向本队列中的下一个缓冲区

hash(bh->b_dev,bh->b_blocknr) = bh->b_next;
/* remove from free list */
if (!(bh->b_prev_free) || !(bh->b_next_free))
panic("Free block list corrupted");
bh->b_prev_free->b_next_free = bh->b_next_free;
bh->b_next_free->b_prev_free = bh->b_prev_free;
if (free_list == bh)// 如果空闲链表头指向本缓冲区,则让其指向下一缓冲区。

free_list = bh->b_next_free;
}

static inline void insert_into_queues(struct buffer_head * bh)//将指定缓冲块插入空闲链表尾并放入hash 队列中

{
/* put at end of free list */
bh->b_next_free = free_list;
bh->b_prev_free = free_list->b_prev_free;
free_list->b_prev_free->b_next_free = bh;
free_list->b_prev_free = bh;
/* put the buffer in new hash-queue if it has a device */
bh->b_prev = NULL;
bh->b_next = NULL;
if (!bh->b_dev)
return;
bh->b_next = hash(bh->b_dev,bh->b_blocknr);
hash(bh->b_dev,bh->b_blocknr) = bh;//插入HASH队列中

bh->b_next->b_prev = bh;//引句前应加入“if (bh->b_next)”判断,因为第一次插入时bh->b_next一定为0(NULL),见line 161行

}

static struct buffer_head * find_buffer(int dev, int block)//利用hash表在高速缓冲中寻找给定设备和指定块的缓冲区块

{
struct buffer_head * tmp;

for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)
if (tmp->b_dev==dev && tmp->b_blocknr==block)
return tmp;
return NULL;
}

/*
* Why like this, I hear you say... The reason is race-conditions.
* As we don't lock buffers (unless we are readint them, that is),
* something might happen to it while we sleep (ie a read-error
* will force it bad). This shouldn't really happen currently, but
* the code is ready.
*/
struct buffer_head * get_hash_table(int dev, int block)//考虑到里程竞争的情况,,利用hash表在高速缓冲中寻找给定设备和指定块的缓冲区块

{
struct buffer_head * bh;

for (;;) {
if (!(bh=find_buffer(dev,block)))// 在高速缓冲中寻找给定设备和指定块的缓冲区,如果没有找到则返回NULL,退出

return NULL;
bh->b_count++;// 对该缓冲区增加引用计数,

wait_on_buffer(bh);//并等待该缓冲区解锁(如果已被上锁)

if (bh->b_dev == dev && bh->b_blocknr == block)// 由于经过了睡眠状态,因此有必要再验证该缓冲区块的正确性,并返回缓冲区头指针

return bh;
bh->b_count--;// 如果该缓冲区所属的设备号或块号在睡眠时发生了改变,则撤消对它的引用计数,重新寻找

}
}

/*
* Ok, this is getblk, and it isn't very clear, again to hinder
* race-conditions. Most of the code is seldom used, (ie repeating),
* so it should be much more efficient than it looks.
*
* The algoritm is changed: hopefully better, and an elusive bug removed.
*/
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)//宏定义用于同时判断缓冲区的修改标志和锁定标志,并且定义修改标志的权重比锁定标志大

struct buffer_head * getblk(int dev,int block)//取高速缓冲中指定的缓冲区块

{//检查所指定的缓冲区是否已经在高速缓冲中,如果不在,就需要在高速缓冲中建立一个指定设备的对应的数据块新项

struct buffer_head * tmp, * bh;

repeat:
if (bh = get_hash_table(dev,block))//考虑到里程竞争的情况,,利用hash表在高速缓冲中寻找给定设备和指定块的缓冲区块

return bh;
tmp = free_list;//HASH表没有找到,则从空闲链表中查找,首先让tmp 指向空闲链表的第一个空闲缓冲区头

do {
if (tmp->b_count)
continue;
if (!bh || BADNESS(tmp)<BADNESS(bh)) {//用的一种策略算法

bh = tmp;//

if (!BADNESS(tmp))
break;
}
/* and repeat until we find something good */
} while ((tmp = tmp->b_next_free) != free_list);//已为指定设备上的块取得对应的高速缓冲区

if (!bh) {//这说明没有找到指定的缓冲块,所有缓冲区都正被使用(所有缓冲区的头部引用计数>0),则睡眠,等待有空闲的缓冲区可用

sleep_on(&buffer_wait);//将当前任务置为不可中断的等待状态,并将此放入睡眼队列buffer_wait中,等再一次醒来运行时,执行下面的语句goto repeat;

goto repeat;
}
wait_on_buffer(bh);// 这里一定找找到了比较合适的空闲的缓冲块bh,等待指定缓冲区解锁

if (bh->b_count)// 如果该缓冲区又被占用(其他任务占用),只好重复上述过程

goto repeat;
while (bh->b_dirt) {// 如果该缓冲区已被修改,则将数据写盘,并再次等待缓冲区解锁

sync_dev(bh->b_dev);//对指定设备进行高速缓冲数据与设备上数据的同步操作

wait_on_buffer(bh);// 等待指定缓冲区解锁

if (bh->b_count)//由于wait_on_buffer函数可能引起进程睡眠,所以还要再一次检查,如果该缓冲区又被占用

goto repeat;
}
/* NOTE!! While we slept waiting for this block, somebody else might */
/* already have added "this" block to the cache. check it */
if (find_buffer(dev,block))//利用hash表在高速缓冲中寻找给定设备和指定块的缓冲区块,如果找到,则说明在我们进程睡眠期间已有别的进程将这缓冲块加入高速缓冲区中,则返回到repeat处

goto repeat;
/* OK, FINALLY we know that this buffer is the only one of it's kind, */
/* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */
bh->b_count=1;//以下的操作就是在高速缓冲区新建一个缓冲块,也就达到了本函数的目的

bh->b_dirt=0;
bh->b_uptodate=0;
remove_from_queues(bh);//从hash 队列和空闲缓冲队列中移走指定的缓冲块,让该缓冲块用于指定设备的缓冲块区

bh->b_dev=dev;
bh->b_blocknr=block;
insert_into_queues(bh);//将新的指定设备的缓冲块插入空闲链表尾并放入hash 队列中

return bh;
}

void brelse(struct buffer_head * buf)// 本任务释放指定的缓冲块区,当buf->b_count为0时,才真正的释放本缓冲块,好让别的进程别的设备应用

{
if (!buf)
return;
wait_on_buffer(buf);// 等待指定缓冲区解锁

if (!(buf->b_count--))//缓冲块区引用数减1,

panic("Trying to free free buffer");
wake_up(&buffer_wait);//唤醒*p所指向的任务。*p为任务等待队列头指针,由于新等待任务是插入等待队列头指针处的,因此唤醒的是最后进入等待队列的任务。

}

/*
* bread() reads a specified block and returns the buffer that contains
* it. It returns NULL if the block was unreadable.
*///首先在高速缓冲区申请一缓冲块,如是该缓冲块包含有效的数据 则返回,否则就从设备中读取指定的数据到这个高速缓冲区的缓冲块中

struct buffer_head * bread(int dev,int block)//// 从指定设备上读取指定的数据块,dev 为设备号

{
struct buffer_head * bh;

if (!(bh=getblk(dev,block)))// 在高速缓冲中申请一块缓冲区。如果返回值是NULL 指针,表示内核出错,死机

panic("bread: getblk returned NULL\n");
if (bh->b_uptodate)// 如果该缓冲区中的数据是有效的(已更新的)可以直接使用,则返回

return bh;
ll_rw_block(READ,bh);// 否则调用ll_rw_block()函数,产生读设备块请求。

wait_on_buffer(bh);//并等待缓冲区解锁

if (bh->b_uptodate)//wait_on_buffer函数调用 可能引起进程睡眠,如果该缓冲区已更新,则返回缓冲区头指针,退出

return bh;
brelse(bh);// 本任务释放指定的缓冲块区

return NULL;
}
//复制内存块从from 地址复制一块数据到to 位置

#define COPYBLK(from,to) \
__asm__("cld\n\t" \
"rep\n\t" \
"movsl\n\t" \
::"c" (BLOCK_SIZE/4),"S" (from),"D" (to) \
:"cx","di","si")

/*
* bread_page reads four buffers into memory at the desired address. It's
* a function of its own, as there is some speed to be got by reading them
* all at the same time, not waiting for one to be read, and then another
* etc.
*/
void bread_page(unsigned long address,int dev,int b[4])//// 读设备上一个页面(4 个缓冲块)的内容到内存指定的地址

{//address要保存的数据位置,dev设备号,b[4]含有4个设备数据块号的数组

struct buffer_head * bh[4];
int i;

for (i=0 ; i<4 ; i++)
if (b[i]) {
if (bh[i] = getblk(dev,b[i]))//取高速缓冲中指定的缓冲区块

if (!bh[i]->b_uptodate)
ll_rw_block(READ,bh[i]);//主要创建读写设备的请示谋项,

} else
bh[i] = NULL;
for (i=0 ; i<4 ; i++,address += BLOCK_SIZE)
if (bh[i]) {
wait_on_buffer(bh[i]);//等待指定缓冲区解锁

if (bh[i]->b_uptodate)
COPYBLK((unsigned long) bh[i]->b_data,address);//复制内存块从from 地址复制一块数据到to 位置

brelse(bh[i]);// 本任务释放指定的缓冲块区

}
}

/*
* Ok, breada can be used as bread, but additionally to mark other
* blocks for reading as well. End the argument list with a negative
* number.
*/
struct buffer_head * breada(int dev,int first, ...)// 从指定设备读取指定的一些块,参数为一系列指定的块号

{
va_list args;
struct buffer_head * bh, *tmp;

va_start(args,first);// 取可变参数表中第1 个参数(块号)

if (!(bh=getblk(dev,first)))//取高速缓冲中指定的缓冲区块

panic("bread: getblk returned NULL\n");
if (!bh->b_uptodate)
ll_rw_block(READ,bh);//主要创建读写设备的请示谋项

while ((first=va_arg(args,int))>=0) {// 然后顺序取可变参数表中其它预读块号,并作与上面同样处理,但不引用

tmp=getblk(dev,first);//取高速缓冲中指定的缓冲区块

if (tmp) {
if (!tmp->b_uptodate)
ll_rw_block(READA,bh);//主要创建读写设备的请示谋项

tmp->b_count--;
}
}
va_end(args);// 可变参数表中所有参数处理完毕

wait_on_buffer(bh);// 等待指定缓冲区解锁

if (bh->b_uptodate)
return bh;
brelse(bh);// 本任务释放指定的缓冲块区

return (NULL);
}

void buffer_init(long buffer_end)// 缓冲区初始化函数,

{//参数buffer_end 是指定的缓冲区内存的末端。对于系统有16MB 内存,则缓冲区末端设置为4MB,对于系统有8MB 内存,缓冲区末端设置为2MB

struct buffer_head * h = start_buffer;
void * b;
int i;

if (buffer_end == 1<<20)//如果缓冲区高端等于1Mb,则由于从640KB-1MB 被显示内存和BIOS 占用,因此实际可用缓冲区内存高端应该是640KB。否则内存高端一定大于1MB

b = (void *) (640*1024);//640K

else
b = (void *) buffer_end;
// 这段代码用于初始化缓冲区,建立空闲缓冲区环链表,并获取系统中缓冲块的数目。

// 操作的过程是从缓冲区高端开始划分1K 大小的缓冲块,与此同时在缓冲区低端建立描述该缓冲块

// 的结构buffer_head,并将这些buffer_head 组成双向链表。

// h 是指向缓冲头结构的指针,而h+1 是指向内存地址连续的下一个缓冲头地址,也可以说是指向h

// 缓冲头的末端外。为了保证有足够长度的内存来存储一个缓冲头结构,需要b 所指向的内存块

// 地址 >= h 缓冲头的末端,也即要>=h+1。

while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {//BLOCK_SIZE= =1024 字节==1k

h->b_dev = 0;
h->b_dirt = 0;
h->b_count = 0;
h->b_lock = 0;
h->b_uptodate = 0;
h->b_wait = NULL;
h->b_next = NULL;
h->b_prev = NULL;
h->b_data = (char *) b;
h->b_prev_free = h-1;
h->b_next_free = h+1;
h++;
NR_BUFFERS++;
if (b == (void *) 0x100000)// 如果地址b 递减到等于1MB,则跳过384KB

b = (void *) 0xA0000;// 让b 指向地址0xA0000(640KB)处

}
h--; // 让h 指向最后一个有效缓冲头

free_list = start_buffer;// 让空闲链表头指向头一个缓冲区头

free_list->b_prev_free = h;// 链表头的b_prev_free 指向前一项(即最后一项)

h->b_next_free = free_list;// h 的下一项指针指向第一项,形成一个环链

for (i=0;i<NR_HASH;i++)// 初始化hash 表(哈希表、散列表),置表中所有的指针为NULL

hash_table[i]=NULL;
}