У чому різниця між простором користувача та простором ядра?


72

Чи використовується простір ядра, коли Kernel виконує від імені користувацької програми, тобто System Call? Або це адресний простір для всіх потоків ядра (наприклад, планувальник)?

Якщо це перша, то це означає, що звичайна програма користувача не може мати більше 3 Гб пам'яті (якщо поділ становить 3 ГБ + 1 ГБ)? Також у такому випадку як ядро ​​може використовувати високу пам'ять, оскільки до якої адреси віртуальної пам’яті будуть відображені сторінки з високої пам’яті, оскільки логічно відображатиметься 1 ГБ простору ядра?

Відповіді:


93

Чи використовується простір ядра, коли Kernel виконує від імені користувацької програми, тобто System Call? Або це адресний простір для всіх потоків ядра (наприклад, планувальник)?

Так і так.

Перш ніж піти далі, ми повинні сказати про пам'ять.

Пам'ять ділиться на дві чіткі області:

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

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

Простірний код ядра має властивість запускатися в «режимі ядра», який (у вашому типовому комп’ютері -x86- комп'ютер) - це те, що ви називаєте кодом, який виконується під кільцем 0 . Зазвичай в архітектурі x86 є 4 кільця захисту . Ring 0 (режим ядра), Ring 1 (може використовуватися гіпервізорами віртуальної машини або драйверами), Ring 2 (може використовуватися драйверами, але я не дуже впевнений у цьому). Кільце 3 - це те, для чого типові програми працюють. Це найменш привілейоване кільце, і програми, що працюють на ньому, мають доступ до підмножини інструкцій процесора. Кільце 0 (простір ядра) - це найбільш привілейоване кільце і має доступ до всіх інструкцій машини. Наприклад, для цього "звичайний" додаток (як-от браузер) не може використовувати інструкції зі збирання x86lgdtзавантажити глобальну таблицю дескрипторів або hltзупинити процесор.

Якщо це перша, то це означає, що звичайна програма користувача не може мати більше 3 Гб пам'яті (якщо поділ становить 3 ГБ + 1 ГБ)? Також у такому випадку як ядро ​​може використовувати високу пам'ять, оскільки до якої адреси віртуальної пам’яті будуть відображені сторінки з високої пам’яті, оскільки логічно відображатиметься 1 ГБ простору ядра?

Щоб отримати відповідь на це, будь ласка, зверніться до чудової відповіді, що тут віг


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

Дякую! Я думаю, зараз я це краще розумію. Просто щоб переконатися, що я правильно це зрозумів, у мене є ще одне питання. Знову ж таки, враховуючи, що перші 3 Гб використовуються для простору користувачів, а 128 МБ ядра використовується для високої пам'яті, чи решта 896 МБ (низька пам'ять) статично відображена під час завантаження?
Poojan

