文件系统-高速缓冲区:

首先我们为什么需要高速缓冲区而不是直接访问块设备中的数据。这是因为,IO设备和内存之间的读写速度不匹配而且有一点数据需要写入或者读出磁盘就访问磁盘,磁盘很快就会损坏,而高速缓冲区就起了一个中间过程的作用,把数据存在高速缓冲区中,需要读取磁盘上的数据时,尝试匹配高速缓冲区中的数据,匹配成功了,那就直接从高速缓冲区中取数据,然后内核再来操作,如果要存入数据,也是先经过缓冲区,再存入磁盘。这样就避免了每次都对IO设备进行操作。

高速缓冲区在整个物理内存中的位置处于内核区和主内存区之间。这里引用《linux0.11代码完全注释》中的图。

在高速缓冲区内部,分成了两个部分,一个是缓冲头结构,另一个是缓冲块。每个缓冲块的大小与块设备上的磁盘逻辑块的大小相同,而缓冲头结构用于连接起缓冲块,以及设置一些属性。结构如图:

那么内核要使用缓冲块时,是怎么和物理设备对应起来的呢?就比如向某个设备写了一些数据,存储在缓冲块中,这个缓冲块怎么才能把数据写入磁盘中。答案是在缓冲头结构中存储了块设备号和缓冲数据的逻辑块号,它们一起唯一确认了缓冲块数据对应的块设备和数据块。并且为了快速的在缓冲区中查看数据块是否在缓冲区中,使用了hash表结构以及空闲缓冲块队列来进行操作和管理,在linux0.11中采用的散列函数是:#define _hash(dev, block) ((unsigned)(dev^block))%NR_HASH。NR_HASH是哈希数组的长度。结构如图:


在图中,双向箭头代表哈希在同一个表项的双向链表指针,对应b_prevb_next字段。虚线表示当前连接在空闲缓冲块链表中空闲缓冲块之间的链表指针,free_list是空闲链表的头指针。
在内核中使用getblk()函数来获取合适的缓冲块。该函数调用get_hash_table()函数,在hash表中确认指定设备号和逻辑块号的缓冲块是否存在,如果存在的话,直接返回对应的缓冲头结构的指针。否则,会从空闲链表头开始对整个空闲链表进行扫描,寻找可以用的空闲缓冲区。有可能可以用的有多个空闲缓冲区,这时就要根据缓冲头结构的修改标志和锁定标志组合而成的权值,来判断哪个空闲块最合适。如果找到的空闲块即没有被修改也没有被锁定,那就使用该空闲块了。如果没有找到空闲块,那就让当前进程进入睡眠,等到继续执行时再次寻找。如果该空闲块被锁定,那么当前进程也需要进入睡眠,等待其它进程解锁。如果在睡眠等待的过程中,该缓冲块又被其它进程占用,就需要重新开始搜索缓冲块了。如果没被其它进程占用,就判断该缓冲块是否已被修改(还未写到盘中),如果已被修改,就将该块写盘,并等待该块解锁。此时如果该缓冲块又被其它进程占用,就又只有重新找空闲的缓冲块了。这里还有一种意外的情况,那就是在当前进程睡眠时,其它进程把我们需要的缓冲块已经加入了hash队列中,所以需要最后搜索一次hash队列,如果在hash队列中找到了该缓冲块,就只好重新执行以上操作了。最后,得到了一块没有被进程引用也没有被上锁以及没有被修改的空闲缓冲块,将该块的引用计数置1,并复位其它的标志,把该缓冲头结构从空闲表中取出。在设置了该缓冲块所属的设备号和相应的逻辑号后,再把该缓冲头结构放入hash表对应表项头部和空闲队列队尾。最后,返回该缓冲块头的指针。流程图如图:

getblk函数返回的可能是一个新的空闲块,也可能是含有我们需要的数据的缓冲块。因此对于读取数据块操作函数bread(),就需要判断该缓冲块的更新标志,已知道所含数据是否有效,如果有效则直接返回给进程,否则就调用底层块读写函数ll_rw_block(),并同时睡眠,等待数据从物理设备写入缓冲块,醒了之后再重新判断是否有效,如果还是不行,那就释放该缓冲块,并返回NULL。流程图如图:

当程序想要释放一个缓冲块时,调用brelse()函数,释放缓冲块并唤醒因为等待该缓冲块而进入睡眠的进程。

最后,除了驱动程序之外,其它上层程序对块设备的读写操作都需要经过高速缓冲区管理程序来实现数据的读写。它们之间的联系主要是通过bread()函数和ll_rw_block()函数实现。如图所示:

init/main.c部分代码

  1. memory_end = (1<<20) + (EXT_MEM_K<<10);
  2. memory_end &= 0xfffff000;
  3. if (memory_end > 16*1024*1024)
  4. memory_end = 16*1024*1024;
  5. if (memory_end > 12*1024*1024)   //内存>12M 设置高速缓冲区大小4M
  6. buffer_memory_end = 4*1024*1024;
  7. else if (memory_end > 6*1024*1024)   // 内存>6M 设置高速缓冲区大小2M
  8. buffer_memory_end = 2*1024*1024;
  9. else
  10. buffer_memory_end = 1*1024*1024;        //否则设置高速缓冲大小1M
  11. main_memory_start = buffer_memory_end;
  12. ifdef RAMDISK
  13. main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
  14. endif

/fs/buffer.c中初始化函数buffer_init()

  1. struct buffer_head *h = start_buffer;
  2. void *b;
  3. int i;
  4. if (buffer_end == 1<<20)      //如果内存末端为1M,则要减掉显存和BIOS所占的内存640K--1M之间
  5. b = (void *) (640*1024);
  6. else
  7. b = (void *) buffer_end;
  8. // 这段代码用于初始化缓冲区,建立空闲缓冲区环链表,并获取系统中缓冲块的数目。
  9. // 操作的过程是从缓冲区高端开始划分1K 大小的缓冲块,与此同时在缓冲区低端建立描述该缓冲块
  10. // 的结构buffer_head,并将这些buffer_head 组成双向链表。
  11. // h 是指向缓冲头结构的指针,而h+1 是指向内存地址连续的下一个缓冲头地址,也可以说是指向h
  12. // 缓冲头的末端外。为了保证有足够长度的内存来存储一个缓冲头结构,需要b 所指向的内存块
  13. // 地址 >= h 缓冲头的末端,也即要>=h+1。
  14. while ((b -= BLOCK_SIZE) >= ((void *) (h + 1)))
  15. {
  16. h->b_dev = 0;      // 使用该缓冲区的设备号。
  17. h->b_dirt = 0;     // 脏标志,也即缓冲区修改标志。
  18. h->b_count = 0;        // 该缓冲区引用计数。
  19. h->b_lock = 0;     // 缓冲区锁定标志。
  20. h->b_uptodate = 0; // 缓冲区更新标志(或称数据有效标志)。
  21. h->b_wait = NULL;      // 指向等待该缓冲区解锁的进程。
  22. h->b_next = NULL;      // 指向具有相同hash 值的下一个缓冲头。
  23. h->b_prev = NULL;      // 指向具有相同hash 值的前一个缓冲头。
  24. h->b_data = (char *) b;    // 指向对应缓冲区数据块(1024 字节)。
  25. h->b_prev_free = h - 1;    // 指向链表中前一项。
  26. h->b_next_free = h + 1;    // 指向链表中下一项。
  27. h++;          // h 指向下一新缓冲头位置。
  28. NR_BUFFERS++;     // 缓冲区块数累加。
  29. if (b == (void *) 0x100000)   // 如果地址b 递减到等于1MB,则跳过384KB,
  30. b = (void *) 0xA0000;   // 让b 指向地址0xA0000(640KB)处。
  31. }
  32. h--;              // 让h 指向最后一个有效缓冲头。
  33. free_list = start_buffer; // 让空闲链表头指向头一个缓冲区头。
  34. free_list->b_prev_free = h;    // 链表头的b_prev_free 指向前一项(即最后一项)。
  35. h->b_next_free = free_list;    // h 的下一项指针指向第一项,形成一个环链。
  36. // 初始化hash 表(哈希表、散列表),置表中所有的指针为NULL。
  37. for (i = 0; i < NR_HASH; i++)
  38. hash_table[i] = NULL;

