热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux内核源码分析--文件系统(三、buffer.c)

前面已经大概的分析了下高速缓存区相关知识,这里再来分析下几个重要的函数;1、清缓存:把缓存区数据和设备进行同步sys_sync(void)

        前面已经大概的分析了下高速缓存区相关知识,这里再来分析下几个重要的函数;

        1、清缓存:把缓存区数据和设备进行同步

        sys_sync(void)函数:首先把i节点从内存中写入到缓存块中( 把i节点写入缓存块函数:sync_inodes(void),从内存inode_table[]  i节点数组中扫描修改过的i节点,然后根据i节点号把所处的整块逻辑块都读取到缓存块中,接着把内存中修改过的i节点存放到缓存块相应位置);扫描缓存区中所有缓存头结构,如果已经修改过就发出设备写请求,就会把修改过的缓存块写入到设备磁盘块中;

        sync_dev(int dev)函数是对指定的块设备进行数据同步,而上面的是对所有缓存区进行数据同步,因为两个清缓存函数类似,所以就挑一个具有代表性的分析下:

//对指定设备进行高速缓存区数据和设备上数据进行同步
//这里采用先让修改的缓存数据和设备块数据同步,
//然后把i节点写入到高速缓存区中,最后再次让缓存区和设备块数据同步。
//这样做是,第一次同步,可以把脏块同步干净,然后把i节点写入高速缓存区中,
//这时候同步i节点就变的简单了。最后同步i节点和因为i节点写入而变脏的块
int sync_dev(int dev)
{
int i;
struct buffer_head * bh;

bh = start_buffer;//得到缓存区开始地址
for (i=0 ; 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();//把内存中改变过的i节点写入到缓存区中
bh = start_buffer;//步骤和上面一样
for (i=0 ; i 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;
}


        2、缓存块插入到双链表/hash链表中,从双链表/hash链表中删除缓存块

        static inline void remove_from_queues(struct buffer_head * bh);从链表中删除缓存块,这没有什么技巧,都是些简单的链表操作;重点看下插入链表,先调整双链表,这里把插入的缓存块放到链表最后,所以会有:越靠近free_list指针的缓存头就越久没有使用(到时候查找空闲缓存头时就更有效率);然后再调整下hash链表,这个hash链表不是一一映射的,而是hash链表(或者说hash数组表),可以查看下:http://blog.csdn.net/yuzhihui_no1/article/details/43677663

//将指定缓冲区插入空闲链表尾并放入hash队列中
static inline void insert_into_queues(struct buffer_head * bh)
{
/* put at end of free list; 放到空闲链表尾部,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 */
//如果他是个块设备,那么插入新hash队列中
bh->b_prev = NULL;
bh->b_next = NULL;
if (!bh->b_dev)//判断是否是块设备
return;
//这个hash队列的插入是从头开始插入的;如果要从尾部插入,则要循环判断到位null
bh->b_next = hash(bh->b_dev,bh->b_blocknr);//插入到得到的hash值队列的第一块,也就是首地址
hash(bh->b_dev,bh->b_blocknr) = bh;//hash组指针要指向对应hash值队列
bh->b_next->b_prev = bh;//处理bh第一个hash项的前指针
}

        3、查找缓存块号

        查找缓存块号基本是使用hash方法来得到的,因为hash方法就像查字典一样,经过几个步骤后就能很快的查找到;而双链表必须要一个个的去匹配,就像从字典的第一页开始一样一页一页的查看,效率明显低很多;

        注:find_buffer(int dev, int block)和get_hash_table(int dev, int block)这两个函数都是在查找已经存在的缓存块(根据设备号和逻辑块号可以查找到),而getblk(int dev, int block)函数是先查找是否已经存放,如果不存在就会从双链表中获取个空闲缓存块,然后进行设置,并且放入到hash链表中;

        find_buffer(int dev, int block)函数,这个函数主要是让设备号dev和逻辑块号block通过hash()函数来得到hash链表头节点,然后遍历整个链表来得到具体的某个缓存块。注意hash()函数得到的是一类缓存块,而不是具体的某个缓存块,也所以是hash链表而不是hash表,这是个基础函数(所谓基础函数就是用来被其他函数包装调用的),只负责查找到具体的缓存块,而不管该缓存块是否被使用,或者上锁。

        get_hash_table(int dev, int block)函数,是包装了上面的find_buffer()函数得到的。先通过find_buff()函数得到缓存头,然后对缓存块引用,再等待该缓存块解锁,再次判断是否是想要的缓存块(因为等待解锁期间,可能会有进程对缓存块修改),如果缓存头结构没变,那返回缓存头结构;如果变了,递减引用,重新再查找。函数中用了一个死循环(搞不懂用死循环为什么不用while(1),而是用for(;;),个人感觉用while(1)通俗易懂),把上面一系列操作都放在死循环中,“要么死掉,要么找到” 这函数太执著了。

        下面才是终极查找缓存块号函数(《爱情公寓》中吕子乔的经典台词:终极.....)getblk(int dev, int block),这个函数设计的非常好,多次判断,最后得到一个干净的空闲缓存块。还有个我觉得很好的就是他设计了权重:锁的权重为1,修改标志的权重为2,如果上锁了就等待下所以花的时间少;但如果修改过了,那得和设备数据同步(向块设备上写数据是比较慢的),完了后还得再判断下是否上锁;所以最后再没有完全干净(引用为0,干净,不上锁)的情况下,找到一个综合权重小的也是个不错选择。

 //同时判断缓存区的修改标志和锁定标志,并且定义修改标志权重比锁标志大
#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;//如果在hash链表中幸运的查找到了缓存块,谢天谢地可以不用走下面的漫长道路了

// 如果上面没有查找到,那么就意味着所需要的缓存块压根就没有在hash链表中
// 这里就需要注意了:在初始化时,所有的缓存头结构都用循环双链表串起来了,所以叫做空闲双链表
// 但是hash链表中初始化时全部置为null,当需要的时候才把缓存头加到hash链表中

//因此 下面是在循环双链表中找一个空闲缓存头,加入到hash表中使用
tmp = free_list;//从链表头开始,因为这是一条LRU链表
//空闲块条件 引用计数为0,干净块,没上锁
do {
if (tmp->b_count)//这是硬条件,如果有别的进程使用,则换下一个
continue;
//其实下面是寻找干净块,没上锁
//下面这个刚还是我是没有理解透彻,后来反复看了下才看懂其中奥妙(也许聪明的你一下子就看懂了我所谓的奥妙)
//if()中的!bh是为bh = NULL,做处理的;BADNESS(tmp)//其实不然,if (!BADNESS(tmp))这一步如果能够退出,那再好不过了,表示没有上锁,也没有修改过的空闲块;
//而如果上面的那一步没有退出,这个BADNESS(tmp)
if (!bh || BADNESS(tmp) bh = tmp;
if (!BADNESS(tmp))
break;
}
/* and repeat until we find something good */
} while ((tmp = tmp->b_next_free) != free_list);
if (!bh) {//如果发现所有缓存区都在使用,那么任务将会睡眠在buffer_wait队列上
sleep_on(&buffer_wait);
goto repeat;//有wake_up()唤醒后再查找空闲缓存区
}
//这里已经获取到合适的空闲缓存块,但要检查下是否被上锁
wait_on_buffer(bh);
if (bh->b_count)//如果被其他进程占用,那么又得重新找一块缓存块
goto repeat;
while (bh->b_dirt) {//如果该缓存区有修改数据,
sync_dev(bh->b_dev);//同步到指定的设备上
wait_on_buffer(bh);//等待解锁
if (bh->b_count)
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))
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);//修改完dev和block后再次插入到正确的地方
return bh;
}

        4、从设备上读取数据到缓存中

         bread(int dev,int block)函数是从设备上读取一个逻辑块内容到缓存块上,其操作过程大概就是通过 getblk(dev, block)函数得到一个缓存块,如果这个缓存块数据是有效的就直接返回缓存块。其实通过get_hash_table(dev, block)函数得到的缓存块会和dev设备上的block号逻辑块一一对应的,就算没找到也会从free链表中申请一个和它一一对应的缓存块;如果缓存块上的数据是无效的,那么就要向设备发送读数据请求,把逻辑块上的数据读取到缓存块上,返回缓存块;

        bread_page(unsigned long address, int dev, int b[4])函数是一次性读取四块逻辑块数据到缓存块中,然后再从缓存块中拷贝到指定内存中;

 //同时读取四块缓存块数据到指定内存中,每一块是1024,一个页就是4096,所以就是4块缓存块内容
