4.2 linux文件系统-高速缓冲区

1:基本信息

代码:linux-0.11

2:高速缓冲区

高速缓冲区的管理要素

  1. 映射关系(内存和磁盘之间的映射关系)
  2. 应用程序与高速缓冲区的交互API
  3. 磁盘的交互API
  4. 高速缓冲区的管理机制(循环链表 + 哈希表 + 单链表)

高速缓冲区位置
位于内核模块和主内存之间
在这里插入图片描述

高速缓冲区的工作流程(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")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值