https://kernel.0voice.com/forum.php?mod=viewthread&tid=629
 
玩转Linux内核 发布于 2022-8-9 22:19:08 阅读 593只看楼主

我们知道文件一般存放在硬盘(机械硬盘或固态硬盘)中,CPU 并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被 CPU 访问。

由于读写硬盘的速度比读写内存要慢很多(DDR4 内存读写速度是机械硬盘500倍,是固态硬盘的200倍),所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux 内核使用 页缓存(Page Cache) 机制来对文件中的数据进行缓存。

本文使用的 Linux 内核版本为:Linux-2.6.23

什么是页缓存

为了提升对文件的读写效率,Linux 内核会以页大小(4KB)为单位,将文件划分为多数据块。当用户对文件中的某个数据块进行读写操作时,内核首先会申请一个内存页(称为 页缓存)与文件中的数据块进行绑定。如下图所示:

如上图所示,当用户对文件进行读写时,实际上是对文件的 页缓存 进行读写。所以对文件进行读写操作时,会分以下两种情况进行处理:

  • 当从文件中读取数据时,如果要读取的数据所在的页缓存已经存在,那么就直接把页缓存的数据拷贝给用户即可。否则,内核首先会申请一个空闲的内存页(页缓存),然后从文件中读取数据到页缓存,并且把页缓存的数据拷贝给用户。
  • 当向文件中写入数据时,如果要写入的数据所在的页缓存已经存在,那么直接把新数据写入到页缓存即可。否则,内核首先会申请一个空闲的内存页(页缓存),然后从文件中读取数据到页缓存,并且把新数据写入到页缓存中。对于被修改的页缓存,内核会定时把这些页缓存刷新到文件中。

页缓存的实现

前面主要介绍了页缓存的作用和原理,接下来我们将会分析 Linux 内核是怎么实现页缓存机制的。

1. address_space

在 Linux 内核中,使用 file 对象来描述一个被打开的文件,其中有个名为 f_mapping 的字段,定义如下:

struct file {
...
struct address_space *f_mapping;
};

从上面代码可以看出,f_mapping 字段的类型为 address_space 结构,其定义如下:

struct address_space {
struct inode *host; /* owner: inode, block_device */
struct radix_tree_root page_tree; /* radix tree of all pages */
rwlock_t tree_lock; /* and rwlock protecting it */
...
};

address_space 结构其中的一个作用就是用于存储文件的 页缓存,下面介绍一下各个字段的作用:

  • host:指向当前 address_space 对象所属的文件 inode 对象(每个文件都使用一个 inode 对象表示)。
  • page_tree:用于存储当前文件的 页缓存。
  • tree_lock:用于防止并发访问 page_tree 导致的资源竞争问题。

从 address_space 对象的定义可以看出,文件的 页缓存 使用了 radix树 来存储。

radix树:又名基数树,它使用键值(key-value)对的形式来保存数据,并且可以通过键快速查找到其对应的值。内核以文件读写操作中的数据 偏移量 作为键,以数据偏移量所在的 页缓存 作为值,存储在 address_space 结构的 page_tree 字段中。

下图展示了上述各个结构之间的关系:

如果对 radix树 不太了解,可以简单将其看成可以通过文件偏移量快速找到其所在 页缓存 的结构,有机会我会另外写一篇关于 radix树 的文章。

2. 读文件操作

现在我们来分析一下读取文件数据的过程,用户可以通过调用 read 系统调用来读取文件中的数据,其调用链如下:

read()
└→ sys_read()
└→ vfs_read()
└→ do_sync_read()
└→ generic_file_aio_read()
└→ do_generic_file_read()
└→ do_generic_mapping_read()

从上面的调用链可以看出,read 系统调用最终会调用 do_generic_mapping_read 函数来读取文件中的数据,其实现如下:

