Рівень слябової розподільника пам’яті

Виділення і звільнення структур даних – це одна з найбільш частих операцій, які виконуються в будь-якому ядрі Для того щоб полегшити процедуру частого виділення і звільнення даних при програмуванні, вводяться списки вільних ресурсів (free list) Список вільних ресурсів містить деякий набір вже виділених структур даних Коли коду необхідний новий екземпляр структури даних, він може взяти одну структуру зі списку вільних ресурсів, замість того щоб виділяти необхідний обсяг памяті і заповнювати його даними відповідної структури Пізніше, коли структура даних більше не потрібна, вона знову повертається в список вільних ресурсів, замість того щоб звільняти память У цьому сенсі список вільних ресурсів являє собою кеш обєктів, в якому зберігаються обєкти одного певного, часто використовуваного типу

Одна з найбільших проблем, повязаних зі списком вільних ресурсів в ядрі, це те, що над ними немає ніякого централізованого контролю Коли відчувається брак вільної памяті, немає ніякої можливості взаємодіяти між ядром і всіма списками вільних ресурсів, які в такій ситуації повинні зменшити розмір свого кеша, щоб звільнити память У ядрі немає ніякої інформації про випадково створених списках вільних ресурсів Для виправлення становища і для універсальності коду ядро ​​надає рівень слябової розподілу памяті (slab layer), який також називається просто слябової розподільником памяті (slab allocator) Рівень слябової розподілу памяті виконує функції загального рівня кешування структур даних

Концепції слябової розподілу памяті вперше були реалізовані в операційній системі SunOS 54 фірми Sun Microsystems . Для рівня кешування структур даних в операційній системі Linux використовується таку ж назву і схожі особливості реалізації

Рівень слябової розподілу памяті служить для досягнення наступних цілей

• Часто використовувані структури даних, швидше за все, будуть часто виділятися і звільнятися, тому їх слід кешувати

• Часті операції виділення і звільнення памяті можуть призвести до фрагментації памяті (до неможливості знайти великі ділянки фізично однорідної памяті) Для запобігання цьому, кешированниє списки вільних ресурсів організовані безперервним чином у фізичній памяті Так як структури даних повертаються знову в список вільних ресурсів, то в результаті ніякої фрагментації не виникає

• Список вільних ресурсів забезпечує поліпшену продуктивність при частих виділеннях і звільненнях обєктів, так як звільнені обєкти відразу ж готові для нового виділення

• Якщо розподільник памяті може використовувати додаткову інформацію, таку як розмір обєкта, розмір сторінки памяті і загальний розмір кешу, то зявляється можливість приймати в критичних ситуаціях більш інтелектуальні рішення

5 І пізніше документовані в роботі Bonwirk J The Slab Allocator: An Object-Caching Kernel Memory

Allocator,&quot  USENIX,  1994

у

• Якщо кеш організований, як повязаний з певним процесором (тобто для кожного процесора в системі використовується свій унікальний окремий кеш), то виділення і звільнення структур даних може виконуватися без використання SMP-блокіропок

• Якщо розподільник памяті розрахований на доступ до неоднорідною памяті (Non-Uniform Memory Access NUMA), то зявляється можливість виділення памяті з того ж вузла (node), на якому ця память запитується

• Збережені обєкти можуть бути пофарбовані ,щоб запобігти відображення різних обєктів на одні й ті ж рядки системного кеша

Рівень слябової розподілу памяті в ОС Linux був реалізований з урахуванням

зазначених принципів

Пристрій слябової розподільника памяті

Рівень слябової розподілу памяті ділить обєкти па групи, які називаються кешами (cache) Різні кеші використовуються для зберігання обєктів різних типів Для кожного типу обєктів існує свій унікальний кеш Наприклад, один кеш використовується для дескрипторів процесів (список вільних структур struc t task_struct), а інший-для індексів файлових систем (struc t inode) Цікаво, що інтерфейс krnalloc () побудований на базі рівня слябової розподілу памяті і використовує сімейство кешей загального призначення

