Ошибка страницы системного вызова read() не зависит от размера файла

Я читаю файлы разного размера (1 КБ - 1 ГБ), используя read() в C. Но каждый раз, когда я проверяю page-faults с помощью perf-stat, он всегда дает мне одинаковые (почти) значения.

Моя машина: (fedora 18 на виртуальной машине, ОЗУ — 1 ГБ, место на диске — 20 ГБ)

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)

Мой код:

 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");

Вывод статистики производительности: (показывает размер файла, время чтения файла и количество ошибок страницы)

[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 

Вопросы:
1. Как могут быть одинаковыми ошибки страницы для файла read() размером 1 КБ и 1 ГБ? Поскольку я тоже читаю данные (строка кода № 84), я удостоверяюсь, что данные фактически читаются.
2. Единственная причина, по которой я могу думать, что они не столкнуться с таким количеством отказов страниц из-за того, что данные уже присутствуют в основной памяти. Если это так, как я могу очистить его, чтобы при запуске моего кода он действительно показывал мне истинные ошибки страницы? В противном случае я никогда не смогу измерить истинную производительность read().

Edit1:
echo 3 > /proc/sys/vm/drop_caches не помогает, вывод остается прежним.

Edit2: для 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

person brokenfoot    schedule 26.04.2014    source источник
comment
Не уверен, что это поможет тебе. Для освобождения кэша страниц: echo 1 › /proc/sys/vm/drop_caches Для освобождения dentries и inode: echo 2 › /proc/sys/vm/drop_caches Для освобождения кэша страниц, dentries и inode: echo 3 › /proc/sys/vm/ drop_caches   -  person Sasi V    schedule 27.04.2014
comment
@wildplasser: какая разница? Я должен отметить, что я не использую эту сумму, мне все равно, что она вычисляет.   -  person brokenfoot    schedule 27.04.2014
comment
Эти сбои страниц, скорее всего, являются результатом загрузки вашего кода, т. е. mmap(2) самого исполняемого файла.   -  person Nikolai Fetissov    schedule 27.04.2014
comment
@Sasi: Спасибо, но на выходе это не имеет никакого значения.   -  person brokenfoot    schedule 27.04.2014
comment
@NikolaiNFetissov: Не могли бы вы рассказать об этом подробнее?   -  person brokenfoot    schedule 27.04.2014
comment
сломанная ступня, можешь также проверить сумму всех bytes_read? Какое у вас ядро ​​и файловая система? Я думаю, что вы не получаете много ошибок страниц, потому что блокировка sys_read способна управлять страницами без ошибок. Он просто выделяет страницу непосредственно перед записью на нее. Ошибка страницы возникает только при доступе к несопоставленной странице, это ловушка.   -  person osgx    schedule 27.04.2014
comment
Количество сбоев страниц более или менее постоянно. Общее время выполнения различается из-за того, что барьеры доступа к памяти L1 + l2 достигаются.   -  person wildplasser    schedule 27.04.2014


Ответы (1)


Я думаю, вы не поняли, в чем именно заключается pagefault. pagefault, согласно Википедии, является "ловушкой" (исключением), своего рода прерыванием, которое генерируется самим ЦП, когда программы пытаются получить доступ к чему-то, что не загружено в физическую память (но обычно уже зарегистрировано в виртуальной памяти, и его страница помечена как «не существующая» P: Текущий бит = 0).

Pagefault — это плохо, потому что заставляет ЦП остановить выполнение пользовательской программы и переключиться на ядро. И pagefaults в режиме ядра не так часто, потому что ядро ​​может проверять наличие страницы перед доступом к ней. Если функция ядра хочет что-то записать на новую страницу (в вашем случае это системный вызов read), она выделит страницу, явно вызвав распределитель страниц, а не пытаясь получить к ней доступ и вызвав ошибку pagefault. Меньше прерываний и меньше кода для выполнения с явным управлением памятью.

--- читать дело ---

Ваше чтение обрабатывается sys_read из fs/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

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

mm/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);

include/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);

Таким образом, я могу отследить, что системный вызов read() заканчивается выделением страницы (alloc_pages) с помощью прямых вызовов. После выделения страницы системный вызов read() выполнит DMA-перенос данных с жесткого диска на новую страницу, а затем вернет ее пользователю (с учетом случая, когда файл не закеширован в pagecache). Если данные уже были в кэше страниц, read() (do_generic_file_read) будет повторно использовать существующую страницу из кэша страниц без фактического чтения с жесткого диска, создав дополнительное сопоставление.

После возврата read() все данные находятся в памяти, и доступ к ним для чтения не приведет к ошибке страницы.

--- дело mmap ---

Если вы перепишете тест, чтобы выполнить mmap()обработку вашего файла, а затем получите доступ (text[offset]) к отсутствующей странице вашего файла (ее не было в кэше страниц), произойдет настоящая ошибка страницы.

Все счетчики отказов страниц (perf stat и /proc/$pid/stat) обновляются ТОЛЬКО тогда, когда центральным процессором генерируются реальные ловушки отказов страниц. Вот обработчик ошибки страницы x86 arch/ x86/mm/fault.c, который будет работать

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);

и где-то позже обработчик pagefault вызовет handle_mm_fault - > handle_pte_fault -> __do_fault, заканчивающийся на vma->vm_ops->fault(vma, &vmf);.

Эта виртуальная функция fault была зарегистрирована в mmap, и я думаю, что это filemap_fault. Эта функция выполнит фактическое выделение страниц (__alloc_page) и чтение с диска в случае пустого кэша страниц (это будет считаться «серьезной» ошибкой страницы, поскольку требует внешнего ввода-вывода) или переназначит страницу из кэша страниц (если данные были предварительно загружены или уже в кэше страниц, считается «незначительной» ошибкой страницы, потому что это было сделано без внешнего ввода-вывода и, как правило, быстрее).


PS: Эксперименты на виртуальной платформе могут что-то изменить; например, даже после очистки дискового кеша (кэша страниц) в гостевой Fedora с помощью echo 3 > /proc/sys/vm/drop_caches данные с виртуального жесткого диска все еще могут кэшироваться основной ОС.

person osgx    schedule 27.04.2014
comment
Я знаю, что такое ошибка страницы. Чего я не понимаю, так это того, как страницы могут быть уже в основной памяти (уже загружены с диска) для файлов размером от 1 КБ до 1 ГБ (как я обновил в своем вопросе, моя основная память (Виртуальная машина) составляет всего 1 ГБ ), что означает, что все страницы не могут одновременно присутствовать в основной памяти для файла размером 1 ГБ. - person brokenfoot; 27.04.2014
comment
Или, другими словами, как может быть равным количество ошибок страниц для файла размером 1 КБ и 1 ГБ? - person brokenfoot; 27.04.2014
comment
Системные вызовы чтения загружают данные с диска без ошибок, без ошибок страниц. Pagefault — это доступ только для загрузки/сохранения к несопоставленной странице. Но прочитайте явно отображаемую страницу перед доступом, без прерываний. Таким образом, создание такой страницы не рассматривается (не считается) как pagefault. - person osgx; 27.04.2014
comment
Попробуйте mmap() записать файл в память, а затем нажимать байт на каждой странице. Я ожидаю, что вы увидите линейную шкалу счетчика ошибок страниц в зависимости от размера файла при таком использовании. - person Jean-Paul Calderone; 27.04.2014
comment
@Jean-PaulCalderone: Да, это правда, для mmap значения для page-fault следующие: 1 КБ — 148, 10 КБ — 151, 100 КБ — 173, 1 МБ — 402, 10 МБ — 2687, 100 МБ — 25 545, 1 ГБ — 147 004 . - person brokenfoot; 27.04.2014
comment
@osgx: я этого не знал. Не могли бы вы указать мне какую-нибудь ссылку? - person brokenfoot; 27.04.2014
comment
Собственно я сейчас копался в источниках между generic_file_aio_read, do_generic_file_read и generic_writepages. На странице makelinux.net/books/lkd2/ch15lev1sec1 говорится, что writepage() виртуальная функция (которая называется формой sys_read()) в основном делают grab_cache_page, что звучит как распределитель страниц памяти чтения. - person osgx; 27.04.2014
comment
@osgx: Итак, согласно упомянутому вами источнику, он создает новую страницу, если эта страница еще не кэширована. Но как фактические данные копируются на эту страницу. Фактические данные могут быть на диске. Разве это не вызовет page-fault ? Я не могу соединить точки. - person brokenfoot; 27.04.2014
comment
Распределение страниц: alloc_page. Данные считываются с диска путем программирования операции записи DMA (диск будет считывать данные и записывать их на некоторую физическую страницу в ОЗУ). В случае read() он и выделяет, и программирует DMA, затем ждет завершения DMA и затем возвращается. Данные на месте, вины нет. В случае mmap() регистрируется только отображение памяти (виртуальная память XX..YY является частью файла ZZZ со смещением OFF). Когда вы получаете доступ к странице из сопоставления, pagefault вызовет код FS, который запрограммирует DMA, и после того, как DMA будет выполнен, он вернет вам данные и повторно выполнит вашу неудачную (asm) операцию загрузки. - person osgx; 27.04.2014
comment
Таким образом, read() программирует DMA до фактического доступа к странице. И к моменту обращения к странице страницы уже были скопированы в оперативную память DMA, так что никаких page-fault. Я правильно понял? И спасибо за терпение! :) - person brokenfoot; 27.04.2014
comment
Да, блокировка чтения загрузит данные с жесткого диска перед возвратом в программу. Это причина того, почему блокировка чтения с пустым кэшем страниц выполняется так медленно (вы можете публиковать результаты теста после drop_caches). В read нет читерства (ленивого) как было в mmap (ваш трогательный античит-код не нужен для read, только для mmap). aio_read сложнее, не следует трогать память до того, как aio_return скажет, что чтение завершено; нет мошенничество разрешено Область считываемого буфера не должна быть доступна во время операции, иначе могут возникнуть неопределенные результаты. - person osgx; 27.04.2014
comment
Хорошо, в случае виртуальной машины у вас есть еще один уровень кэширования, кеш страниц хост-ОС (у Windows тоже есть кеш для файловых систем). Вы выполнили очистку кэша страниц в гостевой ОС, но виртуальный жесткий диск все еще может быть (частично) кэширован в хосте. Используйте голый металл для проверки. - person osgx; 27.04.2014
comment
Большое спасибо @osgx! Кстати, я запустил ту же программу после drop_cache, она дала тот же результат. Я также проверю на родной Linux-машине. - person brokenfoot; 27.04.2014
comment
И последнее, можете ли вы обновить ответы с комментариями, которые вы здесь сделали? - person brokenfoot; 27.04.2014
comment
Я не хочу, потому что я думаю, что ответ правильный на исходный вопрос. Здесь не задавался вопрос о mmap, и cnicutar в stackoverflow.com/a/23295928/196561 уже ответил о разнице mmap/read. Я просто отметил в своем ответе, где находятся ошибки страницы. ОБНОВЛЕНИЕ: хорошо, немного переписал с разделением случаев read и mmap. Добавлена ​​информация о мелких и крупных сбоях страниц. - person osgx; 27.04.2014
comment
PS: Как мы знаем из ответа stackoverflow.com/a/23317928/196561, getrusage сообщает количество прочитанных/записанных блоков данных через read()/write() в полях ru_inblock и ru_oublock файла struct rusage, вместо ru_minflt и ru_majflt в случае mmap(file,..) + pagefault для чтения данных. - person osgx; 28.04.2014