void
do_generic_mapping_read(struct address_space *mapping,
struct file_ra_state *_ra,
struct file *filp,
loff_t *ppos,
read_descriptor_t *desc,
read_actor_t actor)
{
struct inode *inode = mapping->host;
unsigned long index;
struct page *cached_page;
... cached_page = NULL;
index = *ppos >> PAGE_CACHE_SHIFT;
... for (;;) {
struct page *page;
... find_page:
// 1. 查找文件偏移量所在的页缓存是否存在
page = find_get_page(mapping, index);
if (!page) {
...
// 2. 如果页缓存不存在, 那么跳到 no_cached_page 进行处理
goto no_cached_page;
}
... page_ok:
...
// 3. 如果页缓存存在, 那么把页缓存的数据拷贝到用户应用程序的内存中
ret = actor(desc, page, offset, nr);
...
if (ret == nr && desc->count)
continue;
goto out;
... readpage:
// 4. 从文件读取数据到页缓存中
error = mapping->a_ops->readpage(filp, page);
...
goto page_ok;
... no_cached_page:
if (!cached_page) {
// 5. 申请一个内存页作为页缓存
cached_page = page_cache_alloc_cold(mapping);
...
} // 6. 把新申请的页缓存添加到文件页缓存中
error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL);
...
page = cached_page;
cached_page = NULL;
goto readpage;
} out:
...
}

do_generic_mapping_read 函数的实现比较复杂,经过精简后,上面代码只留下最重要的逻辑,可以归纳为以下几个步骤:

  • 通过调用 find_get_page 函数查找要读取的文件偏移量所对应的页缓存是否存在,如果存在就把页缓存中的数据拷贝到应用程序的内存中。
  • 否则调用 page_cache_alloc_cold 函数申请一个空闲的内存页作为新的页缓存,并且通过调用 add_to_page_cache_lru 函数把新申请的页缓存添加到文件页缓存和 LRU 队列中(后面会介绍)。
  • 通过调用 readpage 接口从文件中读取数据到页缓存中,并且把页缓存的数据拷贝到应用程序的内存中。

从上面代码可以看出,当页缓存不存在时会申请一块空闲的内存页作为页缓存,并且通过调用 add_to_page_cache_lru 函数把其添加到文件的页缓存和 LRU 队列中。我们来看看 add_to_page_cache_lru 函数的实现:

 int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
pgoff_t offset, gfp_t gfp_mask)
{
// 1. 把页缓存添加到文件页缓存中
int ret = add_to_page_cache(page, mapping, offset, gfp_mask);
if (ret == 0)
lru_cache_add(page); // 2. 把页缓存添加到 LRU 队列中
return ret;
}

add_to_page_cache_lru 函数主要完成两个工作:

  • 通过调用 add_to_page_cache 函数把页缓存添加到文件页缓存中,也就是添加到 address_space 结构的 page_tree 字段中。
  • 通过调用 lru_cache_add 函数把页缓存添加到 LRU 队列中。LRU 队列用于当系统内存不足时,对页缓存进行清理时使用。

总结

本文主要介绍了 页缓存 的作用和原理,并且介绍了在读取文件数据时对页缓存的处理过程。本文并没有介绍写文件操作对应的页缓存处理和当系统内存不足时怎么释放页缓存,有兴趣的话可以自行阅读相关的代码实现。

原文作者:Linux内核那些事

原文地址:https://cloud.tencent.com/developer/article/1848933(版权归原作者所有,侵权留言联系删除)

[转帖]一文看懂Linux内核页缓存(Page Cache)的更多相关文章

  1. [转帖]一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系

    一文看懂web服务器.应用服务器.web容器.反向代理服务器区别与联系 https://www.cnblogs.com/vipyoumay/p/7455431.html 我们知道,不同肤色的人外貌差别 ...

  2. [转帖] 一文看懂:"边缘计算"究竟是什么?为何潜力无限?

    一文看懂:"边缘计算"究竟是什么?为何潜力无限? 转载cnbeta   云计算 雾计算 边缘计算...   知名创投调研机构CB Insights撰文详述了边缘计算的发展和应用前景 ...

  3. [转帖]一文看懂mysql数据库本质及存储引擎innodb+myisam

    一文看懂mysql数据库本质及存储引擎innodb+myisam https://www.toutiao.com/i6740201316745740807/ 原创 波波说运维 2019-09-29 0 ...

  4. 【转帖】一文看懂docker容器技术架构及其中的各个模块

    一文看懂docker容器技术架构及其中的各个模块 原创 波波说运维 2019-09-29 00:01:00 https://www.toutiao.com/a6740234030798602763/ ...

  5. 一文看懂java io系统 (转)

    出处:  一文看懂java io系统   学习java IO系统,重点是学会IO模型,了解了各种IO模型之后就可以更好的理解java IO Java IO 是一套Java用来读写数据(输入和输出)的A ...

  6. 一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系

    我们知道,不同肤色的人外貌差别很大,而双胞胎的辨识很难.有意思的是Web服务器/Web容器/Web应用程序服务器/反向代理有点像四胞胎,在网络上经常一起出现.本文将带读者对这四个相似概念如何区分. 1 ...

  7. 一文看懂https如何保证数据传输的安全性的【转载、收藏】

    一文看懂https如何保证数据传输的安全性的   一文看懂https如何保证数据传输的安全性的 大家都知道,在客户端与服务器数据传输的过程中,http协议的传输是不安全的,也就是一般情况下http是明 ...

  8. 一文看懂Stacking!(含Python代码)

    一文看懂Stacking!(含Python代码) https://mp.weixin.qq.com/s/faQNTGgBZdZyyZscdhjwUQ

  9. Nature 为引,一文看懂个体化肿瘤疫苗前世今生

    进入2017年,当红辣子鸡PD-1疗法,一路横扫多个适应症.而CAR-T治疗的“小车”在获得FDA专委会推荐后也已经走上高速路,成为免疫治疗又一里程碑事件.PD-1.CAR-T之后,下一个免疫治疗产品 ...

  10. 一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了

    一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了 转载: 大数据本身是个很宽泛的概念,Hadoop生态圈(或者泛生态圈)基本上都是为了处理超过单机尺度的数据处理而诞生的.你可以把它 ...