Далі кеші діляться на сляби (Буквально slab – однорідна плитка, звідси і назва всієї підсистеми) Сляби займають одну або кілька фізично суміжних сторінок памяті Зазвичай сляб займає тільки одну сторінку памяті Кожен кеш може містити кілька слябів

Кожен сляб містить деяку кількість обєктів, які являють собою кешувального структури даних Кожен сляб може бути в одному з трьох станів: повний (full), частково заповнений (partial) і порожній (empty) Повний сляб не містить вільних обєктів (всі обєкти сляба виділені для використання) Частково заповнений сляб містить частину виділених і частина вільних обєктів Коли деяка частина ядра запрошувати новий обєкт, то цей запит задовольняється з частково заповненого сляба, якщо такий існує В іншому випадку запит виконується з пустого сляба Якщо порожнього сляба не існує, то він створюється Очевидно, що повний сляб ніколи не може задовольнити запит, оскільки в ньому немає вільних обєктів Така політика зменшує фрагментацію памяті

Як приклад розглянемо структури inode, які є поданням до оперативної памяті індексів дискових файлів (див розділ 12) Ці структури часто створюються і видаляються, тому є сенс управляти ними за допомогою слябової розподільника памяті Структури struc t inode виділяються з кешу inode_ cachep (така угода по привласненню назв є стандартом) Цей кеш складається з одного або більше слябів, швидше за все слябів багато, оскільки багато обєктів Кожен сляб містить максимально можливу кількість обєктів типу struc t inode Коли ядро ​​виділяє нову структуру типу struc t inode, повертається вказівник на вже виділену, але не використовувану структуру з частково заповненого сляба або, якщо такого немає, з пустого сляба Коли ядру більше не потрібен обєкт типу inode, то слябової розподільник памяті позначає цей обєкт

як вільний На рис 111 показана діаграма взаємин між кешами, слябами та обєктами

Обєкт

Сляб

Обєкт

Обєкт

Кеш

Обєкт

Сляб

Обєкт

Рис 111 Взаємовідносини між кешами, слябами та обєктами

Кожен кеш представляється структурою kmem_cache_s Ця структура містить три списки slab_full, slab_partia l і slab_empty, які зберігаються в структурі kmem_list3 Ці списки містять всі сляби, повязані з даним кешем Кожен сляб представлений наступною структурою struc t slab, яка є дескриптором сляба

struct slab {

struct list head list / * Список повних, частково заповнених або порожніх слябів * /

unsigned long colouroff / * Зміщення для фарбування слябів * /

void * s_mem / * Перший обєкт сляба * /

unsigned int inuse / * Кількість виділених обєктів * /

kmem_bufctl_t free / * Перший вільний обєкт, якщо є * /

}

Дескриптор сляба виділяється або за межами сляба, в кеші загального призначення, або на початку самого сляба Дескриптор зберігається всередині сляба, або якщо загальний розмір сляба досить малий, або якщо всередині самого сляба залишається достатньо місця, щоб розмістити дескриптор

Слябової розподільник створює нові сляби, викликаючи інтерфейс ядра нижнього рівня для виділення памяті __ get_free_page s () так

static void *kmem getpagss(kmem cache_t *cachep, int flags, int nodeid)

{

struct page *page

void *addr

int i

flags |= cachep-&gtgfpflags

if (likely(nodeid == -1)) {

addr =(void*)  get_free_pages(flags,cachep-&gtgfporder)

if (addr)

return NULL

page =virt_to_page(addr)

} else {

page = alloc_pages_node(nodeid, flags, cachep-&gtgfporder)

if (page)

return NULL

addr=page_address(page)

}

i = (1 &lt&lt cachep-&gtgfporder)

if (cachep-&gtflags &amp SLAB_RECLAIM_ACCOUNT)

atomic_add(i, &ampslab_reclaim_pages)

add_page_state(nr_slab, i)

while (i–) { SetPageSlab(page)

page++

}

return addr

}

