关于linux缓冲处理过程及一点块设备的基础知识
一: 块设备硬盘的基础知识:
硬盘存储信息的格式是按柱面、磁头号和扇区来存储的,硬磁盘每个存储表面被划分成若干个磁道,每道划分成若干个扇区。
存储容量=磁头数×柱面数×扇区数×每扇区字节数
磁头数: 磁头是硬盘读取数据的关键部件,,在工作状态时,磁头悬浮在盘片上方,而不与盘片直接接触,电源关闭之后,磁头会自动回到在盘片上的起始位置。硬盘的磁头数取决于硬盘中的碟片数,盘片正反两面都存储着数据,所以一个盘片对应两个磁头才能正常工作。比如总容量80GB的硬盘,采用单碟容量80GB的盘片,那只有一张盘片,该盘片正反面都有数据,则对应两个磁头;而同样总容量120GB的硬盘,采用二张盘片,则只有三个磁头,其中一张盘片的一面没有磁头。
扇区: 硬盘内部是金属盘片,将圆形的盘片划分成若干个扇形区域,这就是扇区.
磁道: 以盘片中心为圆心,把盘片分成若干个同心圆,那每一个划分圆的“线条”,就称为磁道。
柱面: N张盘片中相同位置的磁道组成一个“柱面”,磁道 v数==柱面数
二:高速缓冲工作过程及相关基本算法:
缓冲的结构
struct buffer_head {
char * b_data; //每个都指向一个1024字节地址
unsigned long b_blocknr; //挂钩的设备的块号
unsigned short b_dev; // 数据源的设备号
unsigned char b_uptodate; //更新标志表示数据已经更新
unsigned char b_dirt; //干净或者脏页标志0干净1脏
unsigned char b_count; //经常被用到的引用记数设计模式的计数器
unsigned char b_lock; //锁定标志1锁定
struct task_struct * b_wait; //所有访问此设备此块的任务等待队列
struct buffer_head * b_prev;
struct buffer_head * b_next;
struct buffer_head * b_prev_free;
struct buffer_head * b_next_free;
};
A:初始化过程
0.11基本思想我们要将内存划分为1024字节一块的N块缓冲数据区,并且以上面的数据结构来管理这部分缓冲。我们下面看下基本划分内存的过程:
0.11中内存主要划分成内核0~640KB(内核代码不到640KB),显存和BOIS信息(640KB~1MB),高速缓冲区( 1M ~XM),部分虚拟盘,然后是主存储区。(X的值可以参考设计获取 16M 内存则X=4, 8M 内存则X=2…) .
管理通过hash表和空闲联表共同管理。通过设备号和逻辑块号作为主键进行hash,我们所有操作设备的数据都是通过这段缓冲进行管理。
B:处理过程
请求数据:我们先根据读的位置在hash表中映射位置查看对应位置中是否存在已经被请求的缓冲块,比如是否已经有其他的进程请求过此块内容,如果匹配到那么直接返回hash中的节点位置,执行对应操作就可以了。如果没有hash表中存在,那么就要到空闲块中申请一个块作为此设备的缓冲,并链入hash中,并将此块从空闲队列中移除放到空闲的最后。找此块的条件首先需要引用记数为0标志其没有被任何进程占用,然后需要不脏并且未被锁定。B_dirty, b_lock可以不为0(最好都为0,其次不为脏)。然后依次等待解锁,等待同步直到找到一个完全干净的块。当然其间一旦此块又被占用又会去扫描hash然后扫描空闲队列重复开头操作。
其中可以看到有个比较特殊的bread_page,其实就是一次操作4个块,目的为适应内存管理时的页面交换等操作。因为一个页面是4kb等于4个块大小。
另外有点关于linux使用的LRU算法, 普通直观的LRU算法,要在块表中为每一块设置一个计数器。被装入或被替换的块,其对应的计数器清为"0",同组中其它所有块所属的计数器都加"1"。命中的块,其对应的计数器清为"0"。同组中其它所有计数器中,凡是计数器的值小于命中块所属计数器原来值的,都加"1",其它计数器不变。需要替换时,在同组的所有计数器中选择计数值最大(一般为全1)的计数器,它所对应的块就是要被替换的块。
而linux采用的是双向空闲联表和hash辅助实现了这个算法。值得借鉴。其实看了也只是形式变换了,本质也没有变只是多组变成了一组,最新被使用的联表将被链入空闲最后的地方表示最不空闲的。其中的引用记数和简单的hash和链表结合使用功能不错的技术。