Які відмінності між режимом користувача та режимом ядра, чому і як ви активуєте будь-який із них та які випадки їх використання?
Які відмінності між режимом користувача та режимом ядра, чому і як ви активуєте будь-який із них та які випадки їх використання?
Відповіді:
Режим ядра
У режимі ядра виконуючий код має повний і необмежений доступ до базового обладнання. Він може виконати будь-яку інструкцію ЦП та посилатися на будь-яку адресу пам'яті. Режим ядра, як правило, зарезервований для найнижчих, найбільш надійних функцій операційної системи. Збої в режимі ядра катастрофічні; вони зупинять весь ПК.
Режим користувача
У режимі користувача виконавчий код не має можливості безпосередньо отримувати доступ до апаратної або довідкової пам'яті. Код, що працює в режимі користувача, повинен делегувати системні API для доступу до апаратного забезпечення або пам'яті. Завдяки захисту, що забезпечується таким видом ізоляції, збої в режимі користувача завжди підлягають відновленню. Більша частина коду, що працює на вашому комп’ютері, буде виконуватися в режимі користувача.
Детальніше
Це два різних режими, в яких ваш комп'ютер може працювати. До цього, коли комп’ютери були як велика кімната, якщо щось виходить з ладу - це зупиняє весь комп'ютер. Тож комп'ютерні архітектори вирішують її змінити. Сучасні мікропроцесори реалізують в апараті щонайменше 2 різних стани.
Режим користувача:
Режим ядра:
Як відбувається перемикання.
Перемикання з режиму користувача на режим ядра не відбувається автоматично процесором. Процесор переривається перериваннями (таймери, клавіатура, введення / виведення). Коли відбувається переривання, ЦП припиняє виконання поточної запущеної програми, переходить у режим ядра, виконує обробник переривання. Цей обробник зберігає стан процесора, виконує його операції, відновлює стан і повертається в режим користувача.
http://en.wikibooks.org/wiki/Windows_Programming/User_Mode_vs_Kernel_Mode
http://tldp.org/HOWTO/KernelAnalysis-HOWTO-3.html
Процесор у комп’ютері під управлінням Windows має два різних режими: режим користувача та режим ядра. Процесор перемикається між двома режимами залежно від того, який тип коду працює на процесорі. Програми запускаються в режимі користувача, а основні компоненти операційної системи працюють у режимі ядра. Хоча багато драйверів працюють у режимі ядра, деякі драйвери можуть працювати в режимі користувача.
Коли ви запускаєте програму в режимі користувача, Windows створює процес для програми. Процес надає додатку приватний віртуальний адресний простір та приватну таблицю обробки. Оскільки віртуальний адресний простір програми приватний, одна програма не може змінювати дані, що належать іншій програмі. Кожна програма працює ізольовано, і якщо програма виходить з ладу, збій обмежений лише однією програмою. Інші програми та операційна система не впливають на збій.
Крім приватних, віртуальний адресний простір додатка в режимі користувача обмежений. Процесор, що працює в режимі користувача, не може отримати доступ до віртуальних адрес, зарезервованих для операційної системи. Обмеження віртуального адресного простору програми в режимі користувача не дозволяє додатку змінювати та, можливо, пошкоджувати критичні дані операційної системи.
Весь код, який працює в режимі ядра, розділяє один віртуальний адресний простір. Це означає, що драйвер режиму ядра не ізольований від інших драйверів та самої операційної системи. Якщо драйвер у режимі ядра випадково записується на неправильну віртуальну адресу, дані, що належать операційній системі або іншому драйверу, можуть бути порушені. Якщо драйвер у режимі ядра виходить з ладу, вся операційна система виходить з ладу.
Якщо ви є користувачем Windows один раз, перейшовши через це посилання, ви отримаєте більше.
Кільця процесора - це найяскравіша відмінність
У захищеному режимі x86 процесор завжди знаходиться в одному з 4-х кілець. Ядро Linux використовує лише 0 і 3:
Це найскладніше та швидке визначення ядра проти користувача.
Чому Linux не використовує кільця 1 і 2: Привілейовані кільця процесора: Чому кільця 1 і 2 не використовуються?
Як визначається поточне кільце?
Поточне кільце вибирається комбінацією:
глобальна таблиця дескрипторів: таблиця пам'яті записів GDT, і кожен запис має поле, Privl
яке кодує кільце.
Інструкція LGDT встановлює адресу в поточній таблиці дескрипторів.
Дивіться також: http://wiki.osdev.org/Global_Descriptor_Table
сегмент реєструє CS, DS тощо, які вказують на індекс запису в GDT.
Наприклад, CS = 0
означає, що перший запис GDT в даний час активний для виконуючого коду.
Що може зробити кожне кільце?
Фізичний процесор фізично побудований так:
кільце 0 може робити що завгодно
кільце 3 не може виконати кілька інструкцій і записати в декілька регістрів, особливо:
не може змінити власне кільце! В іншому випадку він може встановити дзвінок 0, і дзвінки будуть марні.
Іншими словами, не може змінювати дескриптор поточного сегмента , який визначає поточне кільце.
не вдається змінити таблиці сторінок: Як працює підказка x86?
Іншими словами, не вдається змінити регістр CR3, а сам підказка запобігає зміні таблиць сторінок.
Це заважає одному процесу бачити пам'ять інших процесів з безпеки / простоти програмування.
не вдається зареєструвати обробники переривань. Вони налаштовуються записом у місця пам'яті, що також запобігається підключенням сторінки.
Обробники працюють у кільці 0 і порушують модель безпеки.
Іншими словами, не можна використовувати інструкції LGDT та LIDT.
не може виконувати інструкції IO, як in
і out
, і, таким чином, мати довільний апаратний доступ.
В іншому випадку, наприклад, дозволи на файли були б марними, якби будь-яка програма могла безпосередньо читати з диска.
Точніше завдяки Майклу Петчу : насправді ОС може дозволити вказівки вводу-виводу на кільце 3, це фактично контролюється сегментом стану завдань .
Що неможливо, це те, щоб кільце 3 давало собі дозвіл на це, якщо б його не було в першу чергу.
Linux завжди забороняє це. Дивіться також: Чому Linux не використовує апаратний контекстний комутатор через TSS?
Як програми та операційні системи переходять між кільцями?
коли ЦП увімкнено, він починає виконувати початкову програму в кільці 0 (добре вид, але це гарне наближення). Ви можете вважати, що ця початкова програма є ядром (але зазвичай це завантажувач, який потім викликає ядро все ще в кільці 0 ).
коли процес userland хоче, щоб ядро щось для нього зробило, наприклад, записати у файл, воно використовує інструкцію, яка генерує переривання, наприклад, int 0x80
абоsyscall
для сигналізації ядра. Приклад світу syscall Linux 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
Коли це відбувається, процесор викликає обробник зворотного виклику переривань, який ядро зареєструвало під час завантаження. Ось конкретний бареметальний приклад, який реєструє обробник і використовує його .
Цей обробник працює в кільці 0, яке вирішує, чи дозволить ядро дозволити цю дію, зробіть дію та перезапустіть програмуlandland в кільці 3. x86_64
коли використовується exec
системний виклик (або коли почнеться/init
ядро ), ядро готує регістри та пам'ять нового процесу користувальницької програми, після чого переходить до точки входу та перемикає процесор на 3
Якщо програма намагається зробити щось неслухняне, наприклад, записати в заборонений регістр або адресу пам'яті (через підкачку), процесор також викликає деякий обробник зворотних викликів ядра в кільці 0.
Але оскільки користувальницька ділянка була неслухняною, ядро може цього разу вбити процес або надати йому попередження сигналом.
Коли ядро завантажується, воно встановлює апаратний годинник з певною фіксованою частотою, яка генерує періодично переривання.
Цей апаратний годинник генерує переривання, які виконують кільце 0, і дозволяють йому планувати, які процеси користувача пробуджуються.
Таким чином, планування може відбутися, навіть якщо процеси не здійснюють жодних системних викликів.
Який сенс мати кілька кілець?
Є дві основні переваги розділення ядра та користувальницької області:
Як з цим пограти?
Я створив налаштування з чистого металу, який повинен бути хорошим способом безпосередньо керувати кільцями: https://github.com/cirosantilli/x86-bare-metal-examples
На жаль, у мене не вистачило терпіння зробити приклад користувача, але я все-таки зайшов у налаштування підкачки, тому поле користування повинно бути здійсненим. Я хотів би побачити прохання про тягнення.
Крім того, модулі ядра Linux запускаються в кільці 0, тому ви можете використовувати їх для випробування привілейованих операцій, наприклад, зчитувати регістри управління: Як отримати доступ до регістрів керування cr0, cr2, cr3 з програми? Помилка сегментації
Ось зручна настройка QEMU + Buildroot, щоб спробувати це, не вбиваючи хоста.
Недоліком модулів ядра є те, що інші kthreads працюють і можуть перешкоджати вашим експериментам. Але теоретично ви можете взяти на себе всі обробники переривань за допомогою модуля ядра та володіти системою, що насправді був би цікавим проектом.
Негативні кільця
Хоча на негативні дзвінки насправді не посилаються в посібнику Intel, фактично існують режими процесора, які мають додаткові можливості, ніж саме кільце 0, і тому добре підходять для імені "негативне кільце".
Одним із прикладів є режим гіпервізора, який використовується у віртуалізації.
Детальнішу інформацію див.
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
Перевірте це за допомогою 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 - У AArch64 рівневої системи програміст Модель - Малюнок D1-1 ілюструє це красиво:
Ситуація з ARM трохи змінилася з появою розширень хостів для віртуалізації ARMv8.1 (VHE) . Це розширення дозволяє ядру ефективно працювати в EL2:
VHE був створений тому, що рішення для віртуалізації в ядрі Linux, такі як KVM, отримали позицію над Xen (див., Наприклад, перехід AWS до KVM, згаданий вище), тому що більшість клієнтів потребують лише VM Linux, і як ви можете собі уявити, будучи все в одному Проект, KVM простіший і потенційно ефективніший, ніж Xen. Тож тепер ядро Linux Linux виступає як гіпервізор у цих випадках.
Зверніть увагу, як ARM, можливо, завдяки перевазі заднього огляду, має кращу конвенцію іменування рівнів привілеїв, ніж x86, без необхідності негативних рівнів: 0 - нижчий і 3 найвищий. Вищі рівні, як правило, створюються частіше, ніж нижчі.
Поточний EL можна запитати за допомогою MRS
інструкції: який поточний режим виконання / виняток тощо?
ARM не вимагає наявності всіх рівнів винятків, щоб дозволити реалізації, які не потребують функції для збереження області чіпів. ARMv8 "Рівень винятку" говорить:
Реалізація може не включати всі рівні виключень. Усі реалізації повинні включати EL0 та EL1. EL2 та EL3 необов'язкові.
Наприклад, QEMU за замовчуванням для EL1, але EL2 та EL3 можна ввімкнути за допомогою параметрів командного рядка: qemu-system-aarch64 введення el1 при емуляції включення a53
Фрагменти коду, протестовані на Ubuntu 18.10.
in
і out
воно доступне для дзвінка 3. TSS може вказати на таблицю дозволів вводу-виводу в поточному завданні, що надає доступ для читання / запису на всі або конкретні порти.
Я збираюся зробити темний удар в темряві і здогадуюсь, що ви говорите про Windows. У двох словах, режим ядра має повний доступ до апаратного забезпечення, але користувальницький режим не робить. Наприклад, багато, якщо не більшість драйверів пристроїв написані в режимі ядра, оскільки їм потрібно контролювати більш детальні деталі свого обладнання.
Дивіться також цю вікі-книгу .
Інші відповіді вже пояснювали різницю між режимом користувача та ядра. Якщо ви дійсно хочете детальніше ознайомитись, вам слід отримати копію Windows Internals , відмінну книгу, яку написали Марк Русинович та Девід Соломон, що описує архітектуру та внутрішні деталі різних операційних систем Windows.
Що
В основному різниця між режимами ядра та користувачем не залежить від ОС і досягається лише обмеженням деяких інструкцій, які можна виконувати лише в режимі ядра за допомогою апаратного проектування. Усі інші цілі, такі як захист пам’яті, можна виконати лише цим обмеженням.
Як
Це означає, що процесор живе або в режимі ядра, або в режимі користувача. Використовуючи деякі механізми, архітектура може гарантувати, що кожен раз, коли він перейде в режим ядра, код ОС буде завантажений для запуску.
Чому?
Маючи цю апаратну інфраструктуру, цього можна досягти в загальних ОС: