Як Uniux працює з пам’яттю

Як же ядро працює з найдорожчим що в нього є, з пам’яттю. Перші спроби
розібратися з нальоту що і як ні до чого не привели. Не всі так просто
як хотілося б. Отовсюду стирчать кінці, начебто всі ясно, але як
зв’язати їх воєдино…

Виникла думка звернутися до минулого , щоб принаймні розібратися як усе
це розвивалося (версія 0.1). Це допомогло зрозуміти і сучасне ядро.
Надалі мова йтиме про ядра серії 2.2 про зміни в 2.4 буде повідомлено
особливо.

Не буду поглиблюватися в тонкості функціонування захищеного режиму
процесора про це написано цілі фоліанти. Подивимося тільки саму суть.

Отже, в овнове усього лежать сторінки пам’яті. У ядрі вони описуються
структурою mem_map_t.

typedef struct page {

/* these must be first (free area handling) */

struct page *next;

struct page *prev;

struct inode *inode;

unsigned long offset;

struct page *next_hash;

atomic_t count;

unsigned long flags; /* atomic flags, some possibly updated
asynchronously */

struct wait_queue *wait;

struct pa???????????????????????????????

Вже отут спостерігається наворороченность. Безліч усяких посилань. Усі
вони використовуються . Одна сторінка може знаходитися в різних списках
, наприклад і всписке сторінок у сторінковому кеше й у списку сторінок
стосовних до відображеного в пам’ять файлу (inode).У структурі
описывающей останній можна знайти і зворотне посилання,що дуже зручно.

Усі сторінки адресуються глобальним покажчиком mem_map

mem_map_t * mem_map

Адресація сторінок порисходит дуже хитро. Якщо раніш у структурі page
було окреме поле указывающее на фізичну адресу (map_nr), то тепер він
обчислюється

static inline unsigned long page_address(struct page * page)

{

return PAGE_OFFSET + PAGE_SIZE * (page — mem_map);

}

Вільні сторінки зберігаються в особливій структурі free_area

static struct free_area_struct free_area[NR_MEM_TYPES][NR_MEM_LISTS];

, де перше поле відповідає за тип області : Ядра, Користувача, DMA
і.т.д. И обробляються по дуже хитрому алгоритмі.

Сторінки поділяються на вільні безупинні обростити розміру 2 у ступені x
помноженої на розмір сторінки ( (2^x)*PAGE_SIZE ). Області одного
розміру лежать в одній області масиву.

….

|——

|Свободные Сторінки розміру PAGE_SIZE*4 —> список вільних областей

|——

|Свободные Сторінки розміру PAGE_SIZE*2 —> список вільних областей

|——

|Свободные Сторінки розміру PAGE_SIZE —> список вільних областей

|——

Виділяє сторінку функція get_free_pages(order). Вона виділяє сторінки
складові область розміру PAGE_SIZE*(2^order). Шукається область
відповідного чи розміру більше. Якщо є тільки область більшого розміру
то вона поділяється на трохи маленьких і береться потрібний шматок. Якщо
вільних сторінок недостатньо, то деякі будуть скинуті в область
підкачування і процес выделенения почнеться знову.

Повертає сторінку функція free_pages(struct page, order). Вивільняє
сторінки починаються з page розміру PAGE_SIZE*(2^order). Область
повертається в масив вільних обрастей у відповідну позицію і після цього
відбувається спроба об’єднати кілька областей для створення одного
більшого розміру.

Відсутність сторінки в пам’яті обрабатыватся ядром особливо. Сторінка чи
може взагалі отсутствовать чи знаходитися в області підкачування.

От власне і вся базова робота з реальними сторінками.Самий час згадати,
що процес працює все-каки з віртуальними адресами, а не з фізичними.
Перетворення відбувається за допомогою вичислений, використовуючи
таблиці дескрипторів, і каталоги таблиць. Linux підтримує 3 рівні
таблиць: каталог таблиць першого рівня (PGD — Page Table
Directory),каталог таблиць другого рівня (PMD — Medium Page Table
Diractory), і нарешті таблиця дескрипторів (PTE — Page Table Entry).
Реально конкректным процесором можуть підтримуватися не всі рівні, але
запас дозволяє підтримувати більше можливих архитектур (Intel має 2
рівні таблиць, а Alpha — цілих 3 ). Перетворення віртуальної адреси у
фізичний відбувається відповідно в 3 етапи. Береться покажчик PGD,
наявний у структурі кожен процес, що описує, перетвориться в покажчик
запису PMD, а останній перетвориться в покажчик у таблиці дескрипторів
PTE. І нарешті до реальної адреси, що вказує на початок сторінки додають
зсув від її початку.

page_dir = pgd_offset(vma->vm_mm, address);

if (pgd_none(*page_dir))

return;

if (pgd_bad(*page_dir)) {

printk(«bad page table directory entry %p:[%lx]\n», page_dir,
pgd_val(*page_dir));

pgd_clear(page_dir);

return;

}

page_middle = pmd_offset(page_dir, address);

if (pmd_none(*page_middle))

return;

if (pmd_bad(*page_middle)) {

printk(«bad page table directory entry %p:[%lx]\n», page_dir,
pgd_val(*page_dir));

pmd_clear(page_middle);

return;

}

page_table = pte_offset(page_middle, address);

Узагалі ж усі дані про використовуваний процесом пам’яті містяться в
структурі mm_struct

struct mm_struct {

struct vm_area_struct *mmap; /* Список відображених областей */

struct vm_area_struct *mmap_avl; /* Ті ж області але вже у виді дерева
для більш швидкого пошуку*/

struct vm_area_struct *mmap_cache; /* Остання знайдена область*/

pgd_t * pgd; /*Каталог таблиць*/

atomic_t count;

int map_count; /* Кількість областей*/

struct semaphore mmap_sem;

unsigned long context;

unsigned long start_code, end_code, start_data, end_data;

unsigned long start_brk, brk, start_stack;

unsigned long arg_start, arg_end, env_start, env_end;

unsigned long rss, total_vm, locked_vm;

unsigned long def_flags;

unsigned long cpu_vm_mask;

unsigned long swap_cnt; /* number of pages to swap on next pass */

unsigned long swap_address;

/*

* This is an architecture-specific pointer: the portable

* part of Linux does not know about any segments.

*/

void * segments;

};

Відразу зауважуємо, що крім цілком зрозумілих покажчиків на початок
даних (start_code, end_code …) коду і стека є покажчики на дані
відображених файлів (mmap). Це треба сказати особливість Linux — тягти в
себе всі що тільки можна. Може бути це і добре, але з іншої сторони так
розбазарюватися пам’яттю (згадаємо ще буфера введення/висновку при
файловій системі, що теж будуть їсти всі нову пам’ять поки вона є) Даний
підхід може негативно відбитися на стабільності системи, адже для
запуску якогось життєво необхідного процесу може знадобитися час на
звільнення зайвих кешей. Простенька перевірка на утрату вільної пам’яті:
уведіть команду «cat /dev/mem >/image » і подивитеся скількох вільних
пам’ятей після цього залишилося. Якщо вам це не подобається, то звернете
погляд на функцію invalidate_inode_pages(* struct_inode), що звільняє
сторінковий кэш для даного файлу.

При будь-якім відкритті файлу, він відразу ж відображається в пам’ять і
додається в сторінковий кэш. Реальний же запит на відображення файлу
тільки повертає адреса на вже скешированные сторінки.

На рівні процесу робота може вестить як зі сторінками напямую, так і
через абстрактну структуру vm_area_struct

struct vm_area_struct {

struct mm_struct * vm_mm; /* VM area parameters */

unsigned long vm_start;

unsigned long vm_end;

/* linked list of VM areas per task, sorted by address */

struct vm_area_struct *vm_next;

pgprot_t vm_page_prot;

unsigned short vm_flags;

/* AVL tree of VM areas per task, sorted by address */

short vm_avl_height;

struct vm_area_struct * vm_avl_left;

struct vm_area_struct * vm_avl_right;

/* For areas with inode, the list inode->i_mmap, for shm areas,

* the list of attaches, otherwise unused.

*/

struct vm_area_struct *vm_next_share;

struct vm_area_struct **vm_pprev_share;

struct vm_operations_struct * vm_ops;

unsigned long vm_offset;

struct file * vm_file;

unsigned long vm_pte; /* shared mem */

};

struct vm_operations_struct {

void (*open)(struct vm_area_struct * area);

void (*close)(struct vm_area_struct * area);

void (*unmap)(struct vm_area_struct *area, unsigned long, size_t);

void (*protect)(struct vm_area_struct *area, unsigned long, size_t,
unsigned int newprot);

int (*sync)(struct vm_area_struct *area, unsigned long, size_t, unsigned
int flags);

void (*advise)(struct vm_area_struct *area, unsigned long, size_t,
unsigned int advise);

unsigned long (*nopage)(struct vm_area_struct * area, unsigned long
address, int write_access);

unsigned long (*wppage)(struct vm_area_struct * area, unsigned long
address,

unsigned long page);

int (*swapout)(struct vm_area_struct *, struct page *);

pte_t (*swapin)(struct vm_area_struct *, unsigned long, unsigned long);

};

Ідея даної структури виникла з ідеї віртуальної файлової системи, тому
всі операції над віртуальними областями абстрактні і можуть бути
специфічними для різних типів пам’яті, наприклад при відображенні файлів
операції читання одні а при відображенні пам’яті (через файл /dev/mem )
зовсім інші. Спочатку vm_area_struct з’явилася для забезпечення
нестатків відображення, але поступово поширюється і на інші області.

Що робити, коли потрібно одержати нову область пам’яті. Є цілих 3
способи.

1. Уже знайомий get_free_page()

2. kmalloc — Простенька (по можливостях, але аж ніяк не коду) процедура
з великими обмеженнями по виділенню нових областей і по їхньому розмірі.

3. vmalloc — Могутня процедура, що працює з віртуальною пам’яттю, може
виділяти великі обсяги пам’яті.

З кожної з двох процедур у ядрі зв’язані ще за списком вільних/зайнятих
областей, що ще більше ускладнює розуміння роботи з пам’яттю. (vmlist
для vmalloc, kmem_cash для kmalloc)

Що ж у 2.4.

Додано підтримку нової архітектури пам’яті NUMA. У противагу класичної
UMA пам’ять поділяється на зони з різним часом доступу до кожної з них .
Це дуже корисно і для кластерных рішень. У зв’язку з цим з’явилися нові
обгортки на функції і знайти суть стало ще складніше. З’явилася також
підтримка пам’яті до 64Гб.

Раніш для усіх файлових систем був один generic_file_read і
generic_file_mmap у зв’язку з тотальним засмоктуванням усього підряд у
пам’ять при читанні (розходження робилися вже тільки на рівні
inode->readpage). Тепер з’явився і generic_file_write. У загальному ще
пари таких generic і прощай віртуальна файлова система.

Але подивимося — побачимо. Адже Linux розвивається дуже швидко і не
завжди передбачувано.

Похожие записи