Перший параметр цієї функції вказує на певний кеш, для якого потрібні нові сторінки памяті Другий параметр містить прапори, які віддаються у функцію __ get_free_page s () Слід звернути увагу на те, як значення цих прапорів обєднуються з іншими значеннями за допомогою логічної операції АБО Дана операція доповнює прапори значенням прапорів кеша, які використовуються за замовчуванням і які обовязково повинні бути присутніми п значенні параметра flags Кількість сторінок памяті – ціла ступінь двійки – зберігається в поле cachep-> gfporder Розглянута функція виглядає більш складною, ніж це може здатися спочатку, оскільки вона також розрахована на NUMA-системи (Non-Uniform Memory Access, системи з неоднорідним доступом до памяті) Якщо параметр nodei d на рівний -1, то робиться спроба виділити память з того ж вузла памяті, на якому виконується запит Таке рішення дозволяє отримати більш високу продуктивність для NUMA-систем Для таких систем звернення до памяті інших вузлів призводить до зниження продуктивності

Для освітніх цілей можна прибрати код, розрахований на NUMA-системи, і отримати більш простий варіант функції kraem_getpages () в наступному вигляді

static inline void * kmem_getpages(kmem_cache_t *cachep, unsigned long flags)

{

void *addr

flags |= cachep-&gtgfpflags

addr = (void*)  get_free_pages(flags, cachep-&gtgfporder)

return addr

}

Память звільняється за допомогою функції kmem_freepages (), яка викликає функцію free_page s () для звільнення необхідних сторінок кеша Звичайно, призначення рівня слябової розподілу – це утриматися від виділення і звільнення сторінок памяті Насправді слябової розподільник використовує функції виділення памяті тільки тоді, коли в даному кеші не доступний жоден частково заповнений або порожній сляб Функція звільнення памяті викликається тільки тоді, коли стає мало доступної памяті і система намагається звільнити память або коли кеш повністю ліквідується

Рівень слябової розподілу управляється за допомогою простого інтерфейсу, і це можна робити окремо для кожного кеша Інтерфейс дозволяє створювати або ліквідовувати нові кеші, а також виділяти або знищувати обєкти в цих кешах Всі механізми оптимізації управління кешами і слябів повністю управляються внутрішніми елементами рівня слябової розподілу памяті Після того як кеш створений, слябової розподільник памяті працює, як спеціалізована система створення обєктів певного типу

Інтерфейс слябової розподільника памяті

Новий кеш можна створити за допомогою виклику наступної функції

kmern_cache_t * kmem_cache_create (const char *name, size_t size, size_t offset, unsigned long flags,

void (*ctor) (void*, kmem_cache_t *,unsigned long), void (*dtor) (void*, kmem_cache_t *,unsigned long))

Перший параметр – це рядок, яка містить імя кеша Другий параметр – це розмір кожного елемента кеша Третій параметр – це зміщення першого обєкта в слябів Він потрібен для того, щоб забезпечити необхідне вирівнювання по кордонах сторінок памяті Зазвичай достатньо вказати значення, рівне нулю, яке відповідає вирівнюванню за замовчуванням Параметр flag s вказує опціональні параметри, які керують поведінкою кеша Він може дорівнювати нулю, що вимикає всі спеціальні особливості поведінки, або складатися з одного або більше значень, показаних нижче і обєднаних за допомогою логічної операції АБО

• SLAB_NO_REAP – цей прапор вказує, що кеш не повинен автоматично прибирати сміття (Тобто звільняти память, в якій зберігаються невикористовувані обєкти) при нестачі памяті в системі Зазвичай цей прапорне потрібно встановлювати, оскільки якщо цей прапор встановлений, то створений кеш може перешкодити нормальній роботі системи при нестачі памяті

• SLAB_HWCACHE_ALIGN – цей прапор вказує рівню слябової розподілу памяті, що розташування кожного обєкта в слябів повинно вирівнюватися по рядках процесорного кешу Це запобігає так зване помилкове розподіл, коли два або більше обєктів відображаються в одну і ту ж рядок системного кеша, незважаючи на те що вони знаходяться за різними адресами памяті При використанні цього прапора зростає продуктивність, але це робиться ціною збільшення займаної памяті, тому що суворе вирівнювання призводить до того, що частина памяті сляба не використовується Ступінь