1
@NlightNFotis Я кажу, що майже 15 людей вважають, що все, що ви сказали, правильно (або так ви
змушуєте

Я думав, що кільце x86 -1призначене для гіпервізорів? en.wikipedia.org/wiki/Protection_ring
Дорі

1
Зверніть увагу на різницю між віртуальною пам'яттю та фізичною пам'яттю. Більшість запитань стосується віртуальної пам'яті. Це відображається на фізичній пам'яті, це ускладнюється, коли фізична пам'ять наближається до 3 Гб, і використовується PAE. Потім воно стає простим знову, коли використовується 64-бітове ядро, в цьому випадку негативні адреси зарезервовані для ядра, а позитивні - для простору користувача. 32-бітні процеси тепер можуть використовувати 4 Гб віртуального простору. 64-бітні процеси можуть використовувати набагато більше - зазвичай варто 48 біт (зараз на x86-64).
ctrl-alt-delor

16

Кільця процесора - це найяскравіша відмінність

У захищеному режимі x86 процесор завжди знаходиться в одному з 4-х кілець. Ядро Linux використовує лише 0 і 3:

  • 0 для ядра
  • 3 для користувачів

Це найскладніше та швидке визначення ядра проти користувача.

Чому Linux не використовує кільця 1 і 2: https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used

Як визначається поточне кільце?

Поточне кільце вибирається комбінацією:

  • глобальна таблиця дескрипторів: таблиця пам'яті записів GDT, і кожен запис має поле, Privlяке кодує кільце.

    Інструкція LGDT встановлює адресу в поточній таблиці дескрипторів.

    Дивіться також: http://wiki.osdev.org/Global_Descriptor_Table

  • сегмент реєструє CS, DS тощо, які вказують на індекс запису в GDT.

    Наприклад, CS = 0означає, що перший запис GDT в даний час активний для виконуючого коду.

Що може зробити кожне кільце?

Фізичний процесор фізично побудований так:

  • кільце 0 може зробити що завгодно

  • кільце 3 не може виконати кілька інструкцій і записати в декілька регістрів, особливо:

    • не може змінити власне кільце! В іншому випадку він може встановити себе на 0 і дзвінки будуть марні.

      Іншими словами, не може змінювати дескриптор поточного сегмента , який визначає поточне кільце.

    • не вдається змінити таблиці сторінок: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work

      Іншими словами, не вдається змінити регістр CR3, а сама сторінка перешкоджає зміні таблиць сторінок.

      Це заважає одному процесу бачити пам'ять інших процесів з безпеки / простоти програмування.

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

      Обробники працюють у кільці 0 і порушують модель безпеки.

      Іншими словами, не можна використовувати інструкції LGDT та LIDT.

    • не може зробити інструкції введення - виведення , як inі out, і , таким чином , мають довільний доступ до залозу.

      В іншому випадку, наприклад, дозволи на файли були б марними, якби будь-яка програма могла безпосередньо читати з диска.

      Точніше завдяки Майклу Петчу : насправді ОС може дозволити вказівки вводу-виводу на кільце 3, це фактично контролюється сегментом стану завдань .

      Що неможливо - це, щоб кільце 3 дало собі дозвіл на це, якщо б його не було в першу чергу.

      Linux завжди забороняє це. Дивіться також: https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

Як програми та операційні системи переходять між кільцями?

  • коли ЦП увімкнено, він починає виконувати початкову програму в кільці 0 (добре вид, але це гарне наближення). Ви можете вважати, що ця початкова програма є ядром (але зазвичай це завантажувач, який потім викликає ядро ​​все ще в кільці 0).

  • коли процес користування користувачем хоче, щоб ядро ​​щось зробило для нього, наприклад, записати у файл, воно використовує інструкцію, яка генерує переривання, наприклад, int 0x80абоsyscall для сигналізації ядра. Приклад світового приводу системного виклику x86-64:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    компілювати та запускати:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub вище за течією .

    Коли це відбувається, процесор викликає обробник зворотного виклику переривання, який ядро ​​зареєструвало під час завантаження. Ось конкретний бареметальний приклад, який реєструє обробник і використовує його .

    Цей обробник працює в кільці 0, яке вирішує, чи дозволить ядро ​​дозволити цю дію, зробіть дію та перезапустіть програму "userland" в кільці 3. x86_64

  • коли використовується execсистемний виклик (або коли ядро почнеться/init ), ядро готує регістри та пам'ять нового процесу користувальницької програми, після чого переходить до точки входу і перемикає процесор на 3

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

    Але оскільки користувальницька область була неслухняною, ядро ​​може цього разу вбити процес або надіслати його попередженням із сигналом.

  • Коли ядро ​​завантажується, воно встановлює апаратний годинник з певною фіксованою частотою, яка генерує періодично переривання.

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

    Таким чином, планування може відбутися, навіть якщо процеси не здійснюють жодних системних викликів.

Який сенс мати кілька кілець?

Є дві основні переваги розділення ядра та користувальницької області:

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

Як з цим пограти?

Я створив голий металевий інструмент, який повинен бути хорошим способом безпосередньо керувати кільцями: https://github.com/cirosantilli/x86-bare-metal-examples

На жаль, я не мав терпіння зробити приклад userland, на жаль, але я все-таки зайшов у налаштування підкачки, тому поле користування повинно бути здійсненим. Я хотів би побачити прохання про тягнення.

Крім того, модулі ядра Linux працюють у кільці 0, так що ви можете використовувати їх для випробування привілейованих операцій, наприклад, читати регістри управління: https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-program-getting-segmenta / 7419306 # 7419306

Ось зручна настройка QEMU + Buildroot, щоб спробувати це, не вбиваючи хоста.

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

Негативні кільця

Хоча на негативні дзвінки фактично не посилаються в посібнику Intel, фактично існують режими процесора, які мають додаткові можливості, ніж саме кільце 0, і тому добре підходять для імені "негативне кільце".

Одним із прикладів є режим гіпервізора, який використовується у віртуалізації.

Детальнішу інформацію див: https://security.stackexchange.com/questions/129098/what-is-protection-ring-1

ARM

В ARM кільця називаються рівнями виключення, але основні ідеї залишаються тими ж.

У ARMv8 існує 4 рівні винятків, які зазвичай використовуються як:

  • EL0: поле користування

  • EL1: ядро ​​("супервізор" в термінології ARM).

    Входить до svcінструкції (SuperVisor Call), раніше відомої як swi до уніфікованої збірки , що є інструкцією, що використовується для здійснення системних викликів Linux. Привіт світу ARMv8 приклад:

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub вище за течією .

    Перевірте це за допомогою QEMU на Ubuntu 16.04:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Ось конкретний бареметальний приклад, який реєструє обробник SVC та виконує виклик SVC .

  • EL2: гіпервізори , наприклад Xen .

    Введено в hvcінструкцію (HyperVisor Call).

    Гіпервізор - це ОС, що ОС - для користувачів.

    Наприклад, Xen дозволяє одночасно запускати кілька операційних систем, таких як Linux або Windows, в одній системі, і він ізолює ОС один від одного для безпеки та зручності налагодження, як і Linux для програм користувача.

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

    Наприклад, AWS використовував Xen до 2017 року, коли його перехід на KVM повідомив новину .

  • EL3: ще один рівень. Приклад TODO

    Введено smcінструкцією (захищений режим виклику)

ARMv8 Architecture Reference Model DDI 0487C.a - Глава D1 - Модель The AArch64 System Level Програміста - Малюнок D1-1 ілюструє це красиво:

введіть тут опис зображення

Зверніть увагу, як ARM, можливо, завдяки перевазі заднього огляду, має кращу конвенцію іменування рівнів привілеїв, ніж x86, без необхідності негативних рівнів: 0 - нижчий та 3 найвищий. Вищі рівні, як правило, створюються частіше, ніж нижчі.

Поточний EL можна запитати за допомогою MRSінструкції: https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc

ARM не вимагає наявності всіх рівнів винятків, щоб дозволити реалізації, які не потребують функції для збереження області чіпів. ARMv8 "Рівень винятку" говорить:

Впровадження може не включати всі рівні виключень. Усі реалізації повинні включати EL0 та EL1. EL2 та EL3 необов'язкові.

Наприклад, QEMU за замовчуванням для EL1, але EL2 та EL3 можна включити за допомогою параметрів командного рядка: https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulating-a53-power-up

Фрагменти коду, протестовані на Ubuntu 18.10.


3

Якщо це перша, то це означає, що звичайна програма користувача не може мати більше 3 Гб пам'яті (якщо поділ становить 3 ГБ + 1 ГБ)?

Так, це стосується звичайної системи Linux. Був набір патчів "4G / 4G", що плавали навколо, в один момент, що зробив цілком незалежними користувацькі та ядерні простори (за ефективність, тому що ядро ​​ускладнило доступ до пам'яті користувача), але я не думаю вони колись були об'єднані вгору за течією, і інтерес зменшувався зі зростанням x86-64

Також у такому випадку як ядро ​​може використовувати високу пам'ять, оскільки до якої адреси віртуальної пам’яті будуть відображені сторінки з високої пам’яті, оскільки логічно відображатиметься 1 ГБ простору ядра?

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

Так народилася концепція низької та високої пам’яті. "низька" пам'ять постійно відображається в адресному просторі ядер. "висока" пам'ять - ні.

Коли процесор виконує системний виклик, він працює в режимі ядра, але все ще в контексті поточного процесу. Таким чином, він може отримати прямий доступ як до адресного простору ядра, так і до адресного простору користувача поточного процесу (якщо ви не використовуєте вищезазначені патчі 4G / 4G). Це означає, що "висока" пам'ять не може бути виділена в користувальницький процес.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.