Linux内核学习笔记之高速缓冲管理(五)

    上节我们已经介绍过高速缓冲区的一些内容,为了解决内存磁盘的数据交互效率问题,操作系统引入了高速缓冲块,其存储介质是内存,大小和磁盘盘块一样为1k。

  • 六脉神剑残卷(一)------高速缓冲区初始化

    首先我们来看下高速缓冲区在内存中的位置

   然后我们来看下在调用buffer_init初始化后这段黄色内存的样子(其中缓冲区低端就是上面的end,缓冲区高端就是上面的4M位置,0.11是2M)


   下面我们就对着这幅图来解读buffer_init函数

void buffer_init(void)
{
	struct buffer_head * h = start_buffer;
	void * b = (void *) BUFFER_END;
	int i;

	while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) {
		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 = (void *) 0xA0000;
	}
	h--;
	free_list = start_buffer;
	free_list->b_prev_free = h;
	h->b_next_free = free_list;
	for (i=0;i<NR_HASH;i++)
		hash_table[i]=NULL;
}

    for循环之前的代码:首先将指向缓冲块头部的h指针指向内核代码尾部end,然后将高速缓冲块指针b指向高速缓冲末端(0.11是2M,这边图里面是4M),当高速缓冲块的起始地址大于缓冲块头部的结束地址(两个块没有交集),说明可以形成一对映射(上图一个粉红色块和一个淡绿色块为一对映射),第一对映射就是上图中最高最长的那条线,高速缓冲块头部数组元素相互之间形成双向链表(上图粉红色的部分),最后free_list指向高速缓冲块头部数组的第一个元素,并使双向链表变成循环双向链表。

    ①为什么要使用头部和数据区分开的结构设计呢?把缓冲区头部和缓冲数据区合并在一起不好吗?我觉得Linus是出于编程方便和执行效率考虑的(我在意淫作者的中心思想~),如果合并到一起,不仅没法使用h+1这种操作了,而每次都要判断下一个缓冲结构头部是不是在640K-1M之间,判断是否相等比判断大小速度是差很多的,而且原本1M-2M之间正好是整数倍个缓冲块数据区,如果和头部合并,那就不是整数倍,不仅前半段会有内存浪费,后半段也有浪费(上图绿色部分),所以这种划分是最节省内存的办法,而且管理信息放在一起,也比较好管理

    ②粉红色块已经是数组了,为什么还要用循环双向链表弄起来?注意这是初始化的情况,当从磁盘读入缓冲块后,就会出现一部分使用,一部分未使用状态的缓冲块,为了能快速索引到能使用的空闲块,所以需要对这些块进行排序,将占用态的放到队列末尾,但是数组实现这个操作效率太慢,而使用循环双向链表的话就简单多了。为什么不像i节点一样用位图?我觉得是由于能映射多少对高速缓冲结构一开始并不知道,无法预先设置好位图大小,所以这边没法用位图来指明空闲块情况,不过个人觉得还是可以设定一个大点的位图,然后在初始化的时候计算映射对的个数并保存起来。

    for循环实现的是高速缓冲块头部指针数组hash_table的初始化,它保存的是占用态的高速缓冲块头部,它的作用需要结合高速缓冲块的使用来理解,请看下文~

  • 六脉神剑残卷(二)------如何获取一个空闲的高速缓冲块

    ①我们先看下buffer_init完成之后内存中的数据结构(上面的就是hash_table,它是指向缓冲块头部的指针数组,目前都是悬空的;下面就是缓冲块头部数组/双向链表,其中上面的双向指针是配合hash_table使用的,用于处理hash值相同的情况,下面的就是用于形成自身循环双向链表结构的指针,高速缓冲块头部结构我们已经在初始化 的while循环里面看过了,紫色的表示当前free_list指向的头部结构)


    这边我简要说下获取高速缓冲块的getblk函数流程,就是找到一个分红色结点,然后将其从hash_table的双向链表和头部数组的循环双向链表中取下来,然后在完成设置后,将已变为蓝色占用态的头部放回两个链表的过程,详细流程图见下面的残卷三,这边将图解其中最重要的两个步骤remove_from_queues和insert_into_queues

    ②为设备号为dev盘块号为block的磁盘盘块获取一个高速缓冲块过程,这里假设hash(dev,block)==0

    这是执行remove_from_queues后高速缓冲结构的样子,注意紫色已经移到下一个头部(free_list),数组的第一个元素已经变成了占用态

    这是执行insert_into_queues后高速缓冲结构的样子,我们会发现头部数组的第一个元素已经被放回循环双向队列中了,不过现在它不再是链首而是链尾了;我们发现hash_table[0]的指针已经指向了占用态头部(不过我觉得这部分源码好像有问题,最后不应该要先判断下next指针指向不为空才能使用其指向的结构的prev指针吗?)

    ②为设备号为dev2盘块号为block2的磁盘盘块获取一个高速缓冲块过程,这里假设hash(dev2,block2)==0,这边主要是为了演示高速缓冲块头部的上面的双向链表指针的作用,所以故意设置本次的hash值还是0


   这是执行remove_from_queues后高速缓冲结构的样子,和上面一样,就不多做解释了


   这是执行insert_into_queues后高速缓冲结构的样子,有木有看到头部结构上面的双向指针的变化,他们自己形成了一个双向链表,而hash_table[0]则指向链首了,之所以这边要用双链而不是单链,主要是高速缓冲块释放的顺序不同,所以要能支持任意位置的节点的删除操作,而双向链表最擅长处理删除操作了。

    对比起复杂的空闲缓冲块申请操作,缓冲块释放操作就简单多了,直接减少缓冲块的引用计数就可以了。那这些链表结构谁来清理?很简单,下次申请到此高速缓冲块的进程负责清理工作,而且如果缓冲块是脏的,还得负责写回磁盘,现在知道我们申请的时候为什么要先卸载下来节点,然后再放回链表里面吧?所以只有第一次用的才是爽的,后面的进程想用都要先帮前面的进程擦干净屁股~

  • 六脉神剑残卷(三)------读取一个磁盘盘块bread

    这部分C代码用流程图解释起来比较清楚(个人觉得难理解的地方也加了评注),流程图下载传送门

    最后我们再看下内存磁盘数据交互的函数之间的层次关系为:
    <1>read(),write()
    <2>block_read(),block_write(),file_read(),file_write(),read_pipe(),write()
    <3>bread()或 breada()
    <4>getblk()
    <5>ll_rw_block()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值