збільшення займаної памяті залежить від розміру обєктів кеша і від того, яким чином відбувається їх виравнінаніе по відношенню до рядків системного кеша Для кешей, які часто використовуються в коді, критичному до продуктивності, буде правильним встановити цей прапор, в інших випадках слід подумати, чи варто це робити

• SLAB_MUST_HWCACHE_ALIGN Якщо включений режим налагодження, то може виявитися неможливим одночасна налагодження та вирівнювання становища обєктів по рядках системного кеша Цей прапор вказує рівню слябової розподілу памяті, що необхідно форсувати вирівнювання становища обєктів по рядках системного кеша У звичайній ситуації цей прапор вказувати необовязково, і досить використовувати попередній Установка цього прапора в режимі налагодження слябової розподільника памяті (за замовчуванням налагодження заборонена) може призвести до значного збільшення витрат памяті Тільки для обєктів, які критичні до вирівнюванню по рядках системного кеша, таких як дескриптори процесів, необхідно вказувати даний прапор

• SLABPOSON-цей прапор вказує на необхідність заповнення слябів відомим числовим значенням (а5а5а5а5) Ця операція називається отруєнням(poisoning)  і служить для відстеження доступу до неініціалізованої памяті

• SLAB_RED_ZONE – цей прапор вказує на необхідність виділення так званих червоних зон(red zone)  для полегшення детектування переповнень буфера

• SLAB_PANIC – цей прапор вказує на необхідність переведення ядра в стан паніки, якщо виділення памяті було невдалим Даний прапор корисний, якщо виділення памяті завжди має завершуватися успішно, як, наприклад, у разі створення кешу структур VMA (областей віртуальної памяті, див главу 14, Адресний простір процесу) при завантаженні системи

• SLAB_CACHE_DMA – цей прапор вказує рівню слябової розподілу, що всі сляби повинні виділятися в памяті, з якої можливі операції прямого доступу до памяті Це необхідно, коли обєкти використовуються в операціях ПДП і повинні знаходитися в зоні ZONE_DMA В іншому випадку ця можливість не потрібна і цей прапор не потрібно встановлювати

Два останніх параметра cto r і dto r – це конструктор і деструктор кеша відповідно Конструктор викликається, коли в кеш додаються нові сторінки памяті Деструктор викликається, коли з кешу видаляються сторінки памяті Якщо вказаний деструктор, то повинен бути зазначений і конструктор На практиці кеші ядра ОС Linux зазвичай не використовують функції конструктора і деструктора В якості цих параметрів можна вказувати значення NULL

У разі успішного виконання функція kmem_cache_creat e () повертає покажчик на створений кеш В іншому випадку повертається NULL Ця функція не може викликатися в контексті переривання, так як вона може переводити процес в стан очікування Для ліквідації кеша необхідно викликати наступну функцію

int kmem_cache_destroy(kmem_cache_t *cachep)

Ця функція ліквідує вказаний кеш Вона зазвичай викликається при вивантаженні модуля, який створює свій кеш З контексту переривання цю функцію викликати не можна, так як вона може переводити викликає процес в стан очікування Перед викликом цієї функції необхідно, щоб були виконані наступні дві умови

• Всі сляби кеша є порожніми Дійсно, якщо в якомусь сляби існує обєкт, який все ще використовується, то як можна ліквідувати кеш

• Ніхто не звертатиметься до кешу під час і особливо після виклику функції kmem_cache_destroy () Цю синхронізацію повинен забезпечити викликає код

У разі успішного виконання функція повертає нуль, в інших випадках повертається ненульове значення

Після того як кеш створений, з нього можна отримати обєкт шляхом виклику наступної функції

void * kmem_cache_alloc(kmem_cache_t *cachep, int flags)

