Як Linux працює з пам’яттю, Linux, Операційні системи, статті

Stanislav Ievlev

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

Виникла думка звернутися до минулого, щоб, по крайней мере, розібратися, як все це розвивалося (з версії 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 page **pprev_hash;
struct buffer_head * buffers;
} mem_map_t;

Уже тут спостерігається певна навороченность. Безліч всяких посилань. Ви не повірите, але всі вони використовуються. Одна сторінка може знаходитися в різних списках, наприклад і в списку сторінок у сторінковому кеші і в списку сторінок стосовних до відображеного в пам’ять файлу (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. І, нарешті, до реального адресою, вказує на початок сторінки додають зсув від її початку. Хороший приклад подібної процедури можна подивитися у функції ядра partial_clear:

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; / * кількість сторінок для свопінгу при наступному проході * /
unsigned long swap_address;
/* * Це архітектурно-залежний покажчик. Переносима частина Linux нічого не знає про сегментах. * /
void * segments;
};

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

При будь-якому відкритті файлу, він відразу ж відображається в пам’ять (Точніше його частина, дочитавши до розміру сторінки. Наприклад, для Intel при читанні 10 байт будуть прочитані 4096) і додається в сторінковий кеш. Реальний же запит на відображення файлу тільки повертає адреса на вже кешовані сторінки.

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

struct vm_area_struct { struct mm_struct * vm_mm; / * параметри області віртуальної пам’яті * /
unsigned long vm_start;
unsigned long vm_end; / * Пов’язується список областей завдання відсортований за адресами * /
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned short vm_flags; / * AVL-дерево областей, для прискореного пошуку, сортування за адресами * /
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right; / * Для областей використовуваних при відображенні файлів або при роботі з розділяється пам’яті, інакше ця частина структури не використовується * /
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; / * Колективна пам’ять * /
};
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 розвивається дуже швидко і не завжди передбачувано.

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

Удачи.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*