IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Linux文件预读对系统的影响

Erlang非业余研究 2011-06-01 13:21:31 累计浏览 4,406 次
本机暂存
    Linux系统很重要的一个性能提升点就是它的Pagecache, 因为内存比IO快太多了,所以大家都想进办法来利用这个cache。 文件系统也不例外,为了达到高性能,文件读取通常采用预读来预测用户的行为,把用户可能需要的数据预先读取到cache去,达到高性能的目的。

    Linux各个发行版readahead的实现差异很大,我们这里重点讨论2.6.18, RHEL 5U4发行版的行为.文件预读的实现主要在mm/readahead.c中,代码才603行。 预读的流程大概是这样的,用户需要文件页面的时候入口函数do_generic_mapping_read会委托page_cache_readahead来进行处理。它首先判断用户的IO是顺序的还是随机的,如果是随机的就没啥好预读. 如果是顺序的话,那么预读算法会根据用户上一次读取的页面的使用情况评估出预读的窗口,决定要读多少页面。读页面的模块会先检查要读取页面在pagecache里面是否已经存在,如果不存在的话就需要发起IO请求,读取相应的页面。

    这个预读的关键参数有3个: 用户的req_size, 预读算法评估出来的nr_to_read,以及实际上IO读取的页面数actual。

    接下来我们就是要查看系统是如何运作的,所以我首先写了个systemtap脚本叫做ratop.stp来获取这些数据:

$ uname -r
2.6.18-164.el5

$ rpm -i kernel-debuginfo-common-2.6.18-164.el5.x86_64.rpm
$ rpm -i kernel-debuginfo-2.6.18-164.el5.x86_64.rpm  

$ cat > ratop.stp
#!/usr/bin/stap -DMAXMAPENTRIES=10240

global total, skip
global req, to_read, actual
global __inode_filename

probe kernel.function("page_cache_readahead")
{
ino = __file_ino($filp)

req[ino]+=$req_size;
total++;
if($ra->flags & 0x2) skip++;
}

probe kernel.function("__do_page_cache_readahead").return
{
ino = __file_ino($filp)

to_read[ino]+= $nr_to_read;
if($return>0) actual[ino]+=$return;
}

probe timer.ms(5000)
{

if(total)
{
foreach( ino in req-)
{
        s0+= req[ino];
        s1+= to_read[ino]
        s2+= actual[ino];
}

printf("\\n%25s,  %5s%6d, %5s%6d, %5s%8d, %5s%8d, %5s%8d\\n\\n",
        ctime(gettimeofday_s()),
        "TOTAL:", total,
        "SKIP:", skip,
        "REQ:",s0,
        "TO_RD:",s1,
        "NR_RD:",s2
        )

    /* print header */
    printf("%25s %8s %8s %8s\\n",
           "FILENAME","REQ","TO_RD","NR_RD")

    foreach( ino in req- limit 20)
        printf("%25s %8d %8d %8d\\n", find_filename(ino), req[ino], to_read[ino], actual[ino]);

}

delete total;
delete skip;
delete req;
delete to_read;
delete actual;
}

probe generic.fop.open
{
__inode_filename[ino]= filename
}

function find_filename(ino)
{
return __inode_filename[ino]==""?sprint(ino):__inode_filename[ino];
}

probe begin
{
println("::");
}

CTRL +D
$ chmod +x ratop.stp
$ sudo ./ratop.stp
::

 Tue May 31 05:41:37 2011,  TOTAL:  2321, SKIP:     0,  REQ:    6308, TO_RD:    6308, NR_RD:    1424

                 FILENAME      REQ    TO_RD    NR_RD
               056878.sst       15       15        0
               062889.sst       13       13        6
..
其中各个参数含义解释如下:
TOTAL: 系统共调用了多少次预读
SKIP: 由于页面在PAGECACHE中存在,略过多少次预读
REQ: 用户准备读取的页面数
TO_RD:预读算法告诉我们要读取的页面数
NR_RD:实际IO系统读取的页面数

这个脚本每5秒打印下系统目前的预读情况。

    好吧,有了这个工具我们就可以做实验了。

     先在一个终端下运行我们的脚本:

$ sudo ./ratop.stp
::

#等着出数据...

    然后在另外一个终端下做实验:

#准备个数据文件
$ dd if=/dev/zero of=test count=1024 bs=4096
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB) copied, 0.008544 seconds, 491 MB/s
#清空pagecache
$ sudo sysctl vm.drop_caches=3
vm.drop_caches = 3
#第一次拷贝
$ cp test junk && sleep 5
#第二次拷贝
$ cp test junk

    我们就可以在之前的脚本窗口里看到下面的信息:

#第一次拷贝test,我们可以看到 用户要1025个页面,预读决定读1084,但是实际IO读了1024,很合理,因为当时pagecache是空的
 Tue May 31 05:50:21 2011,  TOTAL:  1038, SKIP:     0,  REQ:    1039, TO_RD:    1320, NR_RD:    1109

                 FILENAME      REQ    TO_RD    NR_RD
                     test     1025     1084     1024
                       cp        3       36       18
...
#第二次拷贝test,我们可以看到 用户要1025个页面,预读决定读284,但是实际IO读了0,很合理,因为所有的页面在pagecache里面都已经存在
 Tue May 31 05:50:46 2011,  TOTAL:  1038, SKIP:   804,  REQ:    1039, TO_RD:     328, NR_RD:       0

                 FILENAME      REQ    TO_RD    NR_RD
                     test     1025      284        0
                       cp        3        4        0
  ...

    Linux系统不仅为文件的读取提供自动预读,还提供了readahead这样的系统调用和工具,帮助用户主动预加载数据,我们演示下:

$ readahead junk
Preloaded 0 files (0 KB) in 5 ms

    另外一个窗口说:

Tue May 31 05:57:45 2011,  TOTAL:  1044, SKIP:   805,  REQ:    1045, TO_RD:     348, NR_RD:       0

                 FILENAME      REQ    TO_RD    NR_RD
                     junk     1026      284        0
                readahead        3        4        0

    Linux还支持对每个设备设定预读的默认大小,不同的大小可以用来控制预读的力度,用户可以自行改变:

$ pwd
/sys/block/sda/queue
$ cat read_ahead_kb
128
$ echo 256 |sudo tee  read_ahead_kb
256

    后续我会用这个工具分析leveldb数据库的行为,欢迎关注!

    总结: 如果actual读比用户req的要多很多, 那么我们的很多预读就浪费了,可以考虑减少预读的大小。

同分类推荐文章

  1. 从零重建 macOS 开发机:可复现的环境初始化流程 (2026-06-14 20:36:00)
  2. 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 (2026-06-11 08:10:28)
  3. How to Set Up Homebrew Tap for Private CLI Tools: A Complete Guide (2026-05-27 02:13:03)

查看更多 DevOps 文章 →

建议继续学习

  1. 如何成为Python高手 (累计阅读 54,992)
  2. Linux如何统计进程的CPU利用率 (累计阅读 16,307)
  3. 我的 RHCA 之路 (累计阅读 14,012)
  4. Linux内存点滴 用户进程内存空间 (累计阅读 13,229)
  5. 给程序员新手的一些建议 (累计阅读 13,089)
  6. Linux 性能监控、测试、优化工具 (累计阅读 13,011)
  7. 关于linux内存free的一些事情 (累计阅读 12,867)
  8. include(“./file.php”)和include(“file.php”)区别 (累计阅读 12,789)
  9. ps - 按进程消耗内存多少排序 (累计阅读 12,686)
  10. Google怎么用linux (累计阅读 12,581)