//参数:address是内存地址;dev设备号;b[4]4个设备数据块号
void bread_page(unsigned long address,int dev,int b[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;//块号无效则不需要管,把对应的缓存块置为null
//下面是将4块缓存区的内容复制到指定内存位置上
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);//复制内容到address上
brelse(bh[i]);//释放掉缓存区
}
}
        struct buffer_head * breada(int dev,int first, ...)函数会预先读取几个数据块到缓存块中,但其实现的原理和上面两个函数类似,唯一不同的就是该函数使用了,可变参数列表技术,巧妙的实现预读取多个数据块数据;
//和bread函数类似,但是该函数会预先读取一些块,可变参数列表  最后一个参数要为负数
//函数参数是个可变参数,是一系列指定的块号,成功返回第一块的缓存块头指针,否则返回null
struct buffer_head * breada(int dev,int first, ...)
{
va_list args;
struct buffer_head * bh, *tmp;

va_start(args,first);
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);//否则释放缓存块,返回null
return (NULL);
}


        5、缓存区初始化函数

        虽然在前面一篇blog中分析了缓存区初始化的代码,但是这里还是得看下他,因为这个函数设计的真的很好。

//缓存区初始化函数
//参数:buffer_end指向缓存区内存的末端;对于系统有16MB内存,缓存为4MB;8MB--2MB
void buffer_init(long buffer_end)
{
struct buffer_head * h = start_buffer;
void * b;
int i;

if (buffer_end == 1<<20)//640k~1M用于显存和BIOS ROM;高端地址应该设置为b=640kb
b = (void *) (640*1024);
else
b = (void *) buffer_end;//否则高地址还是设置buffer_end

//下面代码用来初始化缓存区,建立空闲缓存区环链表,并获取系统中缓存块的数目
//高端划分1k大小的缓存块,低端设置相应的缓存块头部结果指向高端缓存块
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递减到了1M,则跳到640kb处,因为这段地址被BIOS ROM和显存使用
b = (void *) 0xA0000;
}
h--;//指向最后一个有效缓存头结构
free_list = start_buffer;//空闲指针指向第一个头结构
free_list->b_prev_free = h;//做个循环链表
h->b_next_free = free_list;
for (i=0;i hash_table[i]=NULL;
}

        转载请注明作者和原文出处,原文地址: http://blog.csdn.net/yuzhihui_no1/article/details/43703153

        如果有什么不正确之处,欢迎大家指正,一起努力,共同学习!!



推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了在Vue项目中如何结合Element UI解决连续上传多张图片及图片编辑的问题。作者强调了在编码前要明确需求和所需要的结果,并详细描述了自己的代码实现过程。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
author-avatar
和雅竹
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有