操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之缓冲区的管理

前言

        当一个程序需要读取硬盘上的一个逻辑块时,就会向缓冲区管理程序提出申请。而请求读写的程序进程则进入睡眠等待状态。缓冲区管理程序首先在缓冲区中寻找以前是否已经读取过这块数据。如果缓冲区中已经有了,就直接将对应的缓冲区块头指针返回给程序并唤醒等待的进程。若缓冲区中还不存在所要求的数据块,则缓冲管理程序就会调用低级块读写函数 ll_rw_block(),向相应的块设备驱动程序发出一个读数据块的操作请求。该函数会为此创建一个请求结构项,并插入请求队列中。为了提高读写磁盘的效率,减小磁头移动的距离,内核代码在把请求项插入请求队列时,会使用电梯算法将请求项插入到磁头移动距离最小的请求队列位置处。
        若此时对应块设备的请求项队列为空,则表明此刻该块设备不忙。于是内核就会立刻向该块设备的控制器发出读数据命令。当块设备的控制器将数据读入到指定的缓冲块后,就会发出中断请求信号,并调用相应的读命令后处理函数,处理继续读扇区操作或者结束本次请求项的过程。例如对相应块设备进行关闭操作和设置该缓冲块数据已经更新标志,最后唤醒等待该块数据的进程。

(总而言之,缓冲区是程序操作块设备的数据的载体) 

缓冲区也属于内存的一部分,并且作为后续与块设备打交道的重要结构(主内存块设备的桥梁),而且像缓冲的概念在其他地方也是有使用的,如在最近访问的页目录和页表会被存放在处理器的页高速缓冲器件中。

        于buffer.c程序中用于对高速缓冲区(池)进行操作和管理。高速缓冲区位于内核代码块和主内存区之间。高速缓冲区在块设备与内核其他程序之间起着一个桥梁作用。除了块设备驱动程序以外,内核程序如果需要访问块设备中的数据,就都需要经过高速缓冲区来间接地操作。

如之前的文章所总结,任何程序的运行,都是在对所设计的数据结构的灵活运用,接下来简单地介绍下缓冲区管理的两种关键数据结构。 

两组重要结构

作为需要传入参数的初始化方法 缓冲区初始化 buffer_init ,其中的 buffer_memory_end 是内存划分时的边界设置

void main(void) {
    ...
    buffer_init(buffer_memory_end);
    ...
}

假设内存只有 8M,核心的代码如下:

extern int end;
struct buffer_head * start_buffer = (struct buffer_head *) &end;