随机推荐

  1. Pikachu漏洞靶场 XSS(跨站脚本攻击)

    XSS 关于xss的我也是一知半解,所以只放出payload来. 反射型xss(get) 修改maxlength属性之后提交如下内容: <script>alert(/xss/);</ ...

  2. APM市场冰火两重天,或许只是行业无处安放的焦虑

    近两年来,无论从国内还是到国外,市场出现一种APM批判的论调,甚至有"传统APM已经落后"的个别声音出现. 可放眼望去,国外主流APM厂商如:DATADOG. Dynatrace. ...

  3. C++ 学习宝藏网站分享

    C++ 学习宝藏网站分享 1. C++ 在线参考手册 Cppreference https://zh.cppreference.com C++ 开发者必备的在线参考手册,是我最常访问的 C++ 网站之 ...

  4. 2023-05-31:给定一个整数数组 A,你可以从某一起始索引出发,跳跃一定次数 在你跳跃的过程中,第 1、3、5... 次跳跃称为奇数跳跃 而第 2、4、6... 次跳跃称为偶数跳跃 你可以按以下

    2023-05-31:给定一个整数数组 A,你可以从某一起始索引出发,跳跃一定次数 在你跳跃的过程中,第 1.3.5... 次跳跃称为奇数跳跃 而第 2.4.6... 次跳跃称为偶数跳跃 你可以按以下 ...

  5. 2021-01-16:我截获了登录token的话,是不是就获得了登录状态,这样就不安全了。如何保证安全?

    福哥答案2021-01-06: 知乎答案: 首先,Token 一般放在 Header 或者 Cookies 中,Http 是明文传输,Https 是密文传输.可以一定程度防止Token 截获. 第二, ...

  6. EDS从小白到专家丨打造数据交换的六边形卫士,让你的数据你做主

    本文分享自华为云社区<[EDS从小白到专家]第4期:打造数据交换的六边形卫士,让你的数据你做主>,作者: 开天aPaaS小助手 . 你还在担心数据共享后一旦"失控"将爆 ...

  7. 看故事学Redis:再不懂,我怀疑你是假个开发

    摘要:还不懂Redis?看完这个故事就明白了! 本文转载自博客园社区<还不懂Redis?看完这个故事就明白了!>,作者:轩辕之风 我是Redis 你好,我是Redis,一个叫Antirez ...

  8. Java程序员都要懂得知识点:原始数据类型

    摘要:Java原始数据类型有short.byte.int.long.boolean.char.float.double.原始数据是未处理的或简化的数据,它构成了物理存在的数据,原始数据具有多种存在形式 ...

  9. 40个问题让你快速掌握Java多线程的精髓

    摘要:多线程可以理解为在同一个程序中能够同时运行多个不同的线程来执行不同的任务,这些线程可以同时利用CPU的多个核心运行. 多线程可以理解为在同一个程序中能够同时运行多个不同的线程来执行不同的任务,这 ...

  10. 应对全场景AI框架部署挑战,MindSpore“四招”让你躺平

    摘要:所谓全场景AI,是指可以将深度学习技术快速应用在云边端不同场景下的硬件设备上,包括云服务器.移动终端以及IoT设备等等,高效运行并能有效协同. 本文分享自华为云社区<AI框架的挑战与Min ...