Документ взят из кэша поисковой машины. Адрес
оригинального документа
: http://jet.sao.ru/hq/sts/linux/doc/kernel/kernel24/lki-4.html
Дата изменения: Unknown Дата индексирования: Tue Oct 2 10:03:58 2012 Кодировка: koi8-r Поисковые слова: comet |
В этой главе будет описан 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
этот метод не работает.