Ця функція повертає покажчик на обєкт з кешу, на який вказує параметр cachep Якщо ні в одному з слябів немає вільних обєктів, то рівень слябової розподілу повинен отримати нові сторінки памяті за допомогою функції kmem_getpages (), значення параметра flag s передаєтьсявфункцію__get_free _ page s () Це ті самі прапори, які були розглянуті раніше Швидше за все, необхідно вказувати GFP_KERNEL або GFP_ATOMIC

Далі для видалення обєкта та повернення його в сляб, з якого він був виділений, необхідно використовувати наступну функцію

void kmem_cache_free(kmem_cache_t *cachep, void *objp)

Дана функція позначає обєкт, на який вказує параметр objp, як вільний

Приклад використання слябової розподільника памяті

Давайте розглянемо приклад з реального життя, повязаний з роботою зі структурами task_struc t (дескриптори процесів) Показаний нижче код в дещо більш складній формі наведено у файлі kernel / forkс

У ядрі визначена глобальна змінна, в якій зберігається покажчик на кеш обєктів task_struct:

kmem_cache_t *task_struct_cachep

Під час ініціалізації ядра, у функції forkini t (), цей кеш створюється таким чином

task_struct_cachep = kmem_cache_create(&quottask_struct&quot, sizeof(struct task_struct), ARCH_M1N_TASKALIGN, SLAB_PANIC,

NULL, NULL)

Даний виклик створює кеш з імям task_struct , Який призначений для зберігання обєктів тина struc t task_struct Обєкти створюються з початковим зміщенням в слябів, рівним ARCH_MIN_TASKALIGN байт, і положення всіх обєктів вирівнюється по межах рядків системного кеша, значення цього вирівнювання залежить від апаратної платформи Зазвичай значення вирівнювання задається для кожної апаратної платформи за допомогою визначення препроцесора LI_CACHE_BYTES, яке дорівнює розміру процесорного кешу першого рівня в байтах Конструктор і деструктор відсутні Слід звернути увагу, що повертається значення не перевіряється на рапенство NULL, оскільки зазначений прапор SLAB_PANIC У разі, коли при виділенні памяті сталася помилка, слябової розподільник памяті викличе функцію pani c () Якщо цей прапор не вказаний, то потрібно перевіряти повертається значення на рівність NULL, що сигналізує про помилку Прапор SLAB_PANIC тут використовується тому, що цей каш є необхідним для роботи системи (без дескрипторів процесів працювати якось не добре)

Кожен раз, коли процес викликає функцію for k (), повинен створюватися новий дескриптор процесу (згадайте главу 3, Управління процесами) Це виконується таким чином у функції dup_task_struc t (), яка викликається з функції do_for k ()

struct task_struct *tsk

tsk = kmem_cache_alloc(task struct_cachep, GFP_KERNEL)

if (tsk)

return NULL

Коли процес завершується, якщо немає породжених процесів, які очікують на завершення батьківського процесу, то дескриптор звільняється і повертається назад в кеш task_struct_cachep Ці дії виконуються у функції free_task_struc t (), як показано нижче (де параметр ts k вказує на видаляється дескриптор)

kmem_cache_free(task_struct_cachep, tsk)

Так як дескриптори процесів належать до основних компонентів ядра і завжди необхідні, то кеш task_struct_cache p ніколи не ліквідується Якби він ліквідовувався, то робити це необхідно було б таким чином

int err

err = kmem_cache_destroy (task_struct_cachep)

if (err)

/ * Помилка ліквідації кеша * /

Досить просто, чи не так Рівень слябопого розподілу памяті приховує всі низькорівневі операції, повязані з вирівнюванням, раскрашіпаніем, виділенням і звільненням памяті, зборкою сміття в разі нестачі памяті Колі часто необхідно створювати багато обєктів одного типу, то слід подумати про використання слябової кеша І вже точно не потрібно писати свою реалізацію списку вільних ресурсів

Джерело: Лав, Роберт Розробка ядра Linux, 2-е видання : Пер з англ – М: ТОВ «ІД Вільямс »2006 – 448 с : Ил – Парал тит англ

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


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

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

Ваш отзыв

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

*

*