文件系统预读【转】

转自:https://www.cnblogs.com/linhaostudy/p/16126723.html

正文

所谓预读,是指文件系统为应用程序一次读出比预期更多的文件内容并缓存在page cache中,这样下一次读请求到来时部分页面直接从page cache读取即可。当然,这个细节对应用程序透明,应用程序可能的感觉唯一就是下次读的速度会更快,当然这是好事。

由于应用程序的访问行为存在多样性加上作者对预读的把握不是非常深入,因此,难免存在不是非常精确的地方,望多赐教。我们会通过设置几个情境来分析预读的逻辑。
回到顶部

情境1

// 事例代码

{
        ...

        f   = open("file", ....);
        ret = read(f, buf, 4096);
        ret = read(f, buf, 2 * 4096);
        ret = read(f, buf, 4 * 4096);
        ...

}

该场景非常简单:打开文件,共进行三次读(且是顺序读),那让我们看看操作系统是如何对文件进行预读的。

Read 1

第一次进入内核读处理流程时,在page cache中查找该offset对应的页面是否缓存,因为首次读,缓存未命中,触发一次同步预读:

static void do_generic_file_read(struct file *filp, loff_t *ppos,read_descriptor_t *desc, read_actor_t actor)
{
    ......
    for (;;) {
        ......
        cond_resched();
find_page:
        // 如果没有找到,启动同步预读
        page = find_get_page(mapping, index);
        if (!page) {
            page_cache_sync_readahead(mapping,ra, filp,index, last_index - index);

该同步预读逻辑最终进入如下预读逻辑:

读逻辑会为该文件初始化一个预读窗口:(ra->start, ra->size, ra->async_size),本例中的预读窗口为(0,4,3),初始化该预读窗口后调用ra_submit提交本次读请求。形成的读窗口如下图所示:

文件系统预读【转】

图中看到,应用程序申请访问PAGE 0,内核一共读出PAGE0 ~PAGE3,后三个属于预读页面,而且PAGE_1被标记为PAGE_READAHEAD,当触发到该页面读时,操作系统会进行一次异步预读,这在后面我们会仔细描述。

等这四个页面被读出时,第一次读的页面已经在pagecache中,应用程序从该page中拷贝出内容即可。

Read 2

接下来应用程序进行第二次读,offset=4096, size=8192。内核将其转化为以page为单位计量,offset=1,size=2。即读上面的PAGE1和PAGE2。

感谢第一次的预读,PAGE1和PAGE2目前已经在内存中了,但由于PAGE1被打上了PAGE_AHEAD标记,读到该页面时会触发一次异步预读:

find_page:
        ......
        page = find_get_page(mapping, index);
        if (!page) {
            page_cache_sync_readahead(mapping,
                    ra, filp,
                    index, last_index - index);
            page = find_get_page(mapping, index);
            if (unlikely(page == NULL))
                goto no_cached_page;
        }
        if (PageReadahead(page)) {
            page_cache_async_readahead(mapping,ra, filp, page,index, last_index - index);
        }
static unsigned long
ondemand_readahead(struct address_space *mapping,
           struct file_ra_state *ra, struct file *filp,
           bool hit_readahead_marker, pgoff_t offset,
           unsigned long req_size)
{
    unsigned long max = max_sane_readahead(ra->ra_pages);

    ........

经历了第一次预读,文件的预读窗口状态为(ra->start,ra->size, ra->async_size)=(0, 4, 3),本次的请求为(offset,size)=(1, 2),上面代码的判断条件成立,因此我们会向前推进预读窗口,此时预读窗口变为(ra->start,ra->size, ra->async_size) = (4, 8, 8)

由于本次是异步预读,应用程序可以不等预读完成即可返回,只要后台慢慢读页面即可。本次预读窗口的起始以及大小以及预读大小可根据前一次的预读窗口计算得到,又由于本次是异步预读,因此,预读大小就是本次读的页面数量,因此将本次预读的第一个页面(PAGE 4)添加预读标记。

文件系统预读【转】

由于上面的两次顺序读,截至目前,该文件在操作系统中的page cache状态如下:

文件系统预读【转】

Read 3

接下来应用程序进行第三次读,顺序读,范围是[page3, page6],上面的预读其实已经将这些页面读入page cache了,但是由于page4被打上了PAGE_READAHEAD标记,因此,访问到该页面时会触发一次异步预读,预读的过程与上面的步骤一致,当前预读窗口为(4,8,8),满足顺序性访问特征,根据特定算法计算本次预读大小,更新预读窗口为(12,16,16),新的预读窗口如下:

文件系统预读【转】

对该情境简单总结下,由于三次的顺序读加上内核的预读行为,文件的page cache中的状态当前如下图所示:

文件系统预读【转】
回到顶部

情景2

这里我们来看另外一种情境:单进程文件顺序读,读大小为256KB,看看预读逻辑如何处理这种情况,照例首先给出事例代码:

{
        ...

        f   = open("file", ....);
        ret = read(f, buf, 40 * 4096);
        ret = read(f, buf, 16 * 4096);
        ret = read(f, buf, 32 * 4096);
        ...

}

事例代码中我们一共进行了三次读,顺序读,且读的大小不定,有超过最大预读量的,也有低于最大预读量的。

Read 1

毫无疑问,由于第一次读肯定未在缓存命中,前一篇博客告诉我们需要进行一次同步预读,需要初始化预读窗口

initial_readahead:
    ra->start = offset;
    ra->size = get_init_ra_size(req_size, max);

在初始化预读窗口中判断得出:ra->size=32 pages,即使应用程序要读的数量是40 pages,这样ra->async_size = ra->size=32 pages,在readit逻辑判断成立,因此会重设ra->async_size的值,根据计算应该是32 pages,而总的ra->size=初始值+ra->async_size=64 pages。形成的预读窗口为(0, 64, 32),如下图:

文件系统预读【转】
由于应用程序本次访问的实际页面是PAGE0 ~PAGE40(由于同步预读会全部在缓存命中),因此在访问过程中会碰到page32,此时触发一次异步预读,并向前推进预读窗口’
/* 如果:
      ** 1. 顺序读(本次读偏移为上次读偏移 (ra->start) + 读大小(ra->size,包含预读量) -
      **    上次预读大小(ra->async_size))
      ** 2. offset == (ra->start + ra->size)???
     */
    if ((offset == (ra->start + ra->size - ra->async_size) ||
         offset == (ra->start + ra->size))) {
          // 设置本次读的
        ra->start += ra->size;
        ra->size = get_next_ra_size(ra, max);
        ra->async_size = ra->size;
        goto readit;
    }

更新后的当前预读窗口为(64, 32, 32),如下:

文件系统预读【转】

因此,经过第一次读以后,该文件在内存中page cache状态如下图所示:

文件系统预读【转】

Read 2

由于第二次读只需读出page40 ~ page55,直接在page cache中命中,也不会触发一次异步预读,预读窗口也不会更新,因此,该过程非常简单。本次读完以后,文件在内存page cache的状态如下:

文件系统预读【转】

Read3

应用程序第三次读的范围为page56 ~ page87,由上图可知,这些均可以在page cache中命中,但是由于访问了PAGE64,因此会触发一次异步预读,且当前的预读窗口为(64, 32, 32),根据上面的算法更新预读窗口为(96, 32, 32),因此,本次预读完成以后,文件在page cache中的缓存状态如下:

文件系统预读【转】
回到顶部

情景三

所谓的交织读指的是多线程(进程)读同一个打开的文件描述符,单个线程的顺序读在操作系统看来可能会变成随机读。同样我们还是结合实例来分析。

事例代码

{
        ......

        f = open("file", ......)
        pthread_create(read_file_1, f, ...)
        pthread_create(read_file_2, f, ...)
        ......

}

read_file_1(f)
{
        lseek(f, 0, SEEK_SET);
        read(f, ..., 2 * 4096);
        read(f, ..., 4 * 4096)
        read(f, ..., 16 * 4096)
}

read_file_2(f)
{
        lseek(f, 128 * 4096, SEEK_SET);
        read(f, ..., 2 * 4096);
        read(f, ..., 4 * 4096)
        read(f, ..., 16 * 4096)
}

事例代码中创建了两个线程同时读文件file,每个线程均是顺序读,让我们看看操作系统的预读是如何处理这种情况的。因为多线程的执行顺序可能是多种多样的,我们只列举一种执行流并解释,线程1 read 1,线程2 read 1,线程2 read 2,线程1 read 2,线程1 read 3,线程2 read3。

线程1 Read 1

线程1读文件的前两个页面,由于尚未缓存命中,因此会触发文件系统的一次同步预读,确定预读窗口为(ra->start, ra->size, ra->async_size) = (0, 4, 2),形成的预读窗口如下

文件系统预读【转】

线程2 Read 1

线程2读文件的128和129两个页面,由于这两个页面也尚未缓存在page cache中,也必须启动一次同步预读,这里会更改上面的预读窗口为(128, 4, 2),更新后的预读窗口如下:

由于本次读和上次读是顺序读,且本次访问的4个页面有两个缓存命中,但由于访问了PAGE 130,而该页面又被打上了异步预读标记,因此在访问页面130的时候会触发一次异步预读,更新预读窗口为(132, 8, 8),如下:

文件系统预读【转】

由于本次会访问4个页面,因此PAGE 132也会被访问,从而又触发一次异步预读,更新预读窗口为(140, 16, 16),最终形成的预读窗口如下:

文件系统预读【转】

线程2两次读 read1 和read 2以后形成的page cache状态如下所示:

文件系统预读【转】

线程1 Read 2

接下来线程1进行第二次读,范围是PAGE 2 ~ PAGE 5,由于线程1 read 1将PAGE 2 和PAGE 3已经预读进page cache,因此可直接命中,但在访问PAGE 2的时候会触发一次异步预读,所以这里会更新预读窗口,但很不幸,预读窗口保存的是线程2的预读状态,因此本次访问和之前的预读窗口并不连续,因此我们必须想办法来恢复线程1的之前的预读状态,会触发下面的执行逻辑:

if (hit_readahead_marker) {
        pgoff_t start;

        rcu_read_lock();

这里恢复线程1的预读窗口方法也比较简单:从本次预读的页面开始向后搜索,找到第一个没有缓存在page cache的页面,本例中是page4,然后以此为本次预读的起始页面号,并可以计算出上次的预读窗口大小(page 4 – page 2 = 2),根据这两个值便可确定本次预读窗口为(4, 8, 8)。

更新后的预读窗口如下图所示:

文件系统预读【转】

在访问页面4时,会再次出发异步预读,更新预读窗口为(8, 8, 8),如下图所示:

文件系统预读【转】

因此,线程1经过read 1 和read 2,形成的page cache状态如下:

文件系统预读【转】

线程1 Read 3

线程1第三次读的页面是PAGE 6 ~ PAGE 13,全部在缓存命中,但在访问PAGE 8的时候会触发一次异步预读,更新预读窗口为(16, 16, 16)。

在线程1经历了三次读以后,page cache的状态如下图所示:

文件系统预读【转】

线程2 Read 3

线程2第三次读页面是PAGE 134 ~ PAGE 141,这些全在缓存中命中,但是访问PAGE 140时会触发一次异步预读。更新预读窗口,但是很不幸,之前的预读窗口是线程1的,因此我们必须搜寻才能恢复线程2的预读窗口,搜寻过程之前已经描述,这里不再啰嗦,恢复出线程2的预读窗口为(156, 32,32)。因此,总的来看,由于线程2的三次读形成的page cache状态如下:

文件系统预读【转】
如果您觉得阅读本文对您有帮助,请点一下"推荐"按钮,您的"推荐"将是我最大的写作动力!

Original: https://www.cnblogs.com/sky-heaven/p/16423350.html
Author: sky-heaven
Title: 文件系统预读【转】

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/547095/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • PDF转换OFD(Java实用版)

    前言: 在项目中用到了,就写一下哈 OFD简介 百度百科:https://baike.baidu.com/item/OFD/56227163?fr=aladdin OFD(Open…

    技术杂谈 2023年6月21日
    0100
  • 我的大一总结

    图片不太清楚这里附上链接密码: w98f https://udld.github.io/ (老师总结)大一内容总结 编程语言初级使用 C,java (python 未完成) Lin…

    技术杂谈 2023年7月23日
    095
  • Vue学习笔记(四):事件处理

    1 v-on ¶ Vue中,绑定点击事件用 v-on:实现,例如绑定鼠标点击事件用 v-on:click=”fun”实现,绑定键盘回车键按下事件用 v-on:keydown.ent…

    技术杂谈 2023年7月24日
    093
  • 59.你要的全拿走

    dsfsd posted @2022-09-28 08:33 随遇而安== 阅读(6 ) 评论() 编辑 Original: https://www.cnblogs.com/55z…

    技术杂谈 2023年6月21日
    098
  • 小熊飞桨练习册-02眼疾识别

    文件说明 文件 说明 train.py 训练程序 test.py 测试程序 test-gtk.py 测试程序 GTK 界面 report.py 报表程序 onekey.sh 一键获…

    技术杂谈 2023年7月23日
    078
  • P2P在NAT和STUN

    转自:https://blog.csdn.net/a1989a132/article/details/17139003 本文主要讨论关于P2P通信的一些常见问题和解决方案。主要内容…

    技术杂谈 2023年6月1日
    095
  • WSL VS Code Server for WSL closed unexpectedly

    直接在管理员PowerShell中WSL –shutdown 本博客是个人工作中记录,遇到问题可以互相探讨,没有遇到的问题可能没有时间去特意研究,勿扰。另外建了几个QQ…

    技术杂谈 2023年6月1日
    0101
  • Wireshark基本介绍和学习TCP三次握手

    原文:http://www.cnblogs.com/tankxiao/archive/2012/10/10/2711777.html 阅读目录 wireshark介绍 wiresh…

    技术杂谈 2023年6月1日
    092
  • 猜数字小游戏

    python猜数字游戏 要求: 输入指定范围,在该范围内进行猜数,可多次猜数,直到猜中 如果猜错,给出下次猜数的范围继续猜 思路: 导入random包,生成随机数 利用while循…

    技术杂谈 2023年7月23日
    089
  • 树形dp(背包)

    树形dp 样题: 没有上司的舞会 某大学有 (n) 个职员,编号为 (1\ldots n)。 他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上…

    技术杂谈 2023年7月11日
    071
  • Gerrit: 更新gerrit项目的地址

    项目所在的远程仓库被迁移到了另一个地址,可以使用下列指令更新本地仓库的地址: 假设项目名称是 HoneyComb为例 替换前: 更新 url: 替换后: Original: htt…

    技术杂谈 2023年6月1日
    092
  • Java编辑器的下载和应用——IDEA

    IDEA下载 (1)搜索 IntelliJ IDEA,选择电脑适合的版本下载(跟着指示一步步安装就好了) (2)安装完成后打开,创建一个空项目(在之后的学习中可把所有的代码放这里,…

    技术杂谈 2023年6月21日
    0108
  • 监控浏览器tab切换或最小化事件

    背景:最近遇到1个项目,业务方调用了后端1个开销较大的接口,用于页面实时监控一些关键指标,页面是自动定时请求接口刷新数据,随着用户的增加,后端压力比较大,分析发现,很多用户日常使用…

    技术杂谈 2023年5月31日
    0104
  • 神经网络那些事儿(一)

    这次主要说说神经网络的一些主要思想,包括介绍两种人工神经元(perceptron neuron和sigmoid neuron)以及神经网络的标准学习算法,随机梯度下降法。神经网络可…

    技术杂谈 2023年5月31日
    079
  • Intel网卡的漫游主动性

    posted @2019-09-22 22:22 樊伟胜 阅读(2129 ) 评论() 编辑 Original: https://www.cnblogs.com/fanweishe…

    技术杂谈 2023年5月30日
    096
  • HIT软构博客8 软件构造中的异常

    java中的异常分为两大类:checked异常和unchecked异常。其中,unchecked又分为error和runtime异常。 Unchecked异常不需要try-catc…

    技术杂谈 2023年7月11日
    070
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球