Skip to content

Latest commit

 

History

History
116 lines (97 loc) · 8.24 KB

Linux Page Cache.md

File metadata and controls

116 lines (97 loc) · 8.24 KB

Page cache

Page cache - кэш данных, с которыми работают файловые системы. Эти данные могут быть прочитаны не только с дисков, но и из сети.

Режимы работы кэша

  • Некэшируемый - каждый раз данные читают и записывают сразу на диск. В Linux можно так работать, если открыть файл с флагом O_DIRECT.
  • Write through - чтение производится из кэша, а запись в кэш приводит к немедленной записи на носитель.
  • Write back - чтение и запись производятся только в кэш. Запись на носитель отложена до наступления определённых моментов.

Page cache в Linux по умолчанию работает в режиме Write back.

Структуры данных

Page cache подвязан к struct inode(/include/linux/fs.h) через поле i_mapping типа struct address_space, который содержит дерево закэшированных страниц:

/* /include/linux/fs.h */
struct address_space {
    struct inode        *host;      /* owner: inode, block_device */
    struct radix_tree_root  page_tree;  /* radix tree of all pages */
    ...
}

struct inode {
    ...
    struct address_space    *i_mapping;
    ...
}

Каждая struct page(/include/linux/mm_types.h) имеет поля mapping и index. Mapping указывает на address_space struct inode, которой принадлежит страница. Index - смещение в файле, выраженное в страницах.

struct page {
    /* First double word block */
    unsigned long flags;        /* Atomic flags, some possibly
                     * updated asynchronously */
    union {
        struct address_space *mapping;  /* If low bit clear, points to
                         * inode address_space, or NULL.
                         * If page mapped as anonymous
                         * memory, low bit is set, and
                         * it points to anon_vma object:
                         * see PAGE_MAPPING_ANON below.
                         */
        void *s_mem;            /* slab first object */
        atomic_t compound_mapcount; /* first tail page */
        /* page_deferred_list().next     -- second tail page */
    };

    /* Second double word */
    union {
        pgoff_t index;      /* Our offset within mapping. */
        void *freelist;     /* sl[aou]b first free object */
        /* page_deferred_list().prev    -- second tail page */
    };
    ...
    atomic_t _mapcount;
    ...
}

Флаги состояния страницы

Поле flags struct page имеет несколько флагов (/include/linux/page-flags.h), описывающих состояние страницы:

  • PG_uptodate - выставляется после полного чтения страницы с диска (носителя), если не произвошло ошибки.
  • PG_dirty - данные на страничке новее, чем на диске.
  • PG_writeback - выставляется при начале записи страницы на носитель.

Сброс данных на носитель

Когда Page cache работает в режиме write back, то может быть несколько ситуаций, когда ядро начнёт сбрасывать данные из кэша на диск:

  • Закончилась память в системе, позвали reclaim:
    • Чистый page cache
    • Грязный page cache
    • Анонимную память
    • и т.д.
  • Достигнут предел разрешённого размера грязного page cache.
  • umount() - размонтирование файловой системе.
  • fsync() или sync() - принудительная синхронизация. fsync() - сбрасывает кэш файла. sync() - сбрасывает весь грязный page cache.
  • pdflush - kernel_thread, который отвечает за сброс грязного кэша на носитель.

Ядро проверяет колличество грязного кэша при выполнении операции write(), если файл не замаплен (mmap()). Если же файл замаплен, то для учёта грязного кэша, первоначально зануляется бит w, разрешающий писать, для замапленной страницы в таблице страниц, на которую указывает регистр cr3. Таким образом, страничка мапится только для чтение, и при попытке записи в эту страницу произойдёт исключение, которое уже можно обработать, пометив страницу грязной и разрешив в неё запись.

При начале записи на носитель выставляется флаг PG_writeback и очищаается бит w соответствующей записи в таблице страниц.

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

Сброс анонимной памяти

С анонимной памятью всё несколько сложнее, чем с файлами. В случае с файлами все страничные кадры подвязаны к inode, анонимная память связана с процессами, которые ей владеют, причём одним и тем же анонимным страничным кадром могут владеть несколько процессов, например, при шаренной памяти.

Для решения этой задачи в Linux есть функциональность - обратное отображение(rmap).

Во-первых, анонимная память, от файлов отличается, младшим битом в поле mapping дескриптора страниц struct page. Если младший бит равен нулю, то это файл и поле указывает на inode->address_space, если равен единице, то это анонимная память и указывает на anon_vma.

Во-вторых, поле _mapcount в структуре page содержит количество тех, кто имеет ссылку на данных страничный кадр.

!TODO: разобраться получше и переписать.

В итоге, когда создаётся анонимная область памяти, ядро создаёт структуру anon_vma, к которой сцепляются anon_vma_chain, содержащие VMA, ссылающиеся на одинаковые page.

struct anon_vma {
    struct anon_vma *root;      /* Root of this anon_vma tree */
    struct rw_semaphore rwsem;  /* W: modification, R: walking the list */
    atomic_t refcount;
    unsigned degree;
    struct anon_vma *parent;    /* Parent of this anon_vma */
    struct rb_root rb_root; /* Interval tree of private "related" vmas */
};

struct anon_vma_chain {
    struct vm_area_struct *vma;
    struct anon_vma *anon_vma;
    struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */
    struct rb_node rb;          /* locked by anon_vma->rwsem */
    unsigned long rb_subtree_last;
#ifdef CONFIG_DEBUG_VM_RB
    unsigned long cached_vma_start, cached_vma_last;
#endif
};