Наша совместная команда Banwar.org

Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

Дослідження моделі пам'яті Linux

  1. Дослідження моделі пам'яті Linux Перший крок до освоєння структури Linux Розуміння моделей пам'яті,...
  2. Малюнок 1. Два модуля перетворять адресний простір
  3. Загальна модель модуля управління сегментами
  4. Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів
  5. Малюнок 3. Отримання лінійного адреси з логічного
  6. Модуль управління сегментами в Linux
  7. Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес
  8. обчислення TASKS
  9. Огляд моделі сторінкової організації
  10. Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах
  11. Поля, що використовуються при розбитті сторінок
  12. Малюнок 6. Адресні поля при розбитті на сторінки
  13. Розширене розбиття на сторінки
  14. Модель розбиття на сторінки в Linux
  15. Малюнок 7. Три рівня розбиття на сторінки
  16. Малюнок 8. Лінійні адреси мають різні розміри
  17. Як дозволяється розбиття на сторінки
  18. Зона фізичної пам'яті
  19. Малюнок 9. Взаємовідносини між вузлом, зоною і сторінковим фреймом
  20. Висновок
  21. Ресурси для скачування
  22. Дослідження моделі пам'яті Linux
  23. Архітектура пам'яті x86
  24. Малюнок 1. Два модуля перетворять адресний простір
  25. Загальна модель модуля управління сегментами
  26. Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів
  27. Малюнок 3. Отримання лінійного адреси з логічного
  28. Модуль управління сегментами в Linux
  29. Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес
  30. обчислення TASKS
  31. Огляд моделі сторінкової організації
  32. Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах
  33. Поля, що використовуються при розбитті сторінок
  34. Малюнок 6. Адресні поля при розбитті на сторінки
  35. Розширене розбиття на сторінки
  36. Дослідження моделі пам'яті Linux
  37. Архітектура пам'яті x86
  38. Малюнок 1. Два модуля перетворять адресний простір
  39. Загальна модель модуля управління сегментами
  40. Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів
  41. Малюнок 3. Отримання лінійного адреси з логічного
  42. Модуль управління сегментами в Linux
  43. Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес
  44. обчислення TASKS
  45. Огляд моделі сторінкової організації
  46. Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах
  47. Поля, що використовуються при розбитті сторінок
  48. Малюнок 6. Адресні поля при розбитті на сторінки
  49. Розширене розбиття на сторінки
  50. Дослідження моделі пам'яті Linux
  51. Архітектура пам'яті x86
  52. Малюнок 1. Два модуля перетворять адресний простір
  53. Загальна модель модуля управління сегментами
  54. Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів
  55. Малюнок 3. Отримання лінійного адреси з логічного
  56. Модуль управління сегментами в Linux
  57. Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес
  58. обчислення TASKS
  59. Огляд моделі сторінкової організації
  60. Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах
  61. Поля, що використовуються при розбитті сторінок
  62. Малюнок 6. Адресні поля при розбитті на сторінки
  63. Розширене розбиття на сторінки
  64. Дослідження моделі пам'яті Linux
  65. Архітектура пам'яті x86
  66. Малюнок 1. Два модуля перетворять адресний простір
  67. Загальна модель модуля управління сегментами
  68. Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів
  69. Малюнок 3. Отримання лінійного адреси з логічного
  70. Модуль управління сегментами в Linux
  71. Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес
  72. обчислення TASKS
  73. Огляд моделі сторінкової організації
  74. Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах
  75. Поля, що використовуються при розбитті сторінок
  76. Малюнок 6. Адресні поля при розбитті на сторінки
  77. Розширене розбиття на сторінки
  78. Модель розбиття на сторінки в Linux
  79. Малюнок 7. Три рівня розбиття на сторінки
  80. Малюнок 8. Лінійні адреси мають різні розміри
  81. Як дозволяється розбиття на сторінки
  82. Зона фізичної пам'яті
  83. Малюнок 9. Взаємовідносини між вузлом, зоною і сторінковим фреймом
  84. Висновок
  85. Ресурси для скачування

Дослідження моделі пам'яті Linux

Перший крок до освоєння структури Linux

Розуміння моделей пам'яті, використовуваних в Linux, - це перший крок до освоєння на більш високому рівні структури операційної системи Linux і її реалізації. У цій статті моделі пам'яті Linux і управління пам'яттю розглядаються на ознайомчому рівні.

Операційна система Linux використовує монолітний підхід, при якому визначається набір примітивів або системних викликів для реалізації служб операційної системи, таких як управління процесами, паралельна робота і управління пам'яттю, в декількох модулях, які працюють в режимі супервізора. І хоча Linux з метою сумісності підтримує модель модуля управління сегментами (segment control unit) як символічне уявлення, вона використовує цю модель на мінімальному рівні.

Основними завданнями управління пам'яттю є:

  • Управління віртуальною пам'яттю - логічний рівень між запитами пам'яті в додатках і фізичної пам'яттю.
  • Управління фізичної пам'яттю.
  • Управління віртуальною пам'яттю на рівні ядра (модуль ядра, що займається розподілом пам'яті - компонент, який намагається задовольнити запити до пам'яті). Запит може бути виконаний з ядра або з програми користувача.
  • Управління віртуальним адресним простором.
  • Свопінг і кешування.

Дана стаття покликана допомогти вам в освоєнні внутрішнього устрою Linux з точки зору управління пам'яттю операційної системи. Розглядаються наступні теми:

  • Загальна модель модуля управління сегментами і її особливості в Linux.
  • Загальна модель управління сторінками пам'яті і її особливості в Linux.
  • Фізичні деталі області пам'яті.

У даній статті не розглядаються детально питання управління пам'яттю в ядрі Linux, але інформація по загальній моделі пам'яті і по роботі з нею повинна дати вам основу для подальшого вивчення. Увага в статті приділяється архітектурі x86, але ви можете використовувати ці матеріали і з іншими апаратними реалізаціями.

Архітектура пам'яті x86

У x86-архітектурі пам'ять розділяється на три типи адрес:

  • Логічна адреса - адреса розташування осередку пам'яті, який може бути (а може і ні) пов'язаний безпосередньо з фізичним розташуванням. Логічна адреса зазвичай використовується при запиті інформації з контролера.
  • Лінійна адреса (або лінійне адресний простір) - це пам'ять, адресація якої починається з 0. Кожен наступний байт адресується наступним послідовним номером (0, 1, 2, 3 і т.д.) до кінця пам'яті. Так адресують пам'ять більшість CPU неспроможний Intel-архітектури. У Intel®-архітектурах використовується сегментоване адресний простір, в якому пам'ять розділяється на сегменти розміром 64KB, а сегментний регістр завжди вказує на базовий адресу адресується сегмента. 32-бітний режим в цій архітектурі розглядається як лінійне адресний простір, але в ньому теж використовуються сегменти.
  • Фізична адреса - адреса, представлений битами фізичної адресної шини. Фізична адреса може відрізнятися від логічного; в цьому випадку модуль управління пам'яттю транслює логічний адресу в фізичний.

CPU використовує два модулі для перетворення логічного адреси в його фізичний еквівалент. Перший називається модулем сегментації (segmented unit), а другий - модулем поділу на сторінки (paging unit).

Малюнок 1. Два модуля перетворять адресний простір

Давайте досліджуємо модель модуля управління сегментами.

Загальна модель модуля управління сегментами

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

  • Базова адреса - адреса деякого місця фізичної пам'яті
  • Значення довжини, яке вказує довжину сегмента

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

Підведемо підсумки:

Модуль сегментації представляється як -> модель Сегмент: Зміщення також може бути представлений як -> Ідентифікатор сегмента: Зміщення

Кожен сегмент - це 16-бітове поле, зване ідентифікатором сегмента або селектором сегменту. Процесори сімейства x86 містять кілька програмованих регістрів, званих сегментними, які зберігають ці селектори сегментів. Такими регістрами є cs (сегмент коду), ds (сегмент даних) і ss (сегмент стека). Кожен ідентифікатор сегмента вказує сегмент, який представлений 64-бітовим (8-байтним) дескриптором сегмента. Ці дескриптори сегментів зберігаються в GDT (глобальна таблиця дескрипторів) і може бути також збережена в LDT (локальна таблиця дескрипторів).

Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів

Кожен раз, коли селектор сегмента завантажується в сегментний регістр, відповідний дескриптор сегмента завантажується з пам'яті в відповідний непрограмований регістр CPU. Кожен дескриптор сегмента має довжину 8 байт і представляє один сегмент в пам'яті. Вони зберігаються в таблицях LDT чи GDT. Запис елемента дескриптора сегмента містить і покажчик на перший байт в пов'язаному сегменті, представленому полем Base, і 20-бітове значення (поле Limit), яке представляє розмір сегмента в пам'яті.

Кілька інших полів містять спеціальні атрибути, такі як рівень привілеїв і тип сегмента (cs або ds). Тип сегмента представляється в четирехбітних поле Type.

Оскільки використовуються непрограмований регістр, до GDT або LDT не проводиться звернень до тих пір, поки не буде виконана трансляція логічного адреси в фізичний. Це прискорює роботу з пам'яттю.

Селектор сегмента містить:

  • 13-бітний індекс, який вказує на відповідний дескриптор сегмента, що міститься в GDT або LDT.
  • Прапор TI (Table Indicator), який вказує, в якій таблиці знаходиться дескриптор сегмента. Якщо прапор дорівнює 0, дескриптор знаходиться в GDT; якщо 1 - в LDT.
  • RPL (request privilege level) визначає поточний рівень привілеїв CPU при завантаженні відповідного селектора сегмента в регістр сегмента.

Оскільки дескриптор сегмента має довжину 8 байт, його відносна адреса в GDT або LDT обчислюється шляхом множення найзначиміших 13 бітів селектора сегмента на 8. Наприклад, якщо GDT зберігається за адресою 0x00020000, і поле Index, вказане селектором сегменту, дорівнює 2, тоді адреса відповідного дескриптора сегмента одно (2 * 8) + 0x00020000. Максимальна кількість дескрипторів сегмента, яке може бути збережене в GDT, так само (2 ^ 13 - 1) або 8191.

На малюнку 3 показано графічне представлення обчислення лінійного адреси з логічного.

Малюнок 3. Отримання лінійного адреси з логічного

А тепер подивимося, які відмінності в Linux?

Модуль управління сегментами в Linux

У Linux ця модель має невеликі відмінності. Я вже говорив про те, що Linux використовує модель сегментування обмежено (в основному для сумісності).

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

  • Управління пам'яттю простіше при використанні усіма процесами однакових значень сегментних регістрів (коли вони спільно використовують однаковий набір лінійних адрес).
  • Може бути досягнута сумісність з більшістю архітектур. Деякі RISC-процесори теж підтримують такий обмежений метод сегментування.

На малюнку 4 показані ці відмінності.

Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес

Дескриптори сегментів

Linux використовує такі дескриптори сегментів:

  • Сегмент коду ядра
  • Сегмент даних ядра
  • Сегмент коду користувача
  • Сегмент даних користувача
  • сегмент TSS
  • Сегмент LDT за замовчуванням

Розглянемо детально кожен з них.

Дескриптор сегмента коду ядра в GDT має наступні значення:

  • Base = 0x00000000
  • Limit = 0xffffffff (2 ^ 32 -1) = 4GB
  • G (прапор одиниці сегментування) = 1 для розміру сегмента, вираженого в сторінках
  • S = 1 для звичайного сегмента коду або даних
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • Значення DPL = 0 для режиму ядра

Лінійна адреса для цього сегмента дорівнює 4 GB. S = 1 і type = 0xa позначають сегмент коду. Селектор знаходиться в регістрі cs. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_CS.

Дескриптор сегмента даних ядра має аналогічні значення за винятком поля Type, значення якого встановлено в 2. Це вказує на те, що сегмент є сегментом даних і селектор зберігається в регістрі ds. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_DS.

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

  • Base = 0x00000000
  • Limit = 0xffffffff
  • G = 1
  • S = 1
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • DPL = 3 для призначеного для користувача режиму

Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_CS.

У дескрипторі сегмента даних користувача є тільки одна зміна в порівнянні з попереднім дескриптором - поле Type встановлено в 2 і визначає сегмент даних, які можна прочитати і записати. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_DS.

Крім цих дескрипторів сегментів GDT містить два додаткових дескриптора сегментів для кожного створеного процесу - для сегментів TSS і LDT.

Кожен дескриптор сегмента TSS вказує на окремий процес. TSS зберігає інформацію про апаратне контексті для кожного CPU, який бере участь в перемиканні контексту. Наприклад, при перемиканні режимів U-> K x86 CPU отримує адресу стека режиму ядра з TSS.

Кожен процес має свій власний TSS-дескриптор, що зберігається в GDT. Значення цього дескриптора такі:

  • Base = & tss (адреса поля TSS дескриптора відповідного процесу; наприклад, & tss_struct), який визначений в файлі schedule.h ядра Linux
  • Limit = 0xeb (сегмент TSS займає 236 байт)
  • Type = 9 або 11
  • DPL = 0. користувача режим не звертається до TSS. Прапор G очищений

Всі процеси спільно використовують сегмент LDT за замовчуванням. За замовчуванням він містить нульовий дескриптор сегмента. Цей дескриптор сегмента LDT за замовчуванням зберігається в GDT. Згенерований ядром Linux LDT займає 24 байта. За замовчуванням завжди присутні три записи:

LDT [0] = null LDT [1] = сегмент коду користувача LDT [2] = дескриптор сегмента даних / стека користувача

обчислення TASKS

Знання NR_TASKS (змінної, що визначає кількість одночасних процесів, підтримуваних в Linux. Її значення за замовчуванням в вихідному коді ядра одно 512, що дозволяє мати 256 одночасних підключень до одного екземпляра) необхідно для обчислення максимальної кількості дозволених записів в GDT.

Максимальна кількість дозволених в GDT записів може бути визначено за такою формулою:

Загальна кількість записів в GDT = 12 + 2 * NR_TASKS. Можлива кількість записів в GDT = 2 ^ 13 -1 = 8192.

З 8192 дескрипторів сегментів Linux використовує 6 дескрипторів сегментів, ще 4 додаткових для APM-функцій (функції розширеного управління живленням), а 4 записи в GDT залишаються невикористаними. Таким чином, кінцеве число можливих записів в GDT одно 8192 - 14, або 8180.

У будь-який момент часу ми не можемо мати більш ніж 8180 записів в GDT, отже:

2 * NR_TASKS = 8180
і NR_TASKS = 8180/2 = 4090

Чому 2 * NR_TASKS? Тому що для кожного створеного процесу завантажується не тільки TSS-дескриптор, який використовується для обслуговування перемикань контексту, а й LDT-дескриптор.

Це обмеження кількості процесів в x86-архітектурі відносилося до Linux 2.2, але після ядра 2.4 ця проблема була вирішена частково шляхом відмови від перемикання апаратного контексту (яке робило необхідним використання TSS), а також шляхом заміни його перемиканням процесу.

Тепер, давайте розглянемо сторінкову модель.

Огляд моделі сторінкової організації

Модуль управління сторінками перетворює лінійні адреси у фізичні (див. Малюнок 1). Набір лінійних адрес групується разом, утворюючи сторінки. Ці лінійні адреси безупинні за своєю природою - модуль управління сторінками відображає ці набори безперервної пам'яті в відповідний набір безперервних фізичних адрес, званих сторінковими фреймами. Зверніть увагу на те, що модуль управління сторінками представляє RAM розділеним на сторінкові фрейми фіксованого розміру.

З цієї причини розбиття на сторінки має такі переваги:

  • Права доступу, певні для сторінки, будуть дійсні для групи лінійних адрес, які формують сторінку
  • Довжина сторінки дорівнює довжині сторінкового фрейму

Структура даних, що відображає ці сторінки на сторінкові фрейми, називається, таблицею сторінок. Ці таблиці сторінок зберігаються в основній пам'яті і не започатковано ядром перед дозволом роботи модуля управління сторінками. На малюнку 5 показана таблиця сторінок.

Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах

Зверніть увагу на те, що набір адрес, що містяться в Page1, відповідає певному набору адрес, що містяться в Page Frame1.

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

Поля, що використовуються при розбитті сторінок

Наведемо опис полів, використовуваних для визначення сторінок в x86-архітектурі і допомагають керувати сторінками в Linux. Модуль розбиття на сторінки отримує лінійний адресу, який видає модуль сегментації. Цей лінійний адреса потім розбивається на наступні поля:

  • Directory (каталог) представлений десятьма MSB (Most Significant Bit - біт двійкового числа, що має найбільше значення; MSB іноді називається "найлівіший біт").
  • Table (таблиця) представлена десятьма середніми битами.
  • Offset (зсув) представлено дванадцятьма LSB (Least Significant Bit - біт двійкового числа, що представляє значення одиниці, тобто визначає, є число парних, або непарних; LSB іноді називають "самим правим бітом"; він аналогічний найменш значимою цифрі десяткового числа, що позначає одиниці і розташованої на самій правій позиції).

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

Малюнок 6. Адресні поля при розбитті на сторінки

На початку фізичну адресу Page Directory завантажується в регістр cr3. Поле directory в лінійному адресу визначає запис в Page Directory, що вказує на потрібну Page Table. Адреса в поле table визначає запис в Page Table, яка містить фізичну адресу сторінкового фрейму, що містить сторінку. Поле offset визначає відносну позицію в сторінковому фреймі. Оскільки довжина цього поля дорівнює 12 бітам, кожна сторінка містить 4 KB даних.

Отже, фізичну адресу обчислюється таким чином:

  1. cr3 + Page Directory (10 MSBs) = вказує table_base
  2. table_base + Page Table (10 середніх біт) = вказує page_base
  3. page_base + Offset = фізичну адресу (сторінковий фрейм)