Linux高速缓冲区原理的更多相关文章

  1. Linux下缓冲区溢出攻击的原理及对策(转载)

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实现 ...

  2. Linux下缓冲区溢出攻击的原理及对策

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈 帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实 ...

  3. [转载]Linux Bond的原理及其不足

    本文转自http://www.yunweipai.com/archives/1969.html 支持原创.尊重原创,分享知识! 在企业及电信Linux服务器环境上,网络配置都会使用Bonding技术做 ...

  4. Linux Bond的原理及其不足

    http://www.tektea.com/archives/1969.html. 在企业及电信Linux服务器环境上,网络配置都会使用Bonding技术做网口硬件层面的冗余,防止单个网口应用的单点故 ...

  5. Linux零拷贝原理

    Linux零拷贝原理 前言 磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝.直接 I/O.异步 I/O 等等,这些优化的目的就是为了提 ...

  6. linux下的X server:linux图形界面原理

    linux下的X server:linux图形界面原理   Moblin Core是在Gnome Mobile的平台上建立.我以前玩Linux,提交的都和图像没有关系,连Xwindows都不用启动,开 ...

  7. Linux Kbuild工作原理分析(以DVSDK生成PowerVR显卡内核模块为例)

    一.引文 前篇博文<Makefile之Linux内核模块的Makefile写法分析>,介绍了Linux编译生成内核驱动模块的Makefile的写法,但最近在DVSDK下使用Linux2.6 ...

  8. [转帖]linux下的X server:linux图形界面原理

    linux下的X server:linux图形界面原理 https://www.cnblogs.com/liangxiaofeng/p/5034912.html linux下的X server:lin ...

  9. Linux 文件删除原理_009

    ***了解Linux文件删除原理先了解一下文件inode索引节点,每个文件在Linux系统里都有唯一的索引节点(身份证号) inode.如果文件存在硬链接,那这个文件和这个文件的硬链接的inode是相 ...

随机推荐

  1. CSS综合(问题)

    1.为什么我外层div设置height:auto有效果,而位于这个div里面的一个div设置height:auto就没效果啦?      将三个DIV的高度都设置为自动,overflow:auto;, ...

  2. 将/home空间从新挂载到/var/lib/docker

    [lxl@node1 ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/centos-root 49G 34G 15G ...

  3. 微信小程序 - 图片懒加载

    wxml <!-- 数据源 --> <view class='item-{{index}}' wx:for="{{lazyData}}" wx:key=" ...

  4. nginx获得response自定义的header

    Response header send by upstream is $upstream_http_x_uuid http://wiki.nginx.org/HttpUpstreamModule#. ...

  5. word2013总是出现未响应卡一下如何解决?

    最近在记笔记,word很烦很烦,总是会卡一下,过一会卡一下.本来以为是自动保存后来发现跟自动保存没有关系. 解决方法:禁用硬件图形加速就好了,不行的话再在硬件加速下面有个"使用子像素定位平滑 ...

  6. 事件响应的优先级、stopProgapation禁止下层组件响应

    cocos2d-js没有完整的鼠标事件处理,这点比js/flash的要差一些,不过凑合着也可以用了. 一般界面编程,可以用显示列表的Node作为监听器的优先级,在上方的会比下方的高优先级. 而coco ...

  7. Arduino和C51开发光敏传感器

    技术:51单片机.Arduino.光敏传感器.PCF8591.AD/DA转换   概述 本文介绍了如何接收传感器的模拟信号和如何使用PCF8591 AD/DA转换模块对光敏传感器的模拟信号进行转换.讲 ...

  8. 【php】(转载)分享一个好用的php违禁词 处理类

    1.直接上代码: <?php //定义编码 header( 'Content-Type:text/html;charset=utf-8 '); $words=array('我','你','他') ...

  9. 【phpstudy】安装Oracle 客户端 并连接

    参考连接:https://blog.csdn.net/liuquan007/article/details/77508518 phpstudy2016是32位版 phpstudy2014是64位版本[ ...

  10. Java中用HttpsURLConnection访问Https链接

    在web应用交互过程中,有很多场景需要保证通信数据的安全:在前面也有好多篇文章介绍了在Web Service调用过程中用WS-Security来保证接口交互过程的安全性,值得注意的是,该种方式基于的传 ...