当前位置: 首页 > news >正文

内核的文件预取逻辑及blockdev的相关配置

一、背景

在之前的博客 文件页的预映射逻辑 里,我们讲到了缺页异常的内核预映射的逻辑,要注意预映射和预取是不一样的。预映射操作的是物理内存和虚拟内存的映射关系,而预取操作的是磁盘空间到内存。如何来理解这两句话呢?虽然从CPU角度来看,软件上所有需要用到的数据,如果需要给CPU使用的话,都是需要有对应内存的,但是需要有物理内存和虚拟内存的这个区分,对于用户态而言,我们代码进行读写的都是虚拟内存,其对应的物理内存在真正使用的时候是需要进行对应的,假设应用程序A在读取一个磁盘上的文件的头一个PAGE时,它想用的是第一个PAGE,但是内核为了性能是会进行预取的,也就是说,它会往后多读这个文件一定大小。为了在你真正在使用后面的PAGE时可以进行快速的映射,也就是完成缺页异常。这里面就涉及两种虚拟地址空间,一种是给用户态使用,一种是给内核使用,给内核使用的就是内核虚拟地址空间。对于文件页所进行内核层面的page cache的缓存而言,只需要有内核虚拟地址空间就足够了,因为CPU层面可以用内核的虚拟地址空间的页表来去管理和维护page cache。而用户态在触发缺页异常后,内核的预映射逻辑就是把这些内核管理的page cache内容给映射给用户态,也就是当前正在使用该page cache的进程,也就是用户地址空间。

在之前的 内存管理相关——malloc,mmap,mlock与unevictable列表 博客里的 3.2.3 一节里我们就讲过用blockdev命令来进行读取和设置文件页的预取,我们在下面第二章里,先用strace来去抓在使用blockdev命令时实际调用了什么ioctl,在第三章里,我们讲述相关的原理细节。

二、使用strace来分析blockdev的读取和设置预取的操作

2.1 blockdev读取和设置预取的命令

读取/dev/sda的预取数值的命令:

blockdev --getra /dev/sda

一般默认的这个数值都是256,后面在 3.1 里会介绍这个256是个什么单位。

设置/dev/sda的预取数值的命令,如下面设置成2048:

blockdev --setra 2048 /dev/sda

2.2 通过strace来抓取blockdev读取预取数值时ioctl的情况

下面的命令就是通过strace来抓取blockdev读取预取数值时进行了哪些系统调用的操作

strace -o testblockdev.txt blockdev --getra /dev/sda

抓到的testblockdev.txt里内容如下:

execve("/usr/sbin/blockdev", ["blockdev", "--getra", "/dev/sda"], 0x7ffcb227b3e0 /* 30 vars */) = 0
brk(NULL)                               = 0x623a8d000000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffe1073e30) = -1 EINVAL (无效的参数)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x728728e53000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=76932, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 76932, PROT_READ, MAP_PRIVATE, 3, 0) = 0x728728e40000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0O{\f\225\\=\201\327\312\301P\32$\230\266\235"..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=2220400, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2264656, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x728728c00000
mprotect(0x728728c28000, 2023424, PROT_NONE) = 0
mmap(0x728728c28000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x728728c28000
mmap(0x728728dbd000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x728728dbd000
mmap(0x728728e16000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x215000) = 0x728728e16000
mmap(0x728728e1c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x728728e1c000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x728728e3d000
arch_prctl(ARCH_SET_FS, 0x728728e3d740) = 0
set_tid_address(0x728728e3da10)         = 9502
set_robust_list(0x728728e3da20, 24)     = 0
rseq(0x728728e3e0e0, 0x20, 0, 0x53053053) = 0
mprotect(0x728728e16000, 16384, PROT_READ) = 0
mprotect(0x623a8b936000, 4096, PROT_READ) = 0
mprotect(0x728728e8d000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x728728e40000, 76932)           = 0
getrandom("\x6b\x70\xa1\x81\x12\x48\x9e\x82", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x623a8d000000
brk(0x623a8d021000)                     = 0x623a8d021000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=8876560, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 8876560, PROT_READ, MAP_PRIVATE, 3, 0) = 0x728728200000
close(3)                                = 0
openat(AT_FDCWD, "/dev/sda", O_RDONLY)  = 3
ioctl(3, BLKRAGET, [256])               = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
write(1, "256\n", 4)                    = 4
close(3)                                = 0
dup(1)                                  = 3
close(3)                                = 0
dup(2)                                  = 3
close(3)                                = 0
exit_group(0)                           = ?

下图是我们看抓到的testblockdev.txt产出物里的有关blockdev的--getra操作对应的ioctl:

可以从上图里看到,关键的ioctl是/dev/sda节点的BLKRAGET的操作,我们在内核代码里找到是在block/ioctl.c里:

可以从上图看到BLKRAGET和BLKFRAGET在执行时是一样的效果,虽然从include/uapi/linux/fs.h下面的定义来看,它们一个对应的是block device的read ahead,一个对应的是filesystem的read ahead:

其实在set时,它们也是一样的效果:

三、内核文件预取的默认值和相关逻辑细节

3.1 内核文件预取的默认值和单位

我们来看一下BLKRAGET在ioctl时的相关逻辑:

可以从上图看到,它是通过bdev->bd_disk->bdi->ra_pages的数值乘上PAGE_SIZE再除以一般用的扇区大小512字节。

我们看一下这个ra_pages是在哪里赋值的:

可以从上图里看到,ra_pages是在bdi_alloc时赋值了一个默认的初值,VM_READAHEAD_PAGES内核里定义的是128K:

所以一般的getra得到的就是128K/PAGE_SIZE*PAGE_SIZE/512也就是256。

但是并不是所有的系统上的该数值都是256的,比如如下系统默认是:

从/sys/block/sda/bdi/下的相关节点也可以获取该信息:

只是它们俩单位不一样。

现在问题来了,为什么默认就是128K,计算出来就应该是256,怎么会有2048的情况,因为它会在下图的block/blk-settings.c里的disk_update_readahead函数里进行调整:

3.2 blockdev里的read ahead数值如何影响内核page cache的逻辑

上面是获取和设置该blockdev的read ahead设置的方法及相关细节,那么它是怎么真正影响到实操层面的page cache里的逻辑的呢,我们也给出相关细节,page cache里的相关readahead的操作是在ondemand_readahead函数里,如下图,它是在mm/readahead.c里:

上图里的max_pages变量被赋值成ra->ra_pages的数值,而这个就是预取的page数。

我们看一下这个ra->ra_pages的数值是如何和blockdev里的bdi->ra_pages进行关联的,它们是在下图里的file_ra_state_init里进行关联的:

上图里的inode_to_bdi的实现如下:

可以看到就是bdi->ra_pages赋值给ra->ra_pages的。

http://www.dtcms.com/a/540704.html

相关文章:

  • [特殊字符] Web 字体裁剪优化实践:把 42MB 字体包瘦到 1.6MB
  • 平滑过渡,破解多库并存:浙人医基于金仓KFS的医疗信创实战解析
  • 做经营性的网站需要注册什么条件网站构思
  • Answer企业社区实战:零成本搭建技术问答平台,远程协作效率提升300%!
  • “听书”比“看书”更省力?
  • 大连 手机网站案例网站定位方案
  • window安装MYSQL5.5出错:a windows service with the name MYSQL alreadyexists....
  • 珠海做网站报价影响网站排名的因素
  • 6.1.2.2 大数据方法论与实践指南-离线任务SQL 任务开发规范
  • Java 大视界 -- Java 大数据在智能交通高速公路收费系统优化与通行效率提升实战(429)
  • 网站可以做怀孕单吗平面设计图数字标识
  • 图神经网络入门:手写一个 VanillaGNN-从邻接矩阵理解图神经网络的消息传递
  • 网站模版带后台酒类招商网站大全
  • 营销型网站创建网页制作三剑客通常指
  • 【笔试真题】- 电信-2025.10.11
  • 云渲染与传统渲染:核心差异与适用场景分析
  • 什么是流程监控?如何构建跨系统BPM的实时监控体系?
  • 直通滤波....
  • eclipse做网站代码惠州市
  • 零基础新手小白快速了解掌握服务集群与自动化运维(十五)Redis模块-Redis主从复制
  • 视频网站自己怎么做的正规的大宗商品交易平台
  • vue3 实现贪吃蛇手机版01
  • 胶州网站建设dch100室内装修设计师工资一般多少钱
  • 计算机视觉、医学图像处理、深度学习、多模态融合方向分析
  • 小白入门:基于k8s搭建训练集群,实战CIFAR-10图像分类
  • 关系型数据库大王Mysql——DML语句操作示例
  • VNC安装
  • 网站建设论文 php苏州关键词排名提升
  • 【MySQL】用户管理详解
  • 怎么制作手机网站金坛区建设工程质量监督网站