Оскільки Page Directory і Page Table займають 10 біт, вони можуть адресувати максимум 1024 * 1024 KB, а Offset може адресувати до 2 ^ 12 (4096 байт). Таким чином, сумарна адресуються простір в Page Directory одно 1024 * 1024 * 4096 (2 ^ 32 осередків пам'яті, що дорівнює 4 GB). Тобто, на x86-архітектурі адресується простір обмежений 4 GB.

Розширене розбиття на сторінки

Розширене розбиття на сторінки виходить при видаленні таблиці перетворення Page Table; тоді поділ лінійного адреси здійснюється між Page Directory (10 MSB) і Offset (22 LSB).

22 біт LSB формують кордон в 4 MB для сторінкового фрейму (2 ^ 22). Розширене розбиття на сторінки існує разом зі звичайним розбивкою і включається для відображення великої кількості безперервних лінійних адрес у відповідні фізичні адреси. Операційна система видаляє Page Table і забезпечує, таким чином, розширене розбиття на сторінки. Це вирішується шляхом установки прапора PSE (page size extension).

36-бітний PSE розширює підтримку 36-бітного фізичної адреси зі сторінками розміром 4 MB і підтримує також 4-байтную запис сторінкових каталогів, надаючи, таким чином, простий механізм адресації фізичної пам'яті вище 4 GB і не вимагаючи великих змін дизайну операційних систем. Такий підхід має практичні обмеження, що стосуються потреби розбиття на сторінки.

Модель розбиття на сторінки в Linux

Розбиття на сторінки в Linux аналогічно звичайному розбиття, але x86-архітектура має трирівневий табличний механізм, що складається з:

  • Page Global Directory (pgd - глобальний каталог сторінок), абстрактний верхній рівень багаторівневих таблиць сторінок. Кожен рівень таблиці сторінок має справу з різними розмірами пам'яті - це глобальне каталог може працювати з областями розміром 4 MB. Кожен запис буде покажчиком на більш низкоуровневую таблицю каталогів меншого розміру, тобто pgd - це каталог таблиць сторінок. Коли код проходить цю структуру (це роблять деякі драйвери), то кажуть, що виконується "прохід" за таблицями сторінок.
  • Page Middle Directory (pmd - проміжний каталог сторінок), проміжний рівень таблиць сторінок. На x86-архітектурі pmd не представлені апаратно, але входить в pgd в коді ядра.
  • Page Table Entry (pte - запис таблиці сторінок), нижній рівень, який працює зі сторінками безпосередньо (шукайте PAGE_SIZE), є значенням, яке містить фізичну адресу сторінки разом з пов'язаними бітами, що вказують, наприклад, що запис коректна, і відповідна сторінка присутня в реальному пам'яті.

Ця трирівнева схема розбиття на сторінки також реалізована в Linux для підтримки областей пам'яті великих розмірів. Якщо підтримка великих областей пам'яті не потрібно, ви можете повернутися до дворівневої схемою, встановивши pmd в "1".

Ці рівні оптимізуються під час компіляції, дозволяючи другий і третій рівні (з використанням того ж самого набору коду) простим дозволом або забороною проміжного каталогу. 32-бітові процесори використовують pmd-розбиття, а 64-биті використовують pgd-розбиття.

Малюнок 7. Три рівня розбиття на сторінки

У 64-бітних процесорах:

  • 21 біт MSB не використовуються
  • 13 біт LSB представлені зміщенням сторінки
  • Решта 30 біт розділені на
    • 10 біт для Page Table
    • 10 біт для Page Global Directory
    • 10 біт для Page Middle Directory

Як видно з архітектури, для адресації фактично використовуються 43 біта. Тобто, на 64-бітних процесорах реально доступно 2 певною мірою 43 осередків віртуальної пам'яті.

Кожен процес має свій власний набір каталогів сторінок і таблиць сторінок. Для звернення до сторінкового кадру з реальними даними користувачів операційна система починає з завантаження (на x86-архітектурі) pgd в регістр cr3. Linux зберігає в TSS-сегменті вміст регістра cr3 і потім завантажує інше значення з TSS-сегмента в регістр cr3 кожен раз під час виконання нового процесу в CPU. В результаті модуль розбиття на сторінки посилається на коректний набір таблиць сторінок.

Кожен запис в таблиці pgd вказує на сторінковий фрейм, що містить масив записів pmd, які, в свою чергу, вказують на сторінковий фрейм, що містить pte, який, нарешті, вказує на сторінковий фрейм, що містить призначені для користувача дані. Якщо шукана сторінка знаходиться у файлі підкачки, в таблиці pte зберігається запис файлу підкачки, яка використовується (при помилку відсутності сторінки) при пошуку сторінкового фрейму для завантаження в пам'ять.

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

Малюнок 8. Лінійні адреси мають різні розміри

Резервні сторінкові фрейми

Linux резервує кілька станичних фреймів для коду ядра і структур даних. Ці сторінки ніколи не скидаються в файл підкачки на диск. Лінійні адреси з 0x0 до 0xc0000000 (PAGE_OFFSET) використовуються кодом користувача і кодом ядра. Адреси з PAGE_OFFSET до 0xffffffff використовуються кодом ядра.

Це означає, що з 4 GB тільки 3 GB доступні для призначеного для користувача програми.

Як дозволяється розбиття на сторінки

Механізм розбиття на сторінки, що використовується процесами Linux, має дві фази:

  • Під час початкового завантаження система встановлює таблицю сторінок для 8 MB фізичної пам'яті.
  • Потім друга фаза завершує відображення для залишилася фізичної пам'яті.

У фазі первинного завантаження за ініціалізацію розбиття на сторінки відповідає виклик startup_32 (). Ця функція реалізована в файлі arch / i386 / kernel / head.S. Відображення цих 8 MB відбувається з адреси вище PAGE_OFFSET. Ініціалізація починається зі статично визначеної під час компіляції масиву, званого swapper_pg_dir. Під час компіляції він розміщується за конкретною адресою (0x00101000).

Визначаються записи для двох таблиць, визначених у коді статично - pg0 і pg1. Розмір цих сторінкових фреймів за замовчуванням дорівнює 4 KB, якщо не встановлено біт розширення розміру сторінок (page size extension) (додаткова інформація по PSE знаходиться в розділі " Розширене розбиття на сторінки "). Тоді розмір кожної дорівнює 4 MB. Адреса даних, що вказується глобальним масивом, зберігається в регістрі cr3, що, я вважаю, є першим етапом установки модуля розбиття на сторінки для процесів Linux. Решта сторінкові запису встановлюються у другій фазі.

Друга фаза реалізована в методі paging_init ().

Відображення RAM виконується між PAGE_OFFSET і адресою, що представляє кордон 4 GB (0xFFFFFFFF) в 32-бітної x86-архітектурі. Це означає, що RAM розміром приблизно в 1 GB може бути відображена при завантаженні Linux, і це відбувається за замовчуванням. Однак, якщо встановити HIGHMEM_CONFIG, то фізична пам'ять розміром більше 1 GB може бути відображена для ядра - майте на увазі, що це тимчасовий захід. Це робиться викликом kmap ().

Зона фізичної пам'яті

Я вже показав вам, що ядро ​​Linux (на 32-бітної архітектури) ділить віртуальну пам'ять у відношенні 3: 1 - 3 GB віртуальної пам'яті для простору користувача та 1 GB для простору ядра. Код ядра і його структури даних повинні розміщуватися в цьому 1 GB адресного простору, але навіть ще більшим споживачем цього адресного простору є віртуальне відображення фізичної пам'яті.

Це відбувається тому, що ядро ​​не може маніпулювати пам'яттю, якщо вона не відображена в його адресний простір. Таким чином, максимальною кількістю фізичної пам'яті, яке може обробити ядро, є обсяг пам'яті, який міг би відобразитися в ВАП ядра мінус обсяг, необхідний для відображення самого коду ядра. В результаті Linux на основі x86-системи змогла б працювати з об'ємом менше 1 GB фізичної пам'яті. І це максимум.

Тому, для обслуговування великої кількості користувачів, для підтримки більшого обсягу пам'яті, для поліпшення продуктивності і для установки архітектурно-незалежного способу опису пам'яті модель пам'яті Linux повинна була вдосконалюватися. Для досягнення цих цілей в більш нової моделі кожному CPU був призначений свій банк пам'яті. Кожен банк називається вузлом; кожен вузол розділяється на зони. Зони (що представляють діапазони пам'яті) далі розбивалися на наступні типи:

  • ZONE_DMA (0-16 MB): Діапазон пам'яті, розташований в нижній області пам'яті, чого вимагають деякі пристрої ISA / PCI.
  • ZONE_NORMAL (16-896 MB): Діапазон пам'яті, який безпосередньо відображається ядром в верхні ділянки фізичної пам'яті. Всі операції ядра можуть виконуватися тільки з використанням цієї зони пам'яті; таким чином, це сама критична для продуктивності зона.
  • ZONE_HIGHMEM (896 MB і вище): Частина, що залишилася доступна пам'ять системи не відображається ядром.

Концепція вузла реалізована в ядрі за допомогою структури struct pglist_data. Зона описується структурою struct zone_struct. Фізичний сторінковий фрейм представлений структурою struct Page, і всі ці структури зберігаються в глобальному масиві структур struct mem_map, який розміщується на початку NORMAL_ZONE. Ці основні взаємини між вузлом, зоною і сторінковим фреймом показані на малюнку 9.

Малюнок 9. Взаємовідносини між вузлом, зоною і сторінковим фреймом

Зона верхній області пам'яті з'явилася в системі управління пам'яттю ядра тоді, коли були реалізовані розширення віртуальної пам'яті Pentium II (для доступу до 64 GB пам'яті засобами PAE - Physical Address Extension - на 32-бітних системах) і підтримка 4 GB фізичної пам'яті (знову ж таки, на 32-бітних системах). Ця концепція може бути застосована до платформ x86 і SPARC. Зазвичай ці 4 GB пам'яті роблять доступними відображення ZONE_HIGHMEM в ZONE_NORMAL допомогою kmap (). Зверніть увагу, будь ласка, на те, що не бажано мати більше 16 GB RAM на 32-бітної архітектури навіть при дозволеному PAE.

(PAE - розроблене Intel розширення адрес пам'яті, що дозволяє процесорам збільшити кількість бітів, які можуть бути використані для адресації фізичної пам'яті, з 32 до 36 через підтримку в операційній системі додатків, що використовують Address Windowing Extensions API.)

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

например:

  • Запит на призначену для користувача сторінку повинен бути спочатку заповнений з "нормальною" зони (ZONE_NORMAL);
  • якщо завершення невдало - з ZONE_HIGHMEM;
  • якщо знову невдало - з ZONE_DMA.

Список зон для такого розподілу складається з зон ZONE_NORMAL, ZONE_HIGHMEM і ZONE_DMA в зазначеному порядку. З іншого боку, запит DMA-сторінки може бути виконаний із зони DMA, тому список зон для таких запитів містить тільки зону DMA.

Висновок

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

Ресурси для скачування

Схожі тими

  • Оригінал статті " Explore the Linux memory model ".
  • У статті " Секрети управління пам'яттю "(DeveloperWorks, листопад 2004) надано огляд технологій управління пам'яттю, доступних в Linux, включаючи роботу системи управління пам'яттю, ручне, напівавтоматичне і автоматичне керування пам'яттю.
  • У статті " Порівняння ядер: Покращене керування пам'яттю в ядрі 2.6 "(DeveloperWorks, березень 2004) детально розглянуті нові технології для поліпшення використання великих обсягів пам'яті, зворотне відображення, зберігання сторінок таблиць у верхній пам'яті і підвищена стабільність системи управління пам'яттю.
  • У статті " Linux, за межами (x86) системи "(DeveloperWorks, травень 2005) розглядається, що робити з архітектурою, відмінними від x86.
  • У статті " Використання поділюваних об'єктів в Linux "(DeveloperWorks, травень 2004) розглядається, як оптимально використовувати пам'ять, що розділяється.
  • " Принципи системи управління пам'яттю в Linux "- набір заснованих на практичному досвіді заміток по роботі системи управління пам'яттю Linux в реальному житті.
  • " Драйвери пристроїв Linux ", Третє видання (O'Reilly, лютий 2005) - містить відмінну главу по системі управління пам'яттю і DMA .
  • книга " Освоєння ядра Linux ", Третє видання (O'Reilly, листопад 2005) - екскурсія по коду, формує ядро ​​всіх операційних систем Linux.
  • книга " Освоєння системи управління віртуальною пам'яттю Linux "(Prentice Hall, квітень 2004 року) - повне керівництво по віртуальної пам'яті Linux.
  • Використовуйте у вашому наступному Linux-проект пробне програмне забезпечення IBM , Доступне для завантаження з сайту developerWorks.

Підпішіть мене на ПОВІДОМЛЕННЯ до коментарів

Дослідження моделі пам'яті Linux

Перший крок до освоєння структури Linux

Розуміння моделей пам'яті, використовуваних в Linux, - це перший крок до освоєння на більш високому рівні структури операційної системи Linux і її реалізації. У цій статті моделі пам'яті Linux і управління пам'яттю розглядаються на ознайомчому рівні.

Операційна система Linux використовує монолітний підхід, при якому визначається набір примітивів або системних викликів для реалізації служб операційної системи, таких як управління процесами, паралельна робота і управління пам'яттю, в декількох модулях, які працюють в режимі супервізора. І хоча Linux з метою сумісності підтримує модель модуля управління сегментами (segment control unit) як символічне уявлення, вона використовує цю модель на мінімальному рівні.

Основними завданнями управління пам'яттю є:

  • Управління віртуальною пам'яттю - логічний рівень між запитами пам'яті в додатках і фізичної пам'яттю.
  • Управління фізичної пам'яттю.
  • Управління віртуальною пам'яттю на рівні ядра (модуль ядра, що займається розподілом пам'яті - компонент, який намагається задовольнити запити до пам'яті). Запит може бути виконаний з ядра або з програми користувача.
  • Управління віртуальним адресним простором.
  • Свопінг і кешування.

Дана стаття покликана допомогти вам в освоєнні внутрішнього устрою Linux з точки зору управління пам'яттю операційної системи. Розглядаються наступні теми:

  • Загальна модель модуля управління сегментами і її особливості в Linux.
  • Загальна модель управління сторінками пам'яті і її особливості в Linux.
  • Фізичні деталі області пам'яті.

У даній статті не розглядаються детально питання управління пам'яттю в ядрі Linux, але інформація по загальній моделі пам'яті і по роботі з нею повинна дати вам основу для подальшого вивчення. Увага в статті приділяється архітектурі x86, але ви можете використовувати ці матеріали і з іншими апаратними реалізаціями.

Архітектура пам'яті x86

У x86-архітектурі пам'ять розділяється на три типи адрес:

  • Логічна адреса - адреса розташування осередку пам'яті, який може бути (а може і ні) пов'язаний безпосередньо з фізичним розташуванням. Логічна адреса зазвичай використовується при запиті інформації з контролера.
  • Лінійна адреса (або лінійне адресний простір) - це пам'ять, адресація якої починається з 0. Кожен наступний байт адресується наступним послідовним номером (0, 1, 2, 3 і т.д.) до кінця пам'яті. Так адресують пам'ять більшість CPU неспроможний Intel-архітектури. У Intel®-архітектурах використовується сегментоване адресний простір, в якому пам'ять розділяється на сегменти розміром 64KB, а сегментний регістр завжди вказує на базовий адресу адресується сегмента. 32-бітний режим в цій архітектурі розглядається як лінійне адресний простір, але в ньому теж використовуються сегменти.
  • Фізична адреса - адреса, представлений битами фізичної адресної шини. Фізична адреса може відрізнятися від логічного; в цьому випадку модуль управління пам'яттю транслює логічний адресу в фізичний.

CPU використовує два модулі для перетворення логічного адреси в його фізичний еквівалент. Перший називається модулем сегментації (segmented unit), а другий - модулем поділу на сторінки (paging unit).

Малюнок 1. Два модуля перетворять адресний простір

Давайте досліджуємо модель модуля управління сегментами.

Загальна модель модуля управління сегментами

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

  • Базова адреса - адреса деякого місця фізичної пам'яті
  • Значення довжини, яке вказує довжину сегмента

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

Підведемо підсумки:

Модуль сегментації представляється як -> модель Сегмент: Зміщення також може бути представлений як -> Ідентифікатор сегмента: Зміщення

Кожен сегмент - це 16-бітове поле, зване ідентифікатором сегмента або селектором сегменту. Процесори сімейства x86 містять кілька програмованих регістрів, званих сегментними, які зберігають ці селектори сегментів. Такими регістрами є cs (сегмент коду), ds (сегмент даних) і ss (сегмент стека). Кожен ідентифікатор сегмента вказує сегмент, який представлений 64-бітовим (8-байтним) дескриптором сегмента. Ці дескриптори сегментів зберігаються в GDT (глобальна таблиця дескрипторів) і може бути також збережена в LDT (локальна таблиця дескрипторів).

Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів

Кожен раз, коли селектор сегмента завантажується в сегментний регістр, відповідний дескриптор сегмента завантажується з пам'яті в відповідний непрограмований регістр CPU. Кожен дескриптор сегмента має довжину 8 байт і представляє один сегмент в пам'яті. Вони зберігаються в таблицях LDT чи GDT. Запис елемента дескриптора сегмента містить і покажчик на перший байт в пов'язаному сегменті, представленому полем Base, і 20-бітове значення (поле Limit), яке представляє розмір сегмента в пам'яті.

Кілька інших полів містять спеціальні атрибути, такі як рівень привілеїв і тип сегмента (cs або ds). Тип сегмента представляється в четирехбітних поле Type.

Оскільки використовуються непрограмований регістр, до GDT або LDT не проводиться звернень до тих пір, поки не буде виконана трансляція логічного адреси в фізичний. Це прискорює роботу з пам'яттю.

Селектор сегмента містить:

  • 13-бітний індекс, який вказує на відповідний дескриптор сегмента, що міститься в GDT або LDT.
  • Прапор TI (Table Indicator), який вказує, в якій таблиці знаходиться дескриптор сегмента. Якщо прапор дорівнює 0, дескриптор знаходиться в GDT; якщо 1 - в LDT.
  • RPL (request privilege level) визначає поточний рівень привілеїв CPU при завантаженні відповідного селектора сегмента в регістр сегмента.

Оскільки дескриптор сегмента має довжину 8 байт, його відносна адреса в GDT або LDT обчислюється шляхом множення найзначиміших 13 бітів селектора сегмента на 8. Наприклад, якщо GDT зберігається за адресою 0x00020000, і поле Index, вказане селектором сегменту, дорівнює 2, тоді адреса відповідного дескриптора сегмента одно (2 * 8) + 0x00020000. Максимальна кількість дескрипторів сегмента, яке може бути збережене в GDT, так само (2 ^ 13 - 1) або 8191.

На малюнку 3 показано графічне представлення обчислення лінійного адреси з логічного.

Малюнок 3. Отримання лінійного адреси з логічного

А тепер подивимося, які відмінності в Linux?

Модуль управління сегментами в Linux

У Linux ця модель має невеликі відмінності. Я вже говорив про те, що Linux використовує модель сегментування обмежено (в основному для сумісності).

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

  • Управління пам'яттю простіше при використанні усіма процесами однакових значень сегментних регістрів (коли вони спільно використовують однаковий набір лінійних адрес).
  • Може бути досягнута сумісність з більшістю архітектур. Деякі RISC-процесори теж підтримують такий обмежений метод сегментування.

На малюнку 4 показані ці відмінності.

Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес

Дескриптори сегментів

Linux використовує такі дескриптори сегментів:

  • Сегмент коду ядра
  • Сегмент даних ядра
  • Сегмент коду користувача
  • Сегмент даних користувача
  • сегмент TSS
  • Сегмент LDT за замовчуванням

Розглянемо детально кожен з них.

Дескриптор сегмента коду ядра в GDT має наступні значення:

  • Base = 0x00000000
  • Limit = 0xffffffff (2 ^ 32 -1) = 4GB
  • G (прапор одиниці сегментування) = 1 для розміру сегмента, вираженого в сторінках
  • S = 1 для звичайного сегмента коду або даних
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • Значення DPL = 0 для режиму ядра

Лінійна адреса для цього сегмента дорівнює 4 GB. S = 1 і type = 0xa позначають сегмент коду. Селектор знаходиться в регістрі cs. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_CS.

Дескриптор сегмента даних ядра має аналогічні значення за винятком поля Type, значення якого встановлено в 2. Це вказує на те, що сегмент є сегментом даних і селектор зберігається в регістрі ds. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_DS.

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

  • Base = 0x00000000
  • Limit = 0xffffffff
  • G = 1
  • S = 1
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • DPL = 3 для призначеного для користувача режиму

Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_CS.

У дескрипторі сегмента даних користувача є тільки одна зміна в порівнянні з попереднім дескриптором - поле Type встановлено в 2 і визначає сегмент даних, які можна прочитати і записати. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_DS.

Крім цих дескрипторів сегментів GDT містить два додаткових дескриптора сегментів для кожного створеного процесу - для сегментів TSS і LDT.

Кожен дескриптор сегмента TSS вказує на окремий процес. TSS зберігає інформацію про апаратне контексті для кожного CPU, який бере участь в перемиканні контексту. Наприклад, при перемиканні режимів U-> K x86 CPU отримує адресу стека режиму ядра з TSS.

Кожен процес має свій власний TSS-дескриптор, що зберігається в GDT. Значення цього дескриптора такі:

  • Base = & tss (адреса поля TSS дескриптора відповідного процесу; наприклад, & tss_struct), який визначений в файлі schedule.h ядра Linux
  • Limit = 0xeb (сегмент TSS займає 236 байт)
  • Type = 9 або 11
  • DPL = 0. користувача режим не звертається до TSS. Прапор G очищений

Всі процеси спільно використовують сегмент LDT за замовчуванням. За замовчуванням він містить нульовий дескриптор сегмента. Цей дескриптор сегмента LDT за замовчуванням зберігається в GDT. Згенерований ядром Linux LDT займає 24 байта. За замовчуванням завжди присутні три записи:

LDT [0] = null LDT [1] = сегмент коду користувача LDT [2] = дескриптор сегмента даних / стека користувача

обчислення TASKS

Знання NR_TASKS (змінної, що визначає кількість одночасних процесів, підтримуваних в Linux. Її значення за замовчуванням в вихідному коді ядра одно 512, що дозволяє мати 256 одночасних підключень до одного екземпляра) необхідно для обчислення максимальної кількості дозволених записів в GDT.

Максимальна кількість дозволених в GDT записів може бути визначено за такою формулою:

Загальна кількість записів в GDT = 12 + 2 * NR_TASKS. Можлива кількість записів в GDT = 2 ^ 13 -1 = 8192.

З 8192 дескрипторів сегментів Linux використовує 6 дескрипторів сегментів, ще 4 додаткових для APM-функцій (функції розширеного управління живленням), а 4 записи в GDT залишаються невикористаними. Таким чином, кінцеве число можливих записів в GDT одно 8192 - 14, або 8180.

У будь-який момент часу ми не можемо мати більш ніж 8180 записів в GDT, отже:

2 * NR_TASKS = 8180
і NR_TASKS = 8180/2 = 4090

Чому 2 * NR_TASKS? Тому що для кожного створеного процесу завантажується не тільки TSS-дескриптор, який використовується для обслуговування перемикань контексту, а й LDT-дескриптор.

Це обмеження кількості процесів в x86-архітектурі відносилося до Linux 2.2, але після ядра 2.4 ця проблема була вирішена частково шляхом відмови від перемикання апаратного контексту (яке робило необхідним використання TSS), а також шляхом заміни його перемиканням процесу.

Тепер, давайте розглянемо сторінкову модель.

Огляд моделі сторінкової організації

Модуль управління сторінками перетворює лінійні адреси у фізичні (див. Малюнок 1). Набір лінійних адрес групується разом, утворюючи сторінки. Ці лінійні адреси безупинні за своєю природою - модуль управління сторінками відображає ці набори безперервної пам'яті в відповідний набір безперервних фізичних адрес, званих сторінковими фреймами. Зверніть увагу на те, що модуль управління сторінками представляє RAM розділеним на сторінкові фрейми фіксованого розміру.

З цієї причини розбиття на сторінки має такі переваги:

  • Права доступу, певні для сторінки, будуть дійсні для групи лінійних адрес, які формують сторінку
  • Довжина сторінки дорівнює довжині сторінкового фрейму

Структура даних, що відображає ці сторінки на сторінкові фрейми, називається, таблицею сторінок. Ці таблиці сторінок зберігаються в основній пам'яті і не започатковано ядром перед дозволом роботи модуля управління сторінками. На малюнку 5 показана таблиця сторінок.

Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах

Зверніть увагу на те, що набір адрес, що містяться в Page1, відповідає певному набору адрес, що містяться в Page Frame1.

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

Поля, що використовуються при розбитті сторінок

Наведемо опис полів, використовуваних для визначення сторінок в x86-архітектурі і допомагають керувати сторінками в Linux. Модуль розбиття на сторінки отримує лінійний адресу, який видає модуль сегментації. Цей лінійний адреса потім розбивається на наступні поля:

  • Directory (каталог) представлений десятьма MSB (Most Significant Bit - біт двійкового числа, що має найбільше значення; MSB іноді називається "найлівіший біт").
  • Table (таблиця) представлена десятьма середніми битами.
  • Offset (зсув) представлено дванадцятьма LSB (Least Significant Bit - біт двійкового числа, що представляє значення одиниці, тобто визначає, є число парних, або непарних; LSB іноді називають "самим правим бітом"; він аналогічний найменш значимою цифрі десяткового числа, що позначає одиниці і розташованої на самій правій позиції).

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

Малюнок 6. Адресні поля при розбитті на сторінки

На початку фізичну адресу Page Directory завантажується в регістр cr3. Поле directory в лінійному адресу визначає запис в Page Directory, що вказує на потрібну Page Table. Адреса в поле table визначає запис в Page Table, яка містить фізичну адресу сторінкового фрейму, що містить сторінку. Поле offset визначає відносну позицію в сторінковому фреймі. Оскільки довжина цього поля дорівнює 12 бітам, кожна сторінка містить 4 KB даних.

Отже, фізичну адресу обчислюється таким чином:

  1. cr3 + Page Directory (10 MSBs) = вказує table_base
  2. table_base + Page Table (10 середніх біт) = вказує page_base
  3. page_base + Offset = фізичну адресу (сторінковий фрейм)

Оскільки Page Directory і Page Table займають 10 біт, вони можуть адресувати максимум 1024 * 1024 KB, а Offset може адресувати до 2 ^ 12 (4096 байт). Таким чином, сумарна адресуються простір в Page Directory одно 1024 * 1024 * 4096 (2 ^ 32 осередків пам'яті, що дорівнює 4 GB). Тобто, на x86-архітектурі адресується простір обмежений 4 GB.

Розширене розбиття на сторінки

Розширене розбиття на сторінки виходить при видаленні таблиці перетворення Page Table; тоді поділ лінійного адреси здійснюється між Page Directory (10 MSB) і Offset (22 LSB).

22 біт LSB формують кордон в 4 MB для сторінкового фрейму (2 ^ 22). Розширене розбиття на сторінки існує разом зі звичайним розбивкою і включається для відображення великої кількості безперервних лінійних адрес у відповідні фізичні адреси. Операційна система видаляє Page Table і забезпечує, таким чином, розширене розбиття на сторінки. Це вирішується шляхом установки прапора PSE (page size extension).

Дослідження моделі пам'яті Linux

Перший крок до освоєння структури Linux

Розуміння моделей пам'яті, використовуваних в Linux, - це перший крок до освоєння на більш високому рівні структури операційної системи Linux і її реалізації. У цій статті моделі пам'яті Linux і управління пам'яттю розглядаються на ознайомчому рівні.

Операційна система Linux використовує монолітний підхід, при якому визначається набір примітивів або системних викликів для реалізації служб операційної системи, таких як управління процесами, паралельна робота і управління пам'яттю, в декількох модулях, які працюють в режимі супервізора. І хоча Linux з метою сумісності підтримує модель модуля управління сегментами (segment control unit) як символічне уявлення, вона використовує цю модель на мінімальному рівні.

Основними завданнями управління пам'яттю є:

  • Управління віртуальною пам'яттю - логічний рівень між запитами пам'яті в додатках і фізичної пам'яттю.
  • Управління фізичної пам'яттю.
  • Управління віртуальною пам'яттю на рівні ядра (модуль ядра, що займається розподілом пам'яті - компонент, який намагається задовольнити запити до пам'яті). Запит може бути виконаний з ядра або з програми користувача.
  • Управління віртуальним адресним простором.
  • Свопінг і кешування.

Дана стаття покликана допомогти вам в освоєнні внутрішнього устрою Linux з точки зору управління пам'яттю операційної системи. Розглядаються наступні теми:

  • Загальна модель модуля управління сегментами і її особливості в Linux.
  • Загальна модель управління сторінками пам'яті і її особливості в Linux.
  • Фізичні деталі області пам'яті.

У даній статті не розглядаються детально питання управління пам'яттю в ядрі Linux, але інформація по загальній моделі пам'яті і по роботі з нею повинна дати вам основу для подальшого вивчення. Увага в статті приділяється архітектурі x86, але ви можете використовувати ці матеріали і з іншими апаратними реалізаціями.

Архітектура пам'яті x86

У x86-архітектурі пам'ять розділяється на три типи адрес:

  • Логічна адреса - адреса розташування осередку пам'яті, який може бути (а може і ні) пов'язаний безпосередньо з фізичним розташуванням. Логічна адреса зазвичай використовується при запиті інформації з контролера.
  • Лінійна адреса (або лінійне адресний простір) - це пам'ять, адресація якої починається з 0. Кожен наступний байт адресується наступним послідовним номером (0, 1, 2, 3 і т.д.) до кінця пам'яті. Так адресують пам'ять більшість CPU неспроможний Intel-архітектури. У Intel®-архітектурах використовується сегментоване адресний простір, в якому пам'ять розділяється на сегменти розміром 64KB, а сегментний регістр завжди вказує на базовий адресу адресується сегмента. 32-бітний режим в цій архітектурі розглядається як лінійне адресний простір, але в ньому теж використовуються сегменти.
  • Фізична адреса - адреса, представлений битами фізичної адресної шини. Фізична адреса може відрізнятися від логічного; в цьому випадку модуль управління пам'яттю транслює логічний адресу в фізичний.

CPU використовує два модулі для перетворення логічного адреси в його фізичний еквівалент. Перший називається модулем сегментації (segmented unit), а другий - модулем поділу на сторінки (paging unit).

Малюнок 1. Два модуля перетворять адресний простір

Давайте досліджуємо модель модуля управління сегментами.

Загальна модель модуля управління сегментами

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

  • Базова адреса - адреса деякого місця фізичної пам'яті
  • Значення довжини, яке вказує довжину сегмента

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

Підведемо підсумки:

Модуль сегментації представляється як -> модель Сегмент: Зміщення також може бути представлений як -> Ідентифікатор сегмента: Зміщення

Кожен сегмент - це 16-бітове поле, зване ідентифікатором сегмента або селектором сегменту. Процесори сімейства x86 містять кілька програмованих регістрів, званих сегментними, які зберігають ці селектори сегментів. Такими регістрами є cs (сегмент коду), ds (сегмент даних) і ss (сегмент стека). Кожен ідентифікатор сегмента вказує сегмент, який представлений 64-бітовим (8-байтним) дескриптором сегмента. Ці дескриптори сегментів зберігаються в GDT (глобальна таблиця дескрипторів) і може бути також збережена в LDT (локальна таблиця дескрипторів).

Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів

Кожен раз, коли селектор сегмента завантажується в сегментний регістр, відповідний дескриптор сегмента завантажується з пам'яті в відповідний непрограмований регістр CPU. Кожен дескриптор сегмента має довжину 8 байт і представляє один сегмент в пам'яті. Вони зберігаються в таблицях LDT чи GDT. Запис елемента дескриптора сегмента містить і покажчик на перший байт в пов'язаному сегменті, представленому полем Base, і 20-бітове значення (поле Limit), яке представляє розмір сегмента в пам'яті.

Кілька інших полів містять спеціальні атрибути, такі як рівень привілеїв і тип сегмента (cs або ds). Тип сегмента представляється в четирехбітних поле Type.

Оскільки використовуються непрограмований регістр, до GDT або LDT не проводиться звернень до тих пір, поки не буде виконана трансляція логічного адреси в фізичний. Це прискорює роботу з пам'яттю.

Селектор сегмента містить:

  • 13-бітний індекс, який вказує на відповідний дескриптор сегмента, що міститься в GDT або LDT.
  • Прапор TI (Table Indicator), який вказує, в якій таблиці знаходиться дескриптор сегмента. Якщо прапор дорівнює 0, дескриптор знаходиться в GDT; якщо 1 - в LDT.
  • RPL (request privilege level) визначає поточний рівень привілеїв CPU при завантаженні відповідного селектора сегмента в регістр сегмента.

Оскільки дескриптор сегмента має довжину 8 байт, його відносна адреса в GDT або LDT обчислюється шляхом множення найзначиміших 13 бітів селектора сегмента на 8. Наприклад, якщо GDT зберігається за адресою 0x00020000, і поле Index, вказане селектором сегменту, дорівнює 2, тоді адреса відповідного дескриптора сегмента одно (2 * 8) + 0x00020000. Максимальна кількість дескрипторів сегмента, яке може бути збережене в GDT, так само (2 ^ 13 - 1) або 8191.

На малюнку 3 показано графічне представлення обчислення лінійного адреси з логічного.

Малюнок 3. Отримання лінійного адреси з логічного

А тепер подивимося, які відмінності в Linux?

Модуль управління сегментами в Linux

У Linux ця модель має невеликі відмінності. Я вже говорив про те, що Linux використовує модель сегментування обмежено (в основному для сумісності).

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

  • Управління пам'яттю простіше при використанні усіма процесами однакових значень сегментних регістрів (коли вони спільно використовують однаковий набір лінійних адрес).
  • Може бути досягнута сумісність з більшістю архітектур. Деякі RISC-процесори теж підтримують такий обмежений метод сегментування.

На малюнку 4 показані ці відмінності.

Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес

Дескриптори сегментів

Linux використовує такі дескриптори сегментів:

  • Сегмент коду ядра
  • Сегмент даних ядра
  • Сегмент коду користувача
  • Сегмент даних користувача
  • сегмент TSS
  • Сегмент LDT за замовчуванням

Розглянемо детально кожен з них.

Дескриптор сегмента коду ядра в GDT має наступні значення:

  • Base = 0x00000000
  • Limit = 0xffffffff (2 ^ 32 -1) = 4GB
  • G (прапор одиниці сегментування) = 1 для розміру сегмента, вираженого в сторінках
  • S = 1 для звичайного сегмента коду або даних
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • Значення DPL = 0 для режиму ядра

Лінійна адреса для цього сегмента дорівнює 4 GB. S = 1 і type = 0xa позначають сегмент коду. Селектор знаходиться в регістрі cs. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_CS.

Дескриптор сегмента даних ядра має аналогічні значення за винятком поля Type, значення якого встановлено в 2. Це вказує на те, що сегмент є сегментом даних і селектор зберігається в регістрі ds. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_DS.

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

  • Base = 0x00000000
  • Limit = 0xffffffff
  • G = 1
  • S = 1
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • DPL = 3 для призначеного для користувача режиму

Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_CS.

У дескрипторі сегмента даних користувача є тільки одна зміна в порівнянні з попереднім дескриптором - поле Type встановлено в 2 і визначає сегмент даних, які можна прочитати і записати. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_DS.

Крім цих дескрипторів сегментів GDT містить два додаткових дескриптора сегментів для кожного створеного процесу - для сегментів TSS і LDT.

Кожен дескриптор сегмента TSS вказує на окремий процес. TSS зберігає інформацію про апаратне контексті для кожного CPU, який бере участь в перемиканні контексту. Наприклад, при перемиканні режимів U-> K x86 CPU отримує адресу стека режиму ядра з TSS.

Кожен процес має свій власний TSS-дескриптор, що зберігається в GDT. Значення цього дескриптора такі:

  • Base = & tss (адреса поля TSS дескриптора відповідного процесу; наприклад, & tss_struct), який визначений в файлі schedule.h ядра Linux
  • Limit = 0xeb (сегмент TSS займає 236 байт)
  • Type = 9 або 11
  • DPL = 0. користувача режим не звертається до TSS. Прапор G очищений

Всі процеси спільно використовують сегмент LDT за замовчуванням. За замовчуванням він містить нульовий дескриптор сегмента. Цей дескриптор сегмента LDT за замовчуванням зберігається в GDT. Згенерований ядром Linux LDT займає 24 байта. За замовчуванням завжди присутні три записи:

LDT [0] = null LDT [1] = сегмент коду користувача LDT [2] = дескриптор сегмента даних / стека користувача

обчислення TASKS

Знання NR_TASKS (змінної, що визначає кількість одночасних процесів, підтримуваних в Linux. Її значення за замовчуванням в вихідному коді ядра одно 512, що дозволяє мати 256 одночасних підключень до одного екземпляра) необхідно для обчислення максимальної кількості дозволених записів в GDT.

Максимальна кількість дозволених в GDT записів може бути визначено за такою формулою:

Загальна кількість записів в GDT = 12 + 2 * NR_TASKS. Можлива кількість записів в GDT = 2 ^ 13 -1 = 8192.

З 8192 дескрипторів сегментів Linux використовує 6 дескрипторів сегментів, ще 4 додаткових для APM-функцій (функції розширеного управління живленням), а 4 записи в GDT залишаються невикористаними. Таким чином, кінцеве число можливих записів в GDT одно 8192 - 14, або 8180.

У будь-який момент часу ми не можемо мати більш ніж 8180 записів в GDT, отже:

2 * NR_TASKS = 8180
і NR_TASKS = 8180/2 = 4090

Чому 2 * NR_TASKS? Тому що для кожного створеного процесу завантажується не тільки TSS-дескриптор, який використовується для обслуговування перемикань контексту, а й LDT-дескриптор.

Це обмеження кількості процесів в x86-архітектурі відносилося до Linux 2.2, але після ядра 2.4 ця проблема була вирішена частково шляхом відмови від перемикання апаратного контексту (яке робило необхідним використання TSS), а також шляхом заміни його перемиканням процесу.

Тепер, давайте розглянемо сторінкову модель.

Огляд моделі сторінкової організації

Модуль управління сторінками перетворює лінійні адреси у фізичні (див. Малюнок 1). Набір лінійних адрес групується разом, утворюючи сторінки. Ці лінійні адреси безупинні за своєю природою - модуль управління сторінками відображає ці набори безперервної пам'яті в відповідний набір безперервних фізичних адрес, званих сторінковими фреймами. Зверніть увагу на те, що модуль управління сторінками представляє RAM розділеним на сторінкові фрейми фіксованого розміру.

З цієї причини розбиття на сторінки має такі переваги:

  • Права доступу, певні для сторінки, будуть дійсні для групи лінійних адрес, які формують сторінку
  • Довжина сторінки дорівнює довжині сторінкового фрейму

Структура даних, що відображає ці сторінки на сторінкові фрейми, називається, таблицею сторінок. Ці таблиці сторінок зберігаються в основній пам'яті і не започатковано ядром перед дозволом роботи модуля управління сторінками. На малюнку 5 показана таблиця сторінок.

Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах

Зверніть увагу на те, що набір адрес, що містяться в Page1, відповідає певному набору адрес, що містяться в Page Frame1.

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

Поля, що використовуються при розбитті сторінок

Наведемо опис полів, використовуваних для визначення сторінок в x86-архітектурі і допомагають керувати сторінками в Linux. Модуль розбиття на сторінки отримує лінійний адресу, який видає модуль сегментації. Цей лінійний адреса потім розбивається на наступні поля:

  • Directory (каталог) представлений десятьма MSB (Most Significant Bit - біт двійкового числа, що має найбільше значення; MSB іноді називається "найлівіший біт").
  • Table (таблиця) представлена десятьма середніми битами.
  • Offset (зсув) представлено дванадцятьма LSB (Least Significant Bit - біт двійкового числа, що представляє значення одиниці, тобто визначає, є число парних, або непарних; LSB іноді називають "самим правим бітом"; він аналогічний найменш значимою цифрі десяткового числа, що позначає одиниці і розташованої на самій правій позиції).

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

Малюнок 6. Адресні поля при розбитті на сторінки

На початку фізичну адресу Page Directory завантажується в регістр cr3. Поле directory в лінійному адресу визначає запис в Page Directory, що вказує на потрібну Page Table. Адреса в поле table визначає запис в Page Table, яка містить фізичну адресу сторінкового фрейму, що містить сторінку. Поле offset визначає відносну позицію в сторінковому фреймі. Оскільки довжина цього поля дорівнює 12 бітам, кожна сторінка містить 4 KB даних.

Отже, фізичну адресу обчислюється таким чином:

  1. cr3 + Page Directory (10 MSBs) = вказує table_base
  2. table_base + Page Table (10 середніх біт) = вказує page_base
  3. page_base + Offset = фізичну адресу (сторінковий фрейм)

Оскільки Page Directory і Page Table займають 10 біт, вони можуть адресувати максимум 1024 * 1024 KB, а Offset може адресувати до 2 ^ 12 (4096 байт). Таким чином, сумарна адресуються простір в Page Directory одно 1024 * 1024 * 4096 (2 ^ 32 осередків пам'яті, що дорівнює 4 GB). Тобто, на x86-архітектурі адресується простір обмежений 4 GB.

Розширене розбиття на сторінки

Розширене розбиття на сторінки виходить при видаленні таблиці перетворення Page Table; тоді поділ лінійного адреси здійснюється між Page Directory (10 MSB) і Offset (22 LSB).

22 біт LSB формують кордон в 4 MB для сторінкового фрейму (2 ^ 22). Розширене розбиття на сторінки існує разом зі звичайним розбивкою і включається для відображення великої кількості безперервних лінійних адрес у відповідні фізичні адреси. Операційна система видаляє Page Table і забезпечує, таким чином, розширене розбиття на сторінки. Це вирішується шляхом установки прапора PSE (page size extension).

Дослідження моделі пам'яті Linux

Перший крок до освоєння структури Linux

Розуміння моделей пам'яті, використовуваних в Linux, - це перший крок до освоєння на більш високому рівні структури операційної системи Linux і її реалізації. У цій статті моделі пам'яті Linux і управління пам'яттю розглядаються на ознайомчому рівні.

Операційна система Linux використовує монолітний підхід, при якому визначається набір примітивів або системних викликів для реалізації служб операційної системи, таких як управління процесами, паралельна робота і управління пам'яттю, в декількох модулях, які працюють в режимі супервізора. І хоча Linux з метою сумісності підтримує модель модуля управління сегментами (segment control unit) як символічне уявлення, вона використовує цю модель на мінімальному рівні.

Основними завданнями управління пам'яттю є:

  • Управління віртуальною пам'яттю - логічний рівень між запитами пам'яті в додатках і фізичної пам'яттю.
  • Управління фізичної пам'яттю.
  • Управління віртуальною пам'яттю на рівні ядра (модуль ядра, що займається розподілом пам'яті - компонент, який намагається задовольнити запити до пам'яті). Запит може бути виконаний з ядра або з програми користувача.
  • Управління віртуальним адресним простором.
  • Свопінг і кешування.

Дана стаття покликана допомогти вам в освоєнні внутрішнього устрою Linux з точки зору управління пам'яттю операційної системи. Розглядаються наступні теми:

  • Загальна модель модуля управління сегментами і її особливості в Linux.
  • Загальна модель управління сторінками пам'яті і її особливості в Linux.
  • Фізичні деталі області пам'яті.

У даній статті не розглядаються детально питання управління пам'яттю в ядрі Linux, але інформація по загальній моделі пам'яті і по роботі з нею повинна дати вам основу для подальшого вивчення. Увага в статті приділяється архітектурі x86, але ви можете використовувати ці матеріали і з іншими апаратними реалізаціями.

Архітектура пам'яті x86

У x86-архітектурі пам'ять розділяється на три типи адрес:

  • Логічна адреса - адреса розташування осередку пам'яті, який може бути (а може і ні) пов'язаний безпосередньо з фізичним розташуванням. Логічна адреса зазвичай використовується при запиті інформації з контролера.
  • Лінійна адреса (або лінійне адресний простір) - це пам'ять, адресація якої починається з 0. Кожен наступний байт адресується наступним послідовним номером (0, 1, 2, 3 і т.д.) до кінця пам'яті. Так адресують пам'ять більшість CPU неспроможний Intel-архітектури. У Intel®-архітектурах використовується сегментоване адресний простір, в якому пам'ять розділяється на сегменти розміром 64KB, а сегментний регістр завжди вказує на базовий адресу адресується сегмента. 32-бітний режим в цій архітектурі розглядається як лінійне адресний простір, але в ньому теж використовуються сегменти.
  • Фізична адреса - адреса, представлений битами фізичної адресної шини. Фізична адреса може відрізнятися від логічного; в цьому випадку модуль управління пам'яттю транслює логічний адресу в фізичний.

CPU використовує два модулі для перетворення логічного адреси в його фізичний еквівалент. Перший називається модулем сегментації (segmented unit), а другий - модулем поділу на сторінки (paging unit).

Малюнок 1. Два модуля перетворять адресний простір

Давайте досліджуємо модель модуля управління сегментами.

Загальна модель модуля управління сегментами

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

  • Базова адреса - адреса деякого місця фізичної пам'яті
  • Значення довжини, яке вказує довжину сегмента

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

Підведемо підсумки:

Модуль сегментації представляється як -> модель Сегмент: Зміщення також може бути представлений як -> Ідентифікатор сегмента: Зміщення

Кожен сегмент - це 16-бітове поле, зване ідентифікатором сегмента або селектором сегменту. Процесори сімейства x86 містять кілька програмованих регістрів, званих сегментними, які зберігають ці селектори сегментів. Такими регістрами є cs (сегмент коду), ds (сегмент даних) і ss (сегмент стека). Кожен ідентифікатор сегмента вказує сегмент, який представлений 64-бітовим (8-байтним) дескриптором сегмента. Ці дескриптори сегментів зберігаються в GDT (глобальна таблиця дескрипторів) і може бути також збережена в LDT (локальна таблиця дескрипторів).

Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів

Кожен раз, коли селектор сегмента завантажується в сегментний регістр, відповідний дескриптор сегмента завантажується з пам'яті в відповідний непрограмований регістр CPU. Кожен дескриптор сегмента має довжину 8 байт і представляє один сегмент в пам'яті. Вони зберігаються в таблицях LDT чи GDT. Запис елемента дескриптора сегмента містить і покажчик на перший байт в пов'язаному сегменті, представленому полем Base, і 20-бітове значення (поле Limit), яке представляє розмір сегмента в пам'яті.

Кілька інших полів містять спеціальні атрибути, такі як рівень привілеїв і тип сегмента (cs або ds). Тип сегмента представляється в четирехбітних поле Type.

Оскільки використовуються непрограмований регістр, до GDT або LDT не проводиться звернень до тих пір, поки не буде виконана трансляція логічного адреси в фізичний. Це прискорює роботу з пам'яттю.

Селектор сегмента містить:

  • 13-бітний індекс, який вказує на відповідний дескриптор сегмента, що міститься в GDT або LDT.
  • Прапор TI (Table Indicator), який вказує, в якій таблиці знаходиться дескриптор сегмента. Якщо прапор дорівнює 0, дескриптор знаходиться в GDT; якщо 1 - в LDT.
  • RPL (request privilege level) визначає поточний рівень привілеїв CPU при завантаженні відповідного селектора сегмента в регістр сегмента.

Оскільки дескриптор сегмента має довжину 8 байт, його відносна адреса в GDT або LDT обчислюється шляхом множення найзначиміших 13 бітів селектора сегмента на 8. Наприклад, якщо GDT зберігається за адресою 0x00020000, і поле Index, вказане селектором сегменту, дорівнює 2, тоді адреса відповідного дескриптора сегмента одно (2 * 8) + 0x00020000. Максимальна кількість дескрипторів сегмента, яке може бути збережене в GDT, так само (2 ^ 13 - 1) або 8191.

На малюнку 3 показано графічне представлення обчислення лінійного адреси з логічного.

Малюнок 3. Отримання лінійного адреси з логічного

А тепер подивимося, які відмінності в Linux?

Модуль управління сегментами в Linux

У Linux ця модель має невеликі відмінності. Я вже говорив про те, що Linux використовує модель сегментування обмежено (в основному для сумісності).

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

  • Управління пам'яттю простіше при використанні усіма процесами однакових значень сегментних регістрів (коли вони спільно використовують однаковий набір лінійних адрес).
  • Може бути досягнута сумісність з більшістю архітектур. Деякі RISC-процесори теж підтримують такий обмежений метод сегментування.

На малюнку 4 показані ці відмінності.

Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес

Дескриптори сегментів

Linux використовує такі дескриптори сегментів:

  • Сегмент коду ядра
  • Сегмент даних ядра
  • Сегмент коду користувача
  • Сегмент даних користувача
  • сегмент TSS
  • Сегмент LDT за замовчуванням

Розглянемо детально кожен з них.

Дескриптор сегмента коду ядра в GDT має наступні значення:

  • Base = 0x00000000
  • Limit = 0xffffffff (2 ^ 32 -1) = 4GB
  • G (прапор одиниці сегментування) = 1 для розміру сегмента, вираженого в сторінках
  • S = 1 для звичайного сегмента коду або даних
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • Значення DPL = 0 для режиму ядра

Лінійна адреса для цього сегмента дорівнює 4 GB. S = 1 і type = 0xa позначають сегмент коду. Селектор знаходиться в регістрі cs. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_CS.

Дескриптор сегмента даних ядра має аналогічні значення за винятком поля Type, значення якого встановлено в 2. Це вказує на те, що сегмент є сегментом даних і селектор зберігається в регістрі ds. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_DS.

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

  • Base = 0x00000000
  • Limit = 0xffffffff
  • G = 1
  • S = 1
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • DPL = 3 для призначеного для користувача режиму

Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_CS.

У дескрипторі сегмента даних користувача є тільки одна зміна в порівнянні з попереднім дескриптором - поле Type встановлено в 2 і визначає сегмент даних, які можна прочитати і записати. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_DS.

Крім цих дескрипторів сегментів GDT містить два додаткових дескриптора сегментів для кожного створеного процесу - для сегментів TSS і LDT.

Кожен дескриптор сегмента TSS вказує на окремий процес. TSS зберігає інформацію про апаратне контексті для кожного CPU, який бере участь в перемиканні контексту. Наприклад, при перемиканні режимів U-> K x86 CPU отримує адресу стека режиму ядра з TSS.

Кожен процес має свій власний TSS-дескриптор, що зберігається в GDT. Значення цього дескриптора такі:

  • Base = & tss (адреса поля TSS дескриптора відповідного процесу; наприклад, & tss_struct), який визначений в файлі schedule.h ядра Linux
  • Limit = 0xeb (сегмент TSS займає 236 байт)
  • Type = 9 або 11
  • DPL = 0. користувача режим не звертається до TSS. Прапор G очищений

Всі процеси спільно використовують сегмент LDT за замовчуванням. За замовчуванням він містить нульовий дескриптор сегмента. Цей дескриптор сегмента LDT за замовчуванням зберігається в GDT. Згенерований ядром Linux LDT займає 24 байта. За замовчуванням завжди присутні три записи:

LDT [0] = null LDT [1] = сегмент коду користувача LDT [2] = дескриптор сегмента даних / стека користувача

обчислення TASKS

Знання NR_TASKS (змінної, що визначає кількість одночасних процесів, підтримуваних в Linux. Її значення за замовчуванням в вихідному коді ядра одно 512, що дозволяє мати 256 одночасних підключень до одного екземпляра) необхідно для обчислення максимальної кількості дозволених записів в GDT.

Максимальна кількість дозволених в GDT записів може бути визначено за такою формулою:

Загальна кількість записів в GDT = 12 + 2 * NR_TASKS. Можлива кількість записів в GDT = 2 ^ 13 -1 = 8192.

З 8192 дескрипторів сегментів Linux використовує 6 дескрипторів сегментів, ще 4 додаткових для APM-функцій (функції розширеного управління живленням), а 4 записи в GDT залишаються невикористаними. Таким чином, кінцеве число можливих записів в GDT одно 8192 - 14, або 8180.

У будь-який момент часу ми не можемо мати більш ніж 8180 записів в GDT, отже:

2 * NR_TASKS = 8180
і NR_TASKS = 8180/2 = 4090

Чому 2 * NR_TASKS? Тому що для кожного створеного процесу завантажується не тільки TSS-дескриптор, який використовується для обслуговування перемикань контексту, а й LDT-дескриптор.

Це обмеження кількості процесів в x86-архітектурі відносилося до Linux 2.2, але після ядра 2.4 ця проблема була вирішена частково шляхом відмови від перемикання апаратного контексту (яке робило необхідним використання TSS), а також шляхом заміни його перемиканням процесу.

Тепер, давайте розглянемо сторінкову модель.

Огляд моделі сторінкової організації

Модуль управління сторінками перетворює лінійні адреси у фізичні (див. Малюнок 1). Набір лінійних адрес групується разом, утворюючи сторінки. Ці лінійні адреси безупинні за своєю природою - модуль управління сторінками відображає ці набори безперервної пам'яті в відповідний набір безперервних фізичних адрес, званих сторінковими фреймами. Зверніть увагу на те, що модуль управління сторінками представляє RAM розділеним на сторінкові фрейми фіксованого розміру.

З цієї причини розбиття на сторінки має такі переваги:

  • Права доступу, певні для сторінки, будуть дійсні для групи лінійних адрес, які формують сторінку
  • Довжина сторінки дорівнює довжині сторінкового фрейму

Структура даних, що відображає ці сторінки на сторінкові фрейми, називається, таблицею сторінок. Ці таблиці сторінок зберігаються в основній пам'яті і не започатковано ядром перед дозволом роботи модуля управління сторінками. На малюнку 5 показана таблиця сторінок.

Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах

Зверніть увагу на те, що набір адрес, що містяться в Page1, відповідає певному набору адрес, що містяться в Page Frame1.

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

Поля, що використовуються при розбитті сторінок

Наведемо опис полів, використовуваних для визначення сторінок в x86-архітектурі і допомагають керувати сторінками в Linux. Модуль розбиття на сторінки отримує лінійний адресу, який видає модуль сегментації. Цей лінійний адреса потім розбивається на наступні поля:

  • Directory (каталог) представлений десятьма MSB (Most Significant Bit - біт двійкового числа, що має найбільше значення; MSB іноді називається "найлівіший біт").
  • Table (таблиця) представлена десятьма середніми битами.
  • Offset (зсув) представлено дванадцятьма LSB (Least Significant Bit - біт двійкового числа, що представляє значення одиниці, тобто визначає, є число парних, або непарних; LSB іноді називають "самим правим бітом"; він аналогічний найменш значимою цифрі десяткового числа, що позначає одиниці і розташованої на самій правій позиції).

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

Малюнок 6. Адресні поля при розбитті на сторінки

На початку фізичну адресу Page Directory завантажується в регістр cr3. Поле directory в лінійному адресу визначає запис в Page Directory, що вказує на потрібну Page Table. Адреса в поле table визначає запис в Page Table, яка містить фізичну адресу сторінкового фрейму, що містить сторінку. Поле offset визначає відносну позицію в сторінковому фреймі. Оскільки довжина цього поля дорівнює 12 бітам, кожна сторінка містить 4 KB даних.

Отже, фізичну адресу обчислюється таким чином:

  1. cr3 + Page Directory (10 MSBs) = вказує table_base
  2. table_base + Page Table (10 середніх біт) = вказує page_base
  3. page_base + Offset = фізичну адресу (сторінковий фрейм)

Оскільки Page Directory і Page Table займають 10 біт, вони можуть адресувати максимум 1024 * 1024 KB, а Offset може адресувати до 2 ^ 12 (4096 байт). Таким чином, сумарна адресуються простір в Page Directory одно 1024 * 1024 * 4096 (2 ^ 32 осередків пам'яті, що дорівнює 4 GB). Тобто, на x86-архітектурі адресується простір обмежений 4 GB.

Розширене розбиття на сторінки

Розширене розбиття на сторінки виходить при видаленні таблиці перетворення Page Table; тоді поділ лінійного адреси здійснюється між Page Directory (10 MSB) і Offset (22 LSB).

22 біт LSB формують кордон в 4 MB для сторінкового фрейму (2 ^ 22). Розширене розбиття на сторінки існує разом зі звичайним розбивкою і включається для відображення великої кількості безперервних лінійних адрес у відповідні фізичні адреси. Операційна система видаляє Page Table і забезпечує, таким чином, розширене розбиття на сторінки. Це вирішується шляхом установки прапора PSE (page size extension).

Дослідження моделі пам'яті Linux

Перший крок до освоєння структури Linux

Розуміння моделей пам'яті, використовуваних в Linux, - це перший крок до освоєння на більш високому рівні структури операційної системи Linux і її реалізації. У цій статті моделі пам'яті Linux і управління пам'яттю розглядаються на ознайомчому рівні.

Операційна система Linux використовує монолітний підхід, при якому визначається набір примітивів або системних викликів для реалізації служб операційної системи, таких як управління процесами, паралельна робота і управління пам'яттю, в декількох модулях, які працюють в режимі супервізора. І хоча Linux з метою сумісності підтримує модель модуля управління сегментами (segment control unit) як символічне уявлення, вона використовує цю модель на мінімальному рівні.

Основними завданнями управління пам'яттю є:

  • Управління віртуальною пам'яттю - логічний рівень між запитами пам'яті в додатках і фізичної пам'яттю.
  • Управління фізичної пам'яттю.
  • Управління віртуальною пам'яттю на рівні ядра (модуль ядра, що займається розподілом пам'яті - компонент, який намагається задовольнити запити до пам'яті). Запит може бути виконаний з ядра або з програми користувача.
  • Управління віртуальним адресним простором.
  • Свопінг і кешування.

Дана стаття покликана допомогти вам в освоєнні внутрішнього устрою Linux з точки зору управління пам'яттю операційної системи. Розглядаються наступні теми:

  • Загальна модель модуля управління сегментами і її особливості в Linux.
  • Загальна модель управління сторінками пам'яті і її особливості в Linux.
  • Фізичні деталі області пам'яті.

У даній статті не розглядаються детально питання управління пам'яттю в ядрі Linux, але інформація по загальній моделі пам'яті і по роботі з нею повинна дати вам основу для подальшого вивчення. Увага в статті приділяється архітектурі x86, але ви можете використовувати ці матеріали і з іншими апаратними реалізаціями.

Архітектура пам'яті x86

У x86-архітектурі пам'ять розділяється на три типи адрес:

  • Логічна адреса - адреса розташування осередку пам'яті, який може бути (а може і ні) пов'язаний безпосередньо з фізичним розташуванням. Логічна адреса зазвичай використовується при запиті інформації з контролера.
  • Лінійна адреса (або лінійне адресний простір) - це пам'ять, адресація якої починається з 0. Кожен наступний байт адресується наступним послідовним номером (0, 1, 2, 3 і т.д.) до кінця пам'яті. Так адресують пам'ять більшість CPU неспроможний Intel-архітектури. У Intel®-архітектурах використовується сегментоване адресний простір, в якому пам'ять розділяється на сегменти розміром 64KB, а сегментний регістр завжди вказує на базовий адресу адресується сегмента. 32-бітний режим в цій архітектурі розглядається як лінійне адресний простір, але в ньому теж використовуються сегменти.
  • Фізична адреса - адреса, представлений битами фізичної адресної шини. Фізична адреса може відрізнятися від логічного; в цьому випадку модуль управління пам'яттю транслює логічний адресу в фізичний.

CPU використовує два модулі для перетворення логічного адреси в його фізичний еквівалент. Перший називається модулем сегментації (segmented unit), а другий - модулем поділу на сторінки (paging unit).

Малюнок 1. Два модуля перетворять адресний простір

Давайте досліджуємо модель модуля управління сегментами.

Загальна модель модуля управління сегментами

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

  • Базова адреса - адреса деякого місця фізичної пам'яті
  • Значення довжини, яке вказує довжину сегмента

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

Підведемо підсумки:

Модуль сегментації представляється як -> модель Сегмент: Зміщення також може бути представлений як -> Ідентифікатор сегмента: Зміщення

Кожен сегмент - це 16-бітове поле, зване ідентифікатором сегмента або селектором сегменту. Процесори сімейства x86 містять кілька програмованих регістрів, званих сегментними, які зберігають ці селектори сегментів. Такими регістрами є cs (сегмент коду), ds (сегмент даних) і ss (сегмент стека). Кожен ідентифікатор сегмента вказує сегмент, який представлений 64-бітовим (8-байтним) дескриптором сегмента. Ці дескриптори сегментів зберігаються в GDT (глобальна таблиця дескрипторів) і може бути також збережена в LDT (локальна таблиця дескрипторів).

Малюнок 2. Взаємодія дескрипторів сегментів і регістрів сегментів

Кожен раз, коли селектор сегмента завантажується в сегментний регістр, відповідний дескриптор сегмента завантажується з пам'яті в відповідний непрограмований регістр CPU. Кожен дескриптор сегмента має довжину 8 байт і представляє один сегмент в пам'яті. Вони зберігаються в таблицях LDT чи GDT. Запис елемента дескриптора сегмента містить і покажчик на перший байт в пов'язаному сегменті, представленому полем Base, і 20-бітове значення (поле Limit), яке представляє розмір сегмента в пам'яті.

Кілька інших полів містять спеціальні атрибути, такі як рівень привілеїв і тип сегмента (cs або ds). Тип сегмента представляється в четирехбітних поле Type.

Оскільки використовуються непрограмований регістр, до GDT або LDT не проводиться звернень до тих пір, поки не буде виконана трансляція логічного адреси в фізичний. Це прискорює роботу з пам'яттю.

Селектор сегмента містить:

  • 13-бітний індекс, який вказує на відповідний дескриптор сегмента, що міститься в GDT або LDT.
  • Прапор TI (Table Indicator), який вказує, в якій таблиці знаходиться дескриптор сегмента. Якщо прапор дорівнює 0, дескриптор знаходиться в GDT; якщо 1 - в LDT.
  • RPL (request privilege level) визначає поточний рівень привілеїв CPU при завантаженні відповідного селектора сегмента в регістр сегмента.

Оскільки дескриптор сегмента має довжину 8 байт, його відносна адреса в GDT або LDT обчислюється шляхом множення найзначиміших 13 бітів селектора сегмента на 8. Наприклад, якщо GDT зберігається за адресою 0x00020000, і поле Index, вказане селектором сегменту, дорівнює 2, тоді адреса відповідного дескриптора сегмента одно (2 * 8) + 0x00020000. Максимальна кількість дескрипторів сегмента, яке може бути збережене в GDT, так само (2 ^ 13 - 1) або 8191.

На малюнку 3 показано графічне представлення обчислення лінійного адреси з логічного.

Малюнок 3. Отримання лінійного адреси з логічного

А тепер подивимося, які відмінності в Linux?

Модуль управління сегментами в Linux

У Linux ця модель має невеликі відмінності. Я вже говорив про те, що Linux використовує модель сегментування обмежено (в основному для сумісності).

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

  • Управління пам'яттю простіше при використанні усіма процесами однакових значень сегментних регістрів (коли вони спільно використовують однаковий набір лінійних адрес).
  • Може бути досягнута сумісність з більшістю архітектур. Деякі RISC-процесори теж підтримують такий обмежений метод сегментування.

На малюнку 4 показані ці відмінності.

Малюнок 4. У Linux сегментні регістри вказують на один і той же набір адрес

Дескриптори сегментів

Linux використовує такі дескриптори сегментів:

  • Сегмент коду ядра
  • Сегмент даних ядра
  • Сегмент коду користувача
  • Сегмент даних користувача
  • сегмент TSS
  • Сегмент LDT за замовчуванням

Розглянемо детально кожен з них.

Дескриптор сегмента коду ядра в GDT має наступні значення:

  • Base = 0x00000000
  • Limit = 0xffffffff (2 ^ 32 -1) = 4GB
  • G (прапор одиниці сегментування) = 1 для розміру сегмента, вираженого в сторінках
  • S = 1 для звичайного сегмента коду або даних
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • Значення DPL = 0 для режиму ядра

Лінійна адреса для цього сегмента дорівнює 4 GB. S = 1 і type = 0xa позначають сегмент коду. Селектор знаходиться в регістрі cs. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_CS.

Дескриптор сегмента даних ядра має аналогічні значення за винятком поля Type, значення якого встановлено в 2. Це вказує на те, що сегмент є сегментом даних і селектор зберігається в регістрі ds. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _KERNEL_DS.

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

  • Base = 0x00000000
  • Limit = 0xffffffff
  • G = 1
  • S = 1
  • Type = 0xa для сегмента коду, який можна прочитати і виконати
  • DPL = 3 для призначеного для користувача режиму

Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_CS.

У дескрипторі сегмента даних користувача є тільки одна зміна в порівнянні з попереднім дескриптором - поле Type встановлено в 2 і визначає сегмент даних, які можна прочитати і записати. Доступ до відповідного селектору сегмента в Linux здійснюється через макрос _USER_DS.

Крім цих дескрипторів сегментів GDT містить два додаткових дескриптора сегментів для кожного створеного процесу - для сегментів TSS і LDT.

Кожен дескриптор сегмента TSS вказує на окремий процес. TSS зберігає інформацію про апаратне контексті для кожного CPU, який бере участь в перемиканні контексту. Наприклад, при перемиканні режимів U-> K x86 CPU отримує адресу стека режиму ядра з TSS.

Кожен процес має свій власний TSS-дескриптор, що зберігається в GDT. Значення цього дескриптора такі:

  • Base = & tss (адреса поля TSS дескриптора відповідного процесу; наприклад, & tss_struct), який визначений в файлі schedule.h ядра Linux
  • Limit = 0xeb (сегмент TSS займає 236 байт)
  • Type = 9 або 11
  • DPL = 0. користувача режим не звертається до TSS. Прапор G очищений

Всі процеси спільно використовують сегмент LDT за замовчуванням. За замовчуванням він містить нульовий дескриптор сегмента. Цей дескриптор сегмента LDT за замовчуванням зберігається в GDT. Згенерований ядром Linux LDT займає 24 байта. За замовчуванням завжди присутні три записи:

LDT [0] = null LDT [1] = сегмент коду користувача LDT [2] = дескриптор сегмента даних / стека користувача

обчислення TASKS

Знання NR_TASKS (змінної, що визначає кількість одночасних процесів, підтримуваних в Linux. Її значення за замовчуванням в вихідному коді ядра одно 512, що дозволяє мати 256 одночасних підключень до одного екземпляра) необхідно для обчислення максимальної кількості дозволених записів в GDT.

Максимальна кількість дозволених в GDT записів може бути визначено за такою формулою:

Загальна кількість записів в GDT = 12 + 2 * NR_TASKS. Можлива кількість записів в GDT = 2 ^ 13 -1 = 8192.

З 8192 дескрипторів сегментів Linux використовує 6 дескрипторів сегментів, ще 4 додаткових для APM-функцій (функції розширеного управління живленням), а 4 записи в GDT залишаються невикористаними. Таким чином, кінцеве число можливих записів в GDT одно 8192 - 14, або 8180.

У будь-який момент часу ми не можемо мати більш ніж 8180 записів в GDT, отже:

2 * NR_TASKS = 8180
і NR_TASKS = 8180/2 = 4090

Чому 2 * NR_TASKS? Тому що для кожного створеного процесу завантажується не тільки TSS-дескриптор, який використовується для обслуговування перемикань контексту, а й LDT-дескриптор.

Це обмеження кількості процесів в x86-архітектурі відносилося до Linux 2.2, але після ядра 2.4 ця проблема була вирішена частково шляхом відмови від перемикання апаратного контексту (яке робило необхідним використання TSS), а також шляхом заміни його перемиканням процесу.

Тепер, давайте розглянемо сторінкову модель.

Огляд моделі сторінкової організації

Модуль управління сторінками перетворює лінійні адреси у фізичні (див. Малюнок 1). Набір лінійних адрес групується разом, утворюючи сторінки. Ці лінійні адреси безупинні за своєю природою - модуль управління сторінками відображає ці набори безперервної пам'яті в відповідний набір безперервних фізичних адрес, званих сторінковими фреймами. Зверніть увагу на те, що модуль управління сторінками представляє RAM розділеним на сторінкові фрейми фіксованого розміру.

З цієї причини розбиття на сторінки має такі переваги:

  • Права доступу, певні для сторінки, будуть дійсні для групи лінійних адрес, які формують сторінку
  • Довжина сторінки дорівнює довжині сторінкового фрейму

Структура даних, що відображає ці сторінки на сторінкові фрейми, називається, таблицею сторінок. Ці таблиці сторінок зберігаються в основній пам'яті і не започатковано ядром перед дозволом роботи модуля управління сторінками. На малюнку 5 показана таблиця сторінок.

Малюнок 5. Таблиця сторінок визначає відповідність сторінок сторінковим фреймах

Зверніть увагу на те, що набір адрес, що містяться в Page1, відповідає певному набору адрес, що містяться в Page Frame1.

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

Поля, що використовуються при розбитті сторінок

Наведемо опис полів, використовуваних для визначення сторінок в x86-архітектурі і допомагають керувати сторінками в Linux. Модуль розбиття на сторінки отримує лінійний адресу, який видає модуль сегментації. Цей лінійний адреса потім розбивається на наступні поля:

  • Directory (каталог) представлений десятьма MSB (Most Significant Bit - біт двійкового числа, що має найбільше значення; MSB іноді називається "найлівіший біт").
  • Table (таблиця) представлена десятьма середніми битами.
  • Offset (зсув) представлено дванадцятьма LSB (Least Significant Bit - біт двійкового числа, що представляє значення одиниці, тобто визначає, є число парних, або непарних; LSB іноді називають "самим правим бітом"; він аналогічний найменш значимою цифрі десяткового числа, що позначає одиниці і розташованої на самій правій позиції).

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

Малюнок 6. Адресні поля при розбитті на сторінки

На початку фізичну адресу Page Directory завантажується в регістр cr3. Поле directory в лінійному адресу визначає запис в Page Directory, що вказує на потрібну Page Table. Адреса в поле table визначає запис в Page Table, яка містить фізичну адресу сторінкового фрейму, що містить сторінку. Поле offset визначає відносну позицію в сторінковому фреймі. Оскільки довжина цього поля дорівнює 12 бітам, кожна сторінка містить 4 KB даних.

Отже, фізичну адресу обчислюється таким чином:

  1. cr3 + Page Directory (10 MSBs) = вказує table_base
  2. table_base + Page Table (10 середніх біт) = вказує page_base
  3. page_base + Offset = фізичну адресу (сторінковий фрейм)

Оскільки Page Directory і Page Table займають 10 біт, вони можуть адресувати максимум 1024 * 1024 KB, а Offset може адресувати до 2 ^ 12 (4096 байт). Таким чином, сумарна адресуються простір в Page Directory одно 1024 * 1024 * 4096 (2 ^ 32 осередків пам'яті, що дорівнює 4 GB). Тобто, на x86-архітектурі адресується простір обмежений 4 GB.

Розширене розбиття на сторінки

Розширене розбиття на сторінки виходить при видаленні таблиці перетворення Page Table; тоді поділ лінійного адреси здійснюється між Page Directory (10 MSB) і Offset (22 LSB).

22 біт LSB формують кордон в 4 MB для сторінкового фрейму (2 ^ 22). Розширене розбиття на сторінки існує разом зі звичайним розбивкою і включається для відображення великої кількості безперервних лінійних адрес у відповідні фізичні адреси. Операційна система видаляє Page Table і забезпечує, таким чином, розширене розбиття на сторінки. Це вирішується шляхом установки прапора PSE (page size extension).

36-бітний PSE розширює підтримку 36-бітного фізичної адреси зі сторінками розміром 4 MB і підтримує також 4-байтную запис сторінкових каталогів, надаючи, таким чином, простий механізм адресації фізичної пам'яті вище 4 GB і не вимагаючи великих змін дизайну операційних систем. Такий підхід має практичні обмеження, що стосуються потреби розбиття на сторінки.

Модель розбиття на сторінки в Linux

Розбиття на сторінки в Linux аналогічно звичайному розбиття, але x86-архітектура має трирівневий табличний механізм, що складається з:

  • Page Global Directory (pgd - глобальний каталог сторінок), абстрактний верхній рівень багаторівневих таблиць сторінок. Кожен рівень таблиці сторінок має справу з різними розмірами пам'яті - це глобальне каталог може працювати з областями розміром 4 MB. Кожен запис буде покажчиком на більш низкоуровневую таблицю каталогів меншого розміру, тобто pgd - це каталог таблиць сторінок. Коли код проходить цю структуру (це роблять деякі драйвери), то кажуть, що виконується "прохід" за таблицями сторінок.
  • Page Middle Directory (pmd - проміжний каталог сторінок), проміжний рівень таблиць сторінок. На x86-архітектурі pmd не представлені апаратно, але входить в pgd в коді ядра.
  • Page Table Entry (pte - запис таблиці сторінок), нижній рівень, який працює зі сторінками безпосередньо (шукайте PAGE_SIZE), є значенням, яке містить фізичну адресу сторінки разом з пов'язаними бітами, що вказують, наприклад, що запис коректна, і відповідна сторінка присутня в реальному пам'яті.

Ця трирівнева схема розбиття на сторінки також реалізована в Linux для підтримки областей пам'яті великих розмірів. Якщо підтримка великих областей пам'яті не потрібно, ви можете повернутися до дворівневої схемою, встановивши pmd в "1".

Ці рівні оптимізуються під час компіляції, дозволяючи другий і третій рівні (з використанням того ж самого набору коду) простим дозволом або забороною проміжного каталогу. 32-бітові процесори використовують pmd-розбиття, а 64-биті використовують pgd-розбиття.

Малюнок 7. Три рівня розбиття на сторінки

У 64-бітних процесорах:

  • 21 біт MSB не використовуються
  • 13 біт LSB представлені зміщенням сторінки
  • Решта 30 біт розділені на
    • 10 біт для Page Table
    • 10 біт для Page Global Directory
    • 10 біт для Page Middle Directory

Як видно з архітектури, для адресації фактично використовуються 43 біта. Тобто, на 64-бітних процесорах реально доступно 2 певною мірою 43 осередків віртуальної пам'яті.

Кожен процес має свій власний набір каталогів сторінок і таблиць сторінок. Для звернення до сторінкового кадру з реальними даними користувачів операційна система починає з завантаження (на x86-архітектурі) pgd в регістр cr3. Linux зберігає в TSS-сегменті вміст регістра cr3 і потім завантажує інше значення з TSS-сегмента в регістр cr3 кожен раз під час виконання нового процесу в CPU. В результаті модуль розбиття на сторінки посилається на коректний набір таблиць сторінок.

Кожен запис в таблиці pgd вказує на сторінковий фрейм, що містить масив записів pmd, які, в свою чергу, вказують на сторінковий фрейм, що містить pte, який, нарешті, вказує на сторінковий фрейм, що містить призначені для користувача дані. Якщо шукана сторінка знаходиться у файлі підкачки, в таблиці pte зберігається запис файлу підкачки, яка використовується (при помилку відсутності сторінки) при пошуку сторінкового фрейму для завантаження в пам'ять.

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

Малюнок 8. Лінійні адреси мають різні розміри

Резервні сторінкові фрейми

Linux резервує кілька станичних фреймів для коду ядра і структур даних. Ці сторінки ніколи не скидаються в файл підкачки на диск. Лінійні адреси з 0x0 до 0xc0000000 (PAGE_OFFSET) використовуються кодом користувача і кодом ядра. Адреси з PAGE_OFFSET до 0xffffffff використовуються кодом ядра.

Це означає, що з 4 GB тільки 3 GB доступні для призначеного для користувача програми.

Як дозволяється розбиття на сторінки

Механізм розбиття на сторінки, що використовується процесами Linux, має дві фази:

  • Під час початкового завантаження система встановлює таблицю сторінок для 8 MB фізичної пам'яті.
  • Потім друга фаза завершує відображення для залишилася фізичної пам'яті.

У фазі первинного завантаження за ініціалізацію розбиття на сторінки відповідає виклик startup_32 (). Ця функція реалізована в файлі arch / i386 / kernel / head.S. Відображення цих 8 MB відбувається з адреси вище PAGE_OFFSET. Ініціалізація починається зі статично визначеної під час компіляції масиву, званого swapper_pg_dir. Під час компіляції він розміщується за конкретною адресою (0x00101000).

Визначаються записи для двох таблиць, визначених у коді статично - pg0 і pg1. Розмір цих сторінкових фреймів за замовчуванням дорівнює 4 KB, якщо не встановлено біт розширення розміру сторінок (page size extension) (додаткова інформація по PSE знаходиться в розділі " Розширене розбиття на сторінки "). Тоді розмір кожної дорівнює 4 MB. Адреса даних, що вказується глобальним масивом, зберігається в регістрі cr3, що, я вважаю, є першим етапом установки модуля розбиття на сторінки для процесів Linux. Решта сторінкові запису встановлюються у другій фазі.

Друга фаза реалізована в методі paging_init ().

Відображення RAM виконується між PAGE_OFFSET і адресою, що представляє кордон 4 GB (0xFFFFFFFF) в 32-бітної x86-архітектурі. Це означає, що RAM розміром приблизно в 1 GB може бути відображена при завантаженні Linux, і це відбувається за замовчуванням. Однак, якщо встановити HIGHMEM_CONFIG, то фізична пам'ять розміром більше 1 GB може бути відображена для ядра - майте на увазі, що це тимчасовий захід. Це робиться викликом kmap ().

Зона фізичної пам'яті

Я вже показав вам, що ядро ​​Linux (на 32-бітної архітектури) ділить віртуальну пам'ять у відношенні 3: 1 - 3 GB віртуальної пам'яті для простору користувача та 1 GB для простору ядра. Код ядра і його структури даних повинні розміщуватися в цьому 1 GB адресного простору, але навіть ще більшим споживачем цього адресного простору є віртуальне відображення фізичної пам'яті.

Це відбувається тому, що ядро ​​не може маніпулювати пам'яттю, якщо вона не відображена в його адресний простір. Таким чином, максимальною кількістю фізичної пам'яті, яке може обробити ядро, є обсяг пам'яті, який міг би відобразитися в ВАП ядра мінус обсяг, необхідний для відображення самого коду ядра. В результаті Linux на основі x86-системи змогла б працювати з об'ємом менше 1 GB фізичної пам'яті. І це максимум.

Тому, для обслуговування великої кількості користувачів, для підтримки більшого обсягу пам'яті, для поліпшення продуктивності і для установки архітектурно-незалежного способу опису пам'яті модель пам'яті Linux повинна була вдосконалюватися. Для досягнення цих цілей в більш нової моделі кожному CPU був призначений свій банк пам'яті. Кожен банк називається вузлом; кожен вузол розділяється на зони. Зони (що представляють діапазони пам'яті) далі розбивалися на наступні типи:

  • ZONE_DMA (0-16 MB): Діапазон пам'яті, розташований в нижній області пам'яті, чого вимагають деякі пристрої ISA / PCI.
  • ZONE_NORMAL (16-896 MB): Діапазон пам'яті, який безпосередньо відображається ядром в верхні ділянки фізичної пам'яті. Всі операції ядра можуть виконуватися тільки з використанням цієї зони пам'яті; таким чином, це сама критична для продуктивності зона.
  • ZONE_HIGHMEM (896 MB і вище): Частина, що залишилася доступна пам'ять системи не відображається ядром.

Концепція вузла реалізована в ядрі за допомогою структури struct pglist_data. Зона описується структурою struct zone_struct. Фізичний сторінковий фрейм представлений структурою struct Page, і всі ці структури зберігаються в глобальному масиві структур struct mem_map, який розміщується на початку NORMAL_ZONE. Ці основні взаємини між вузлом, зоною і сторінковим фреймом показані на малюнку 9.

Малюнок 9. Взаємовідносини між вузлом, зоною і сторінковим фреймом

Зона верхній області пам'яті з'явилася в системі управління пам'яттю ядра тоді, коли були реалізовані розширення віртуальної пам'яті Pentium II (для доступу до 64 GB пам'яті засобами PAE - Physical Address Extension - на 32-бітних системах) і підтримка 4 GB фізичної пам'яті (знову ж таки, на 32-бітних системах). Ця концепція може бути застосована до платформ x86 і SPARC. Зазвичай ці 4 GB пам'яті роблять доступними відображення ZONE_HIGHMEM в ZONE_NORMAL допомогою kmap (). Зверніть увагу, будь ласка, на те, що не бажано мати більше 16 GB RAM на 32-бітної архітектури навіть при дозволеному PAE.

(PAE - розроблене Intel розширення адрес пам'яті, що дозволяє процесорам збільшити кількість бітів, які можуть бути використані для адресації фізичної пам'яті, з 32 до 36 через підтримку в операційній системі додатків, що використовують Address Windowing Extensions API.)

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

например:

  • Запит на призначену для користувача сторінку повинен бути спочатку заповнений з "нормальною" зони (ZONE_NORMAL);
  • якщо завершення невдало - з ZONE_HIGHMEM;
  • якщо знову невдало - з ZONE_DMA.

Список зон для такого розподілу складається з зон ZONE_NORMAL, ZONE_HIGHMEM і ZONE_DMA в зазначеному порядку. З іншого боку, запит DMA-сторінки може бути виконаний із зони DMA, тому список зон для таких запитів містить тільки зону DMA.

Висновок

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

Ресурси для скачування

Схожі тими

  • Оригінал статті " Explore the Linux memory model ".
  • У статті " Секрети управління пам'яттю "(DeveloperWorks, листопад 2004) надано огляд технологій управління пам'яттю, доступних в Linux, включаючи роботу системи управління пам'яттю, ручне, напівавтоматичне і автоматичне керування пам'яттю.
  • У статті " Порівняння ядер: Покращене керування пам'яттю в ядрі 2.6 "(DeveloperWorks, березень 2004) детально розглянуті нові технології для поліпшення використання великих обсягів пам'яті, зворотне відображення, зберігання сторінок таблиць у верхній пам'яті і підвищена стабільність системи управління пам'яттю.
  • У статті " Linux, за межами (x86) системи "(DeveloperWorks, травень 2005) розглядається, що робити з архітектурою, відмінними від x86.
  • У статті " Використання поділюваних об'єктів в Linux "(DeveloperWorks, травень 2004) розглядається, як оптимально використовувати пам'ять, що розділяється.
  • " Принципи системи управління пам'яттю в Linux "- набір заснованих на практичному досвіді заміток по роботі системи управління пам'яттю Linux в реальному житті.
  • " Драйвери пристроїв Linux ", Третє видання (O'Reilly, лютий 2005) - містить відмінну главу по системі управління пам'яттю і DMA .
  • книга " Освоєння ядра Linux ", Третє видання (O'Reilly, листопад 2005) - екскурсія по коду, формує ядро ​​всіх операційних систем Linux.
  • книга " Освоєння системи управління віртуальною пам'яттю Linux "(Prentice Hall, квітень 2004 року) - повне керівництво по віртуальної пам'яті Linux.
  • Використовуйте у вашому наступному Linux-проект пробне програмне забезпечення IBM , Доступне для завантаження з сайту developerWorks.

Підпішіть мене на ПОВІДОМЛЕННЯ до коментарів

Новости

Banwar.org
Наша совместная команда Banwar.org. Сайт казино "Пари Матч" теперь доступен для всех желающих, жаждущих волнения и азартных приключений.