Переваги і недоліки системи двійників



Цей підхід за продуктивністю випереджає інші алгоритми динамічного розподілу, він особливо ефективний для великих масивів пам’яті. Головним його недоліком є значна внутрішня фрагментація (як можна помітити, у наведеному прикладі було виділено на 86 Кбайт пам’яті більше, ніж потрібно), тому для розподілу блоків малого розміру частіше використовують інші підходи, зокрема розглянуті раніше методи послідовного пошуку або ізольованих списків вільних блоків.

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

Опишемо реалізацію вивільнення пам’яті, зайнятої об’єктами. Основна проблема, що виникає при цьому, пов’язана із необхідністю відслідковувати використання кожного об’єкта у програмі. Це потрібно для того щоб визначити, коли можна вивільнити пам’ять, яку займає об’єкт. Для реалізації такого відстеження використовують два основні підходи: підрахунок посилань (reference counting) і збирання сміття (garbage collection).

Підрахунок посилань

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

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

Збирання сміття

Тут розглянемо збирання сміття із маркуванням і очищенням (mark and sweep garbage collection). Така технологія дає змогу знайти всю пам’ять, яку може адресувати процес, починаючи із деяких заданих покажчиків. Усю іншу пам’ять при цьому вважають недосяжною, і вона може бути вивільнена. Алгоритм такого збирання сміття виконують у два етапи.

  1. На етапі маркування виділяють спеціальні адреси пам’яті, які називають кореневими адресами. Як джерела для таких адрес звичайно використовують усі глобальні та локальні змінні процесу, які є покажчиками. Після цього всі об’єкти, які містяться у пам’яті за цими кореневими адресами, спеціальним чином позначаються. Далі аналогічно позначають всю пам’ять, яка може бути адресована покажчиками, що містяться у позначених раніше об’єктах, і т. д.
  2. На етапі збирання сміття деякий фоновий процес (збирач сміття) проходить всіма об’єктами і вивільняє пам’ять з-під тих із них, які не були позначені на етапі маркування.

Реалізація динамічного керування пам’яттю в Linux

У цьому розділі опишемо особливості динамічного керування пам’яттю Linux.

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

Розподіл фізичної пам’яті ядром

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

Розподіл об’єктів ядром

Алгоритм двійників не підходить для розподілу блоків пам’яті довільного розміру, оскільки мінімальний обсяг пам’яті, який він може виділити або вивільнити, становить один фрейм (4 Кбайт), що спричиняє значну внутрішню фрагментацію. Для розподілу пам’яті під окремі об’єкти застосовують кусковий розподілювач (slab allocator) [5, 61, 74]. При його розробці намагаються організувати кешування часто використовуваних об’єктів ядра в ініціалізованому стані (оскільки більша частина часу під час розміщення об’єкта витрачається на його ініціалізацію, а не на саме виділення пам’яті), а також виділення пам’яті блоками малого розміру без внутрішньої фрагментації, властивої алгоритму системи двійників.

Кеші об’єктів та їхні види

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

Розрізняють два види кешів об’єктів.

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

Кеші загального призначення, які використовують для зберігання блоків пам’яті довільного призначення конкретного розміру. Є кеші для блоків розміром від 25 = 32 біт до 217 = 131072 біт, їх називають size–N (N – розмір блоку кеша у байтах, наприклад, size–128).

Кускові блоки

Пам’ять для кеша виділяють у вигляді кускових блоків (slabs). Кусковий блок складається із одного або кількох неперервно розташованих фреймів пам’яті, розділених на фрагменти пам’яті (chunks) одного розміру, які містять об’єкти (рис. 10.6). Розмір фрагмента пам’яті залежить від типу кеша, для якого виділяють кусковий блок (він дорівнює розміру об’єкта, екземпляри якого потрібно розподіляти за допомогою цього кеша). Використання таких блоків для розподілу пам’яті знижує як зовнішню, так і внутрішню фрагментацію.

Рис. 10.6. Структура кускового блоку розподілювача об’єктів Linux

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

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

Із поточним станом кешів об’єктів можна ознайомитися, виконавши команду

$ cat /proc/slabinfo

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

vm_area_struct           1060 1180 64   20   20   1

size-8192              З     З     8192 3     3     2

size-32                              5607 6215 32   50   55   1


Дата добавления: 2018-04-05; просмотров: 364; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!