void buffer_init(long buffer_end) {
    struct buffer_head * h = start_buffer;
    void * b = (void *) buffer_end;
    while ( (b -= 1024) >= ((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++;
    }
    h--;
    free_list = start_buffer;
    free_list->b_prev_free = h;
    h->b_next_free = free_list;
    for (int i=0;i<307;i++)
        hash_table[i]=NULL;
}

其中外部变量 end,而缓冲区开始位置 start_buffer 就等于这个变量的内存地址。
这个外部变量 end 并不是操作系统代码写就的,而是由链接器 ld 在链接整个程序时设置的一个外部变量,帮助计算好了整个内核代码的末尾地址。内存分布图可以精确为如下:

主内存缓冲区的分界线,就直接代码里写死了,就是上图中的 2M。可是内核程序占多大内存在写的时候完全不知道,就算知道了如果改动一点代码也会变化,所以就由程序编译链接时由链接器程序帮我们把这个内核代码末端的地址计算出来,作为一个外部变量 end 拿来即用。

两个重要结构变量,一个是 buffer_head 结构的 h,代表缓冲头,其指针值是 start_buffer,就是图中的内核代码末端地址 end,也就是缓冲区开头。 一个是 b,代表缓冲块,指针值是 buffer_end,也就是图中的 2M,就是缓冲区结尾。 缓冲区结尾的 b 每次循环 -1024,也就是一页的值,缓冲区结尾的 h 每次循环 +1(一个 buffer_head 大小的内存),直到碰一块为止。

        所有缓冲块的 buffer_head 被链接成一个双向链表结构,称为空闲链表。图中 free_list 指针是该链表的头指针,指向空闲块链表中第一个“最为空闲的”缓冲块,即近期最少使用的缓冲块。而该缓冲块的反向指针b_prev_free 则指向缓冲块链表中最后一个缓冲块,即最近刚使用的缓冲块。缓冲块的缓冲头数据结构如下

(一个从上往下,一个从下往上。)

这个过程中,h 被附上了属性值,其中比较关键的是这个 buffer 所表示的数据部分 b_data,也就是指向了上面的缓冲块 b。这个 buffer 的前后空闲 buffer 的指针 b_prev_freeb_next_free。当缓冲头 h 的所有 next 和 prev 指针都指向彼此时,就构成了一个双向链表

free_list = start_buffer;
free_list->b_prev_free = h;
h->b_next_free = free_list;

free_list 指向了缓冲头双向链表的第一个结构,然后就可以顺着这个结构,从双向链表中遍历到任何一个缓冲头结构了,而通过缓冲头又可以找到这个缓冲头对应的缓冲块。 (缓冲头就是具体缓冲块的管理结构,而 free_list 开头的双向链表又是缓冲头的管理结构,整个管理体系就这样建立起来了。)从 free_list 开始遍历,就可以找到这里的所有内容了。但是,还有一步,能帮助更高效地管理。

for (i=0;i<307;i++)
    hash_table[i]=NULL;

总而言之,一个 307 大小的 hash_table 数组,这个代码在 buffer.c 中,而 buffer.c 是在 fs 包下的,也即文件系统包下的。并且它后续是为文件系统而服务,具体是内核程序如果需要访问块设备中的数据,就都需要经过缓冲区来间接地操作。 (也即,读取块设备的数据(硬盘中的数据),需要先读到缓冲区中,如果缓冲区已有了,就不用从块设备读取了,直接取走。)

        在双向链表从头遍历即可知道缓冲区是否已经有了要读取的块设备中的数据,但是效率太低。所以需要一个 hashmap 的结构方便快速查找(结合链表和数组的优点形成的数据结构),即 hash_table 这个数组的作用。 现在只是初始化这个 hash_table。之后当要读取某个块设备上的数据时,首先要搜索相应的缓冲块。

如下函数:

#define _hashfn(dev,block) (((unsigned)(dev^block))%307)
#define hash(dev,block) hash_table[_hashfn(dev,block)]

// 搜索合适的缓冲块 
struct buffer_head * getblk(int dev,int block) {
    ...
    struct buffer_head bh = get_hash_table(dev,block);
    ...
}

struct buffer_head * get_hash_table(int dev, int block) {
    ...    
    find_buffer(dev,block);
    ...
}

static struct buffer_head * find_buffer(int dev, int block) { 
    ...     
    hash(dev,block);
    ...
}

(即通过   (设备号^逻辑块号) % 307  找到在 hash_table 里的索引下标)

与C++中的 哈希表(散列表) 类似,如果哈希冲突就形成链表。

 其中的缓冲块搜索函数 getblk(),

        获取适合的空闲缓冲块。该函数会首先调用get_hash_table()函数,在 hash 表队列中搜索指定设备号和逻辑块号的缓冲块是否已经存在。如果指定的缓冲块存在就立刻返回对应缓冲头结构的指针;如果不存在,则从空闲链表头开始,对空闲链表进行扫描,寻找一个空闲缓冲块。在寻找过程中还要对找到的空闲缓冲块作比较,根据赋予修改标志和锁定标志组合而成的权值,比较哪个空闲块最适合。若找到的空闲块既没有被修改也没有被锁定,就不用继续寻找了。

        若此时没有找到空闲块,则让当前进程进入睡眠状态,待继续执行时再次寻找。若该空闲块被锁定,则进程也需进入睡眠,等待驱动程序对其解锁。若在睡眠等待的过程中,该缓冲块又被其他进程占用,那么只要再重头开始搜索缓冲块。否则判断该缓冲块是否已被修改过,若是,则将该块写盘,并等待该块解锁。此时如果该缓冲块又被别的进程占用,那么又一次全功尽弃,只好再重头开始执行 getblk()

        此时有可能出现另外一个意外情况,也就是在我们睡眠时,可能其他进程已经将我们所需要的缓冲块加进了 hash 队列中,因此这里需要最后一次搜索一下 hash 队列。如果真的在 hash 队列中找到了我们所需要的缓冲块,那么我们又得对找到的缓冲块进行以上判断处理,因此,又一次地我们需要重头开始执行 getblk() 

        最后,才算找到了一块没有被进程使用、没有被上锁,而且是干净(修改标志未置位)的空闲缓冲块。于是我们就将该块的引用次数置 1,并复位其他几个标志,然后从空闲表中移出该块的缓冲头结构。在设置了该缓冲块所属的设备号和相应的逻辑号后,再将其插入 hash 表对应表项首部并链接到空闲队列的末尾处。由于搜索空闲块是从空闲队列头开始的,因此这种先从空闲队列中移出并使用最近不常用的缓冲块,然后再重新插入到空闲队列尾部的操作也就实现了最近最少使用LRU 算法。最终,返回该缓冲块头的指针。

哈希表 + 双向链表,这可以实现著名的 LRU 算法,之后的缓冲区使用和弃用,正是这个算法发挥了作用。之后通过文件系统来读取硬盘文件时,都需要使用和弃用这个缓冲区里的内容,缓冲区即是用户进程的内存和硬盘之间的桥梁,也可以说是page_cache的雏形。

        搜索函数在每次获取新的空闲缓冲块时,就会把它移到 free_list 头指针所指链表的最后面,即越靠近链表末端的缓冲块被使用的时间就越近。因此如果 hash 表中没有找到对应缓冲块,就会在搜索新空闲缓冲块时从 free_list 链表头处开始搜索。内核取得缓冲块的算法使用了以下策略:

        如果指定的缓冲块存在于 hash 表中,则说明已经得到可用缓冲块,于是直接返回:否则就需要在链表中从 free_list 头指针处开始搜索,即从最近最少使用的缓冲块处开始。

        因此最理想的情况是找到一个完全空闲的缓冲块,即b_dirt 和b_lock 标志均为0的缓冲块;但是如果不能满足这两个条件,那么就需要根据b_dirt和b_lock标志计算出一个值。因为设备操作通常很耗时,所以在计算时需加大b_dirt 的权重。然后我们在计算结果值最小的缓冲块上等待(如果缓冲块已经上锁)。最后当标志b_lock 为0时,表示所等待的缓冲块原内容已经写到块设备上。因此getblk() 就获得了一块空闲缓冲块。


        对于更新和同步(Synchronization)操作,其主要作用是让内存中的一些缓冲块内容与磁盘等块设备上的信息一致。sync_inodes()的主要作用是把i节点表 inode_table 中的i节点信息与磁盘上的一致起来。但需要经过系统高速缓冲区这一中间环节。实际上,任何同步操作都被分成了两个阶段

1.数据结构信息与高速缓冲区中的缓冲块同步问题,由驱动程序独立负责;

2.高速缓冲区中数据块与磁盘对应块的同步问题,由这里的缓冲管理程序负责;

        sync_inodes() 函数不会直接与磁盘打交道,它只能前进到缓冲区这一步,即只负责与缓冲区中的信息同步。剩下的操作需要缓冲管理程序负责。为了让 sync_inodes()知道哪些i节点与磁盘上的不同,就必须首先让缓冲区中内容与磁盘上的内容一致。这样 sync_indes()通过与当前磁盘在缓冲区中的最新数据比较才能知道哪些磁盘 inode 需要修改和更新。最后再进行高速缓冲区与磁盘设备的第二次同步操作,做到内存中的数据与块设备中的数据真正的同步。

        高速缓冲区在提高对块设备的访问效率和增加数据共享方面起着重要的作用。除驱动程序以外,内核其他上层程序对块设备的读写操作都需要经过高速缓冲区管理程序来间接地实现。它们之间的主要联系是通过高速缓冲区管理程序中的 bread()函数和块设备低层接口函数ll_rw_block()来实现。上层程序若要访问块设备数据就通过 bread()向缓冲区管理程序申请。如果所需的数据已经在高速缓冲区中,管理程序就会将数据直接返回给程序。如果所需的数据暂时还不在缓冲区中,则管理程序会通过 ll_rw block() 向块设备驱动程序申请,同时让程序对应的进程睡眠等待。等到块设备驱动程序把指定的数据放入高速缓冲区后,管理程序才会返回给上层程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/960994.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器上安装Nginx详细步骤

第一步&#xff1a;上传nginx压缩包到指定目录。 第二步&#xff1a;解压nginx压缩包。 第三步&#xff1a;配置编译nginx 配置编译方法&#xff1a; ./configure 配置编译后结果信息&#xff1a; 第四步&#xff1a;编译nginx 在nginx源文件目录中直接运行make命令 第五步&…

【算法】经典博弈论问题——威佐夫博弈 python

目录 威佐夫博弈(Wythoff Game)【模板】 威佐夫博弈(Wythoff Game) 有两堆石子&#xff0c;数量任意&#xff0c;可以不同&#xff0c;游戏开始由两个人轮流取石子 游戏规定&#xff0c;每次有两种不同的取法 1)在任意的一堆中取走任意多的石子 2)可以在两堆中同时取走相同数量…

linux挂载新硬盘,查看新硬盘,格式化分区,创建挂载点,挂载逻辑卷,整盘方式挂载,LVM方式挂载,查看linux 磁盘卷组的剩余空间,ext4与xfs区别

摘要 挂载新硬盘&#xff0c;本文作者整理了几乎所有相关的知识点 作者采用的是本文第二种挂载方式&#xff08;LVM&#xff09;&#xff0c;只用了下面6条命令搞定 # 说明&#xff1a; # /dev/mapper/appvg-mylv1 逻辑卷完整名称 # # /dev/mapper目录是Linux系统中用…

Golang并发机制及CSP并发模型

Golang 并发机制及 CSP 并发模型 Golang 是一门为并发而生的语言&#xff0c;其并发机制基于 CSP&#xff08;Communicating Sequential Processes&#xff0c;通信顺序过程&#xff09; 模型。CSP 是一种描述并发系统中交互模式的正式语言&#xff0c;强调通过通信来共享内存…

HTML5+SVG+CSS3实现雪中点亮的圣诞树动画效果源码

源码介绍 这是一款基于HTML5SVGCSS3实现雪中点亮的圣诞树动画效果源码。画面中的圣诞树矗立在雪地中&#xff0c;天上飘落着雪花。当鼠标滑过圣诞树时&#xff0c;可见到圣诞树上的灯光闪烁&#xff0c;同时左下角探出雪怪模样的半个脑袋&#xff0c;四处张望着。整体画面栩栩…

基于蓝牙6.0的RSSI和UWB融合定位方法,可行性分析

融合RSSI&#xff08;接收信号强度指示&#xff09;和UWB&#xff08;超宽带&#xff09;两种技术进行蓝牙6.0定位是完全可行的&#xff0c;并且可以带来更高的定位精度和稳定性。本文给出分析和MATLAB仿真结果 文章目录 技术优势RSSIUWB融合的优势 实现方案数据融合算法硬件要…

Openfga 授权模型搭建

1.根据项目去启动 配置一个 openfga 服务器 先创建一个 config.yaml文件 cd /opt/openFGA/conf touch ./config.yaml 怎么配置&#xff1f; 根据官网来看 https://github.com/openfga/openfga/blob/main/.config-schema.json 这里讲述详细的每一个配置每一个类型 这些配…

零刻SER7接口及配置跑分

今天入手了一台迷你机-零刻SER7 &#xff0c;不得不说这机身是真的小啊&#xff0c;相比于传统台式机&#xff0c;它几乎不占空间&#xff0c;可以轻松放置在桌面、电视柜甚至背包中&#xff0c;非常适合需要频繁移动或空间有限的用户。尽管体积小巧&#xff0c;但零刻SER7在性…

ResNeSt: Split-Attention Networks 参考论文

参考文献 [1] Tensorflow Efficientnet. https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet. Accessed: 2020-03-04. 中文翻译&#xff1a;[1] TensorFlow EfficientNet. https://github.com/tensorflow/tpu/tree/master/models/official/efficien…

能够对设备的历史数据进行学习与分析,通过与设备当前状态的比对,识别潜在故障并做出预判的名厨亮灶开源了。

明厨亮灶视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。AI技术可以24小时…

大数据学习之Kafka消息队列、Spark分布式计算框架一

Kafka消息队列 章节一.kafka入门 4.kafka入门_消息队列两种模式 5.kafka入门_架构相关名词 Kafka 入门 _ 架构相关名词 事件 记录了世界或您的业务中 “ 发生了某事 ” 的事实。在文档中 也称为记录或消息。当您向 Kafka 读取或写入数据时&#xff0c;您以事件的 形式执行…

爱书爱考平台说明

最近我开发了一个综合性的考试平台&#xff0c;内容包括但不限于职业资格证考试、成人教育、国家公务员考试等内容。目前1.0版本已经开发完成&#xff0c;其他的功能陆续完善中。 微信小程序搜索"爱书爱考" 微信小程序图标如下图: 目前维护了java相关的面试题的考题…

Docker/K8S

文章目录 项目地址一、Docker1.1 创建一个Node服务image1.2 volume1.3 网络1.4 docker compose 二、K8S2.1 集群组成2.2 Pod1. 如何使用Pod(1) 运行一个pod(2) 运行多个pod 2.3 pod的生命周期2.4 pod中的容器1. 容器的生命周期2. 生命周期的回调3. 容器重启策略4. 自定义容器启…

2023年吉林省职业院校技能大赛网络系统管理样题-网络配置(华三代码)

目录 附录1:拓扑图 附录2:地址规划表 1.S1 2.S3 3.S4 4.S5 5.S7 6.S8 7.S9 8.R1 9.R2 10.R3 11.EG1 12.EG2 13.AC1 14.AC2 附录1:拓扑图 编号 型号

HTML-新浪新闻-实现标题-排版

标题排版 图片标签&#xff1a;<img> src&#xff1a;指定图片的url&#xff08;绝对路径/相对路径&#xff09; width&#xff1a;图片的宽度&#xff08;像素/相对于父元素的百分比&#xff09; heigth&#xff1a;图片的高度&#xff08;像素/相对于父元素的百分比&a…

基于物联网的智能环境监测系统(论文+源码)

1系统的功能及方案设计 本课题为基于物联网的智能环境监测系统的设计与实现&#xff0c;整个系统采用stm32f103单片机作为主控制器&#xff0c;通过DHT11传感器实现智能环境监测系统温度和湿度的检测&#xff0c;通过MQ传感器实现CO2浓度检测&#xff0c;通过光照传感器实现光照…

全面解析文件上传下载删除漏洞:风险与应对

在数字化转型的时代&#xff0c;文件上传、下载与删除功能已经成为各类应用程序的标准配置&#xff0c;从日常办公使用的协同平台&#xff0c;到云端存储服务&#xff0c;再到社交网络应用&#xff0c;这些功能在给用户带来便捷体验、显著提升工作效率的同时&#xff0c;也隐藏…

1.2第1章DC/DC变换器的动态建模-1.2Buck-Boost 变换器的交流模型--电力电子系统建模及控制 (徐德鸿)--读书笔记

1.2 Buck-Boost 变换器的交流模型 Buck- Boost变换器是一种典型的DC/DC变换器&#xff0c;具有升压和降压功能其输出电压的极性与输入电压相反&#xff0c;见图1-4a。当电感L的电流i(t)连续时一个开关周期可以分为两个阶段。在阶段1&#xff0c;开关在位置1时&#xff0c;即&am…

06-AD向导自动创建P封装(以STM32-LQFP48格式为例)

自动向导创建封装 自动向导创建封装STM32-LQFP48Pin封装1.选则4排-LCC或者QUAD格式2.计算焊盘相定位长度3.设置默认引脚位置(芯片逆时针)4.特殊情况下:加额外的标记 其他问题测量距离:Ctrl M测量 && Ctrl C清除如何区分一脚和其他脚?芯片引脚是逆时针看的? 自动向导…

若依基本使用及改造记录

若依框架想必大家都了解得不少&#xff0c;不可否认这是一款及其简便易用的框架。 在某种情况下&#xff08;比如私活&#xff09;使用起来可谓是快得一匹。 在这里小兵结合自身实际使用情况&#xff0c;记录一下我对若依框架的使用和改造情况。 一、源码下载 前往码云进行…