1:基本信息
代码:linux-0.11
2:高速缓冲区
高速缓冲区的管理要素
- 映射关系(内存和磁盘之间的映射关系)
- 应用程序与高速缓冲区的交互API
- 磁盘的交互API
- 高速缓冲区的管理机制(循环链表 + 哈希表 + 单链表)
高速缓冲区位置
位于内核模块和主内存之间
高速缓冲区的工作流程(buff.c)
- 高速缓冲区存储着对应的块设备数据
- 当从块设备中读取数据,OS首次从高速缓冲区进行检索,如果每有就从块设备中读取,如果有且是最新的,则直接和高速缓冲区交互
3 缓冲区的分布方式
高速缓冲区有缓冲低区和高区
低区和高区的缓冲块一一对应
4. 对应缓冲头结构 struct buffer_head
struct buffer_head {
char * b_data; /* pointer to data block (1024 bytes) */
unsigned long b_blocknr; /* 块号 */
unsigned short b_dev; /* 数据源的设备号(0 = free)*/
unsigned char b_uptodate; //更新的标志位
unsigned char b_dirt; /*修改标志: 0-未修改(clean),1-已修改(dirty) */
unsigned char b_count; /* 使用该块的用户 */
unsigned char b_lock; /* 缓冲区是否被锁定 0 - ok, 1 -locked */
struct task_struct * b_wait; //指向等待该缓冲区释放的进程。
struct buffer_head * b_prev; //hash队列上前一块
struct buffer_head * b_next; //hash队列上下一块
struct buffer_head * b_prev_free; //空闲表的前一块
struct buffer_head * b_next_free; //空闲表的下一块
};
4.1 空闲链表
其中空闲链表的指向结构入下图:
4.2 Hash表
有效缓冲区的Hash表的结构指向如下:
其中 散列项 = 设备号 异或 逻辑块号
注意,从空闲链表中取出的缓冲头,除了要放到hash表中,也要再放到空闲链表的末端,这是为了最近最少使用算法LRU(最近使用的都插入到链表最后,当空闲链表中的都在使用的时候,那么可以使用链表前面的缓冲头,这个缓冲头就是最近没被使用过的)
从空闲到有效的过程
首先分配一个buff_head
设置一个buff_head 指向空闲缓冲区的一个有效的buff_head
更新buff_head里所有的设置项
把该buff_head从空闲链表中出链
算出buff_head对应的散列号,并加入到对应的散列项后链表的未端
并把该buff_head放到空闲链表的末端
5 获取一块空闲缓冲块
5.1 getblk
/*
* 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))
return bh;//如果找到bh,就直接返回
tmp = free_list; //free_list是空闲缓冲区链表的头部
do {
if (tmp->b_count) //tmp->b_count非0则表示被进程占用了,就不能占用,就跳出本次循环继续寻找
continue;
/*寻找权重最小的,如果是0的话,就直接可以用,不需要再找了*/
if (!bh || BADNESS(tmp)<BADNESS(bh)) { //(bh非空 || tmp的权重 小于 bh的权重) )
bh = tmp; //bh存放权重最小的
if (!BADNESS(tmp))//tmp的权重为0,说明最小,就break出来不再寻找
break;
}
/* and repeat until we find something good */
} while ((tmp = tmp->b_next_free) != free_list);
if (!bh) { //没有空闲缓冲区话
sleep_on(&buffer_wait);//就进行睡眠等待
goto repeat;//唤醒后再次回到repeat的位置,再次寻找
}
wait_on_buffer(bh); //等待(若已经被上锁)
if (bh->b_count) //再等待期间结束后,是否又被占用
goto repeat; //被占用就在repeat重新寻找
while (bh->b_dirt) {//该块已经被修改?(有残余数据)则进行回写同步
sync_dev(bh->b_dev); //回写同步,缓存中的数据写入磁盘中
wait_on_buffer(bh);//等待(若已经被上锁)
if (bh->b_count)//块又被别人占用
goto repeat;//被占用了就在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表中
goto repeat; //回到repeat,在开头的get_hash_table函数中会把hash对应位置的指针返回
/* 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;//设置应用计数为1
bh->b_dirt=0;//复位修改标志
bh->b_uptodate=0;//复位更新标志
/*
* 从hash队列和空闲链表中移出该缓存区头,让缓冲区指定设备和其上的指定块,
* 然后根据此新设备号和块号重新插入空闲的链表和hash队列的新位置,并最终返回缓冲头指针
*/
remove_from_queues(bh);//从hash表和空闲链表移出该缓存区头
bh->b_dev=dev;//设置新的设备号
bh->b_blocknr=block;//设置新的块号
insert_into_queues(bh);//重新插入空闲的链表和hash队列的新位置
return bh;//返回缓冲头指针
}
5.1.1 get_hash_table
找到hash表中找到对应的缓冲头结构体,并进行处理
struct buffer_head * get_hash_table(int dev, int block)
{
struct buffer_head * bh;
for (;;) {
if (!(bh=find_buffer(dev,block)))
return NULL;
bh->b_count++; //把找到的bh的count加1
wait_on_buffer(bh);//等待bh被其他占用的线程释放
if (bh->b_dev == dev && bh->b_blocknr == block)//验证dev 和 block 是否和bh中的一致,再次验证为了防止在等待其他线程期间,别的进程把这些值改掉
return bh;//一致,把bh返回
bh->b_count--;//验证不一致,把加的1再减掉
}
}
//通过宏hash找到在hash表中的位置
#define NR_HASH 307
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH) //dev异或block然后用NR_HASH进行取余
#define hash(dev,block) hash_table[_hashfn(dev,block)]
static struct buffer_head * find_buffer(int dev, int block)
{
struct buffer_head * tmp;
for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next)//遍历hash表散列项后的链表
if (tmp->b_dev==dev && tmp->b_blocknr==block) //找到dev 和 block都满足的buffer_head
return tmp;//返回这个结构体指针
return NULL;//没找到就返回NULL
}
5.1.2 sync_dev
如果这个缓冲块是被使用的,要通过最少使用算法被替换的,要把里面的数据进行写回到磁盘中,这个缓冲区才能被使用。
int sync_dev(int dev)
{
int i;
struct buffer_head * bh;
bh = start_buffer;
for (i=0 ; i<NR_BUFFERS ; i++,bh++) { //遍历buff找到这个buff_head
if (bh->b_dev != dev)
continue;
wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_dirt) //这个块的与dev相等 && 这个块是脏的(有残余数据)
ll_rw_block(WRITE,bh); //底层的读写块函数
}
sync_inodes();
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;
}
5.1.3 find_buffer
查看满足dev和block的缓冲头是否在hash表中
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]
static struct buffer_head * find_buffer(int dev, int block)
{
struct buffer_head * tmp;
for (tmp = hash(dev,block) ; tmp != NULL ; tmp = tmp->b_next) //遍历hash表
if (tmp->b_dev==dev && tmp->b_blocknr==block)//buff_head存在
return tmp;//返回已存在的buff_head的指针
return NULL;//没有存在就返回NULL
}
5.1.4 remove_from_queues
从hash表中和空闲链表中移除。这是因为有可能所有的buff都在使用。是利用LRU最少使用算法,从hash表中找的的一块已经被使用,而近期没有被使用的buffer。所以需要从hash表中取出。而就算再hash表中存在的缓冲头,在空闲链表中也存在,所以需要先取出后续用于放到空闲链表的末尾,来满足LRU。
#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]
static inline void remove_from_queues(struct buffer_head * bh)
{
/* 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(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;
}
5.1.5 执行流程
上述代码执行流程如下:
6 从设备上读取数据
该函数根据指定设备号dev和数据块号block,首先再高速缓冲区中申请一块缓冲区块
如果该缓冲区中已经包含有效的数据就直接返回该缓冲区块指针
否则就从设备中读取指定块到该缓冲区中,并返回缓冲块指针
6.1 bread()
/*
* 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)
{
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);//数据不可用,调用这个函数,产生读设备请求
wait_on_buffer(bh);//等待指定数据写入,等待缓冲区解锁,进入睡眠
if (bh->b_uptodate)//睡眠醒后,检查缓冲区是否已更新
return bh;//更新就返回
brelse(bh); //没有更新,说明操作设备失败,释放缓冲区
return NULL; //返回空
}
释放指定的缓冲块
void brelse(struct buffer_head * buf)
{
if (!buf)
return;
wait_on_buffer(buf);//等待该缓冲区解锁
if (!(buf->b_count--))//引用计数见一
panic("Trying to free free buffer");
wake_up(&buffer_wait);//唤醒等待空闲缓冲区的进程
}
6.2 bread()执行流程图
6.3 内核程序块设备访问操作
7 初始化函数
初始化缓冲区,建立空闲循环链表,建立hash表
mian
->buffer_init(buff_memory_end)
extern int end;
struct buffer_head * start_buffer = (struct buffer_head *) &end; //位于内存中内核之后的位置,参考第4节的内存分配图
struct buffer_head * hash_table[NR_HASH];
#define NR_HASH 307
void buffer_init(long buffer_end)
{
struct buffer_head * h = start_buffer;//高速缓冲器的开始
void * b;
int i;
if (buffer_end == 1<<20) //高速缓冲区大小的限制,由内核main函数中的buff_memory_end来决定
b = (void *) (640*1024);//当前缓冲大小为1M, 0-640K作为缓冲区, 640k-1M的位置由显示内存(0.11没有单独的显存,只能放这里)和BIOS来占用(放taglist)
else
b = (void *) buffer_end;
//从缓冲末尾开始向头部,每隔BLOCK_SIZE分配一块,直到分配到距离放缓冲头位置h缓冲头之后(h从缓冲区头部向尾部增加
//h是结构体buff_head, 并将这些buff_head组成双向循环链表
while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {
h->b_dev = 0; //使用该缓冲块的设备号清0
h->b_dirt = 0;//脏标志清0
h->b_count = 0;//引用计数清0
h->b_lock = 0;//锁定标志清0
h->b_uptodate = 0;//缓冲块更新标志清0
h->b_wait = NULL;//指向等待该缓冲区解锁的进程为NULL
h->b_next = NULL;//hash下一个缓冲头
h->b_prev = NULL;//hash上一个缓冲头
h->b_data = (char *) b;//对英的缓冲块的位置地址
h->b_prev_free = h-1;//链表前一项
h->b_next_free = h+1;//链表后一项
h++;//h指向下一个新缓冲头位置
NR_BUFFERS++;//缓冲区块数累加
if (b == (void *) 0x100000)//如果b递减到1MB,则跳过384KB (640K - 1024K用于显存和BIOS)
b = (void *) 0xA0000; //让b指向地址0xA0000(640KB),再继续往前面分配
}
h--;//在循环中一直h++,则最后一次的h是无效位置,则需要减一, 让h指向最后一个有效的缓冲头位置
free_list = start_buffer; //空闲链表头free_list,指向高速缓冲区的起始
free_list->b_prev_free = h; //free_list 前一项指向整体链表的最后一项
h->b_next_free = free_list;//最后一项h指向第一项,形成了一个双向环链
for (i=0;i<NR_HASH;i++) //初始化hash表,数组内的指针全部指向NULL
hash_table[i]=NULL;
}
8 其他函数
//从form地址赋值一块到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")