read()系统调用页面错误不依赖于文件大小

bro*_*oot 3 c linux page-fault perf

我正在使用read()in 阅读不同大小的文件(1KB - 1GB)C.但每次我检查page-faults使用时perf-stat,它总是给我相同(几乎)的值.

我的机器:( 虚拟机上的fedora 18,RAM - 1GB,磁盘空间 - 20 GB)

uname -a
Linux localhost.localdomain 3.10.13-101.fc18.x86_64 #1 SMP Fri Sep 27 20:22:12 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

mount | grep "^/dev"
/dev/mapper/fedora-root on / type ext4 (rw,relatime,seclabel,data=ordered)
/dev/sda1 on /boot type ext4 (rw,relatime,seclabel,data=ordered)
Run Code Online (Sandbox Code Playgroud)

我的代码:

 10 #define BLOCK_SIZE 1024
. . . 
 19         char text[BLOCK_SIZE];
 21         int total_bytes_read=0;
. . .

 81         while((bytes_read=read(d_ifp,text,BLOCK_SIZE))>0)
 82         {
 83                 write(d_ofp, text, bytes_read); // writing to /dev/null
 84                 total_bytes_read+=bytes_read;
 85                 sum+=(int)text[0];  // doing this just to make sure there's 
                                             // no lazy page loading by read()
                                             // I don't care what is in `text[0]`
 86         }
 87         printf("total bytes read=%d\n", total_bytes_read);
 88         if(sum>0)
 89                 printf("\n");
Run Code Online (Sandbox Code Playgroud)

Perf-stat输出:( 显示文件大小,读取文件的时间和页面错误数)

[read]:   f_size:    1K B, Time:  0.000313 seconds, Page-faults: 150, Total bytes read: 980 
[read]:   f_size:   10K B, Time:  0.000434 seconds, Page-faults: 151, Total bytes read: 11172
[read]:   f_size:  100K B, Time:  0.000442 seconds, Page-faults: 150, Total bytes read: 103992
[read]:   f_size:    1M B, Time:  0.00191  seconds, Page-faults: 151, Total bytes read: 1040256
[read]:   f_size:   10M B, Time:  0.050214 seconds, Page-faults: 151, Total bytes read: 10402840 
[read]:   f_size:  100M B, Time:  0.2382   seconds, Page-faults: 150, Total bytes read: 104028372 
[read]:   f_size:    1G B, Time:  5.7085   seconds, Page-faults: 148, Total bytes read: 1144312092 
Run Code Online (Sandbox Code Playgroud)

问题: 1.1KB和1GB大小的
文件的页面错误如何read()相同?由于我也在读数据(代码行#84),我确保实际读取数据.
2.我能想到它没有遇到那么多页面错误的唯一原因是因为数据已经存在于主存储器中.如果是这种情况,我该如何刷新它,这样当我运行我的代码时它实际上显示了真正的页面错误?否则我永远无法衡量真正的表现read().

Edit1:
echo 3 > /proc/sys/vm/drop_caches没有帮助,输出仍然保持不变.

Edit2: For mmap,输出perf-stat是:

[mmap]:   f_size:    1K B, Time:  0.000103 seconds, Page-faults: 14
[mmap]:   f_size:   10K B, Time:  0.001143 seconds, Page-faults: 151
[mmap]:   f_size:  100K B, Time:  0.002367 seconds, Page-faults: 174
[mmap]:   f_size:    1M B, Time:  0.007634 seconds, Page-faults: 401
[mmap]:   f_size:   10M B, Time:  0.06812  seconds, Page-faults: 2,688
[mmap]:   f_size:  100M B, Time:  0.60386  seconds, Page-faults: 25,545
[mmap]:   f_size:    1G B, Time:  4.9869   seconds, Page-faults: 279,519
Run Code Online (Sandbox Code Playgroud)

osg*_*sgx 5

我想你不明白究竟是什么页面错误.根据维基百科,pagefault 是一个"陷阱"(例外),一种中断,当程序试图访问某些东西时由CPU本身生成,而这些内容没有加载到物理内存中(但通常已经在虚拟内存中注册了标记为"不存在"的页面P:存在位= 0).

Pagefault很糟糕,因为它迫使CPU停止执行用户程序并切换到内核.并且内核模式中的页面故障并不常见,因为内核可以在访问之前检查页面存在.如果内核函数想要将内容写入新页面(在您的情况下是readsyscall),它将通过显式调用页面分配器来分配页面,而不是通过尝试访问它并导致页面错误.使用显式内存管理执行的中断更少,代码更少.

---阅读案例---

你读的是通过处理sys_readFS/read_write.c.这是调用链(可能不完全):

472 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
479                 ret = vfs_read(f.file, buf, count, &pos);
  vvv
353 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
368                         ret = file->f_op->read(file, buf, count, pos);
  vvv
Run Code Online (Sandbox Code Playgroud)

FS/EXT4/file.c

626 const struct file_operations ext4_file_operations = {
628         .read           = do_sync_read,

... do_sync_read -> generic_file_aio_read -> do_generic_file_read
Run Code Online (Sandbox Code Playgroud)

毫米/ filemap.c

1100 static void do_generic_file_read(struct file *filp, loff_t *ppos,
1119         for (;;) {
1120                 struct page *page;
1127                 page = find_get_page(mapping, index);
1128                 if (!page) {
1134                                 goto no_cached_page;  
  // osgx - case when pagecache is empty  ^^vv
1287 no_cached_page:
1288                 /*
1289                  * Ok, it wasn't cached, so we need to create a new
1290                  * page..
1291                  */
1292                 page = page_cache_alloc_cold(mapping);
Run Code Online (Sandbox Code Playgroud)

包括/ LINUX/pagemap.h

233 static inline struct page *page_cache_alloc_cold(struct address_space *x)
235         return __page_cache_alloc(mapping_gfp_mask(x)|__GFP_COLD);
  vvv
222 static inline struct page *__page_cache_alloc(gfp_t gfp)
224         return alloc_pages(gfp, 0);
Run Code Online (Sandbox Code Playgroud)

所以我可以通过直接调用跟踪read()syscall在页面分配(alloc_pages)中的结束.分配页面后,read()系统调用会将数据从HDD传输到新页面,然后返回给用户(考虑到文件未在页面缓存中缓存的情况).如果数据已经在页面缓存中,read()(do_generic_file_read)将通过创建其他映射来重用页面缓存中的现有页面,而无需实际读取HDD.

read()返回时,所有的数据在内存中,并读取访问不会产生页缺失.

--- mmap案例---

如果您重写测试以执行mmap()文件,然后访问(text[offset])文件的非当前页面(它不在页面缓存中),则会发生真正的页面故障.

所有页面故障计数器(perf stat/proc/$pid/stat)仅在CPU生成真正的页面故障陷阱时更新.这是页面错误arch/x86/mm/fault.c的 x86处理程序,它将起作用

1224 dotraplinkage void __kprobes
1225 do_page_fault(struct pt_regs *regs, unsigned long error_code)
1230         __do_page_fault(regs, error_code);
  vvv
1001 /*
1002  * This routine handles page faults.  It determines the address,
1003  * and the problem, and then passes it off to one of the appropriate
1004  * routines.
1005  */
1007 __do_page_fault(struct pt_regs *regs, unsigned long error_code)
 /// HERE is the perf stat pagefault event generator VVV 
1101         perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
Run Code Online (Sandbox Code Playgroud)

以后某个页面故障处理程序将调用 handle_mm_fault- > handle_pte_fault- > __do_fault结束vma->vm_ops->fault(vma, &vmf);.

这个fault虚拟功能已注册mmap,我认为是filemap_fault.此函数将__alloc_page在空页面缓存的情况下执行实际页面分配()和磁盘读取(这将被视为"主要"页面错误,因为它需要外部I/O)或将从页面缓存重新映射页面(如果数据已被预取或已经预取在pagecache中,被视为"次要"pagefault,因为它是在没有外部I/O且通常更快的情况下完成的.


PS:在虚拟平台上进行实验可能会改变一些事情; 例如,即使在guest echo 3 > /proc/sys/vm/drop_caches虚拟机Fedora中清理磁盘缓存(pagecache)之后,来自虚拟硬盘驱动器的数据仍然可以被主机OS缓存.

  • 读取系统调用从磁盘加载数据而没有FAULTing,没有页面错误.Pagefault仅对未映射页面进行加载/存储访问.但是在访问之前读取显式映射页面,没有中断.因此,这样的页面创建不考虑(不计算)为pagefault. (2认同)
  • 尝试将文件mmap()放入内存,然后在每个页面上点击一个字节.我希望您会看到页面错误计数与此用法中的文件大小呈线性关系. (2认同)
  • 是的,阻止读取将在返回程序之前从HDD加载数据.这就是为什么用空的pagecache阻塞读取速度很慢的原因(你可以在drop_caches之后发布测试结果).在`read`中没有像在`mmap`中那样作弊(懒惰)(`read`不需要你的"触摸"反作弊代码,仅适用于`mmap`).[`aio_read`](http://linux.die.net/man/3/aio_read)比较复杂,你不应该在'aio_return`说读取结束之前触摸内存;不允许作弊"*正在读取缓冲区在操作过程中不得访问,否则可能会出现未定义的结果.*" (2认同)
  • 好的,在虚拟机的情况下,你还有一个缓存层,主机操作系统的页面缓存(windows也有文件系统的缓存).您在来宾操作系统中刷新了页面缓存,但虚拟硬盘仍然可以(部分)缓存在主机中.使用裸金属进行测试. (2认同)