В этой главе будет описан Linux 2.4 pagecache. Pagecache - это кэш страниц физической памяти. В мире UNIX концепция кэша страниц приобрела известность с появлением SVR4 UNIX, где она заменила буферный кэш, использовавшийся в операциях вода/вывода.
В SVR4 кэш страниц предназначен исключительно для хранения
    данных файловых систем и потому использует в качестве
    хеш-параметров структуру struct vnode и смещение в файле, в Linux
    кэш страниц разрабатывлся как более универсальный механизм,
    использущий struct address_space (описывается ниже) в качестве
    первого параметра. Поскольку кэш страниц в Linux тесно связан с
    понятием адресных пространств, для понимания принципов работы кэша
    страниц необходимы хотя бы основные знания об adress_spaces.
    Address_space - это некоторое программное обеспечение MMU (Memory
    Management Unit), с помощью которого все страницы одного объекта
    (например inode) отображаются на что-то другое (обычно на
    физические блоки диска). Структура struct address_space определена
    в include/linux/fs.h как:
struct address_space { struct list_head clean_pages; struct list_head dirty_pages; struct list_head locked_pages; unsigned long nrpages; struct address_space_operations *a_ops; struct inode *host; struct vm_area_struct *i_mmap; struct vm_area_struct *i_mmap_shared; spinlock_t i_shared_lock; };
Для понимания принципов работы address_spaces, нам достаточно
    остановиться на некоторых полях структуры, указанной выше:
    clean_pages, dirty_pages и
    locked_pages являются двусвязными списками всех
    "чистых", "грязных" (измененных) и
    заблокированных страниц, которые принадлежат данному адресному
    пространству, nrpages - общее число страниц в данном
    адресном пространстве. a_ops задает методы управления
    этим объектом и host - указатель на inode, которому
    принадлежит данное адресное пространство - может быть NULL,
    например в случае, когда адресное пространство принадлежит
    программе подкачки (mm/swap_state.c,).
Назначение clean_pages, dirty_pages,
    locked_pages и nrpages достаточно
    прозрачно, поэтому более подробно остановимся на структуре
    address_space_operations, определенной в том же
    файле:
struct address_space_operations { int (*writepage)(struct page *); int (*readpage)(struct file *, struct page *); int (*sync_page)(struct page *); int (*prepare_write)(struct file *, struct page *, unsigned, unsigned); int (*commit_write)(struct file *, struct page *, unsigned, unsigned); int (*bmap)(struct address_space *, long); };
Для понимания основ адресных пространств (и кэша страниц)
    следует рассмотреть ->writepage и
    ->readpage, а так же
    ->prepare_write и
    ->commit_write.
Из названий методов уже можно предположить действия, которые они выполняют, однако они требуют некоторого уточнения. Их использование в ходе операций ввода/вывода для более общих случаев дает хороший способ для их понимания. В отличие от большинства других UNIX-подобных операционных систем, Linux имеет набор универсальных файловых операций (подмножество операций SYSV над vnode) для передачи данных ввода/вывода через кэш страниц. Это означает, что при работе с данными отсутствует непосредственное обращение к файловой системе (read/write/mmap), данные будут читаться/записываться из/в кэша страниц (pagecache), по мере возможности. Pagecache будет обращаться к файловой системе либо когда запрошенной страницы нет в памяти, либо когда необходимо записать данные на диск в случае нехватки памяти.
При выполнении операции чтения, универсальный метод сначала пытается отыскать страницу по заданным inode/index.
      hash = page_hash(inode->i_mapping, index);
    
    Затем проверяется - существует ли заданная страница.
      hash = page_hash(inode->i_mapping, index); page =
      __find_page_nolock(inode->i_mapping, index, *hash);
    
    Если таковая отсутствует, то в памяти размещается новая страница и добавляется в кэш.
      page = page_cache_alloc();
       __add_to_page_cache(page, mapping, index, hash);
    
    После этого страница заполняется данными с помощью вызова метода
    ->readpage.
      error = mapping->a_ops->readpage(file, page);
    
    И в заключение данные копируются в пользовательское пространство.
Для записи данных в файловую систему существуют два способа: один - для записи отображения (mmap) и другой - системный вызов write(2). Случай mmap наиболее простой, поэтому рассмотрим его первым. Когда пользовательское приложение вносит изменения в отображение, подсистема VM (Virtual Memory) помечает страницу.
      SetPageDirty(page);
    
    Поток ядра bdflush попытается освободить страницу, в фоне или в
    случае нехватки памяти, вызовом метода ->writepage
    для страниц, которые явно помечены как "грязные". Метод
    ->writepage выполняет запись содержимого страницы
    на диск и освобождает ее.
Второй способ записи намного более сложный. Для каждой страницы
    выполняется следующая последовательность действий (полный исходный
    код смотрите в mm/filemap.c:generic_file_write()).
      page = __grab_cache_page(mapping, index,
      &cached_page);
       mapping->a_ops->prepare_write(file, page, offset,
      offset+bytes);
       copy_from_user(kaddr+offset, buf, bytes);
       mapping->a_ops->commit_write(file, page, offset,
      offset+bytes);
    
    Сначала делается попытка отыскать страницу либо разместить
    новую, затем вызывается метод ->prepare_write,
    пользовательский буфер копируется в пространство ядра и в
    заключение вызывается метод ->commit_write. Как вы
    уже вероятно заметили ->prepare_write и
    ->commit_write существенно отличаются от
    ->readpage и ->writepage, потому
    что они вызываются не только во время физического ввода/вывода, но
    и всякий раз, когда пользователь модифицирует содержимое файла.
    Имеется два (или более?) способа обработки этой ситуации, первый -
    использование буферного кэша Linux, чтобы задержать физический
    ввод/вывод, устанавливая указатель page->buffers на
    buffer_heads, который будет использоваться в запросе
    try_to_free_buffers (fs/buffers.c) при нехватке памяти
    и широко использующийся в текущей версии ядра. Другой способ -
    просто пометить страницу как "грязная" и понадеяться на
    ->writepage, который выполнит все необходимые
    действия. В случае размера страниц в файловой системе меньшего чем
    PAGE_SIZE этот метод не работает.