Режим ядра, прерывания и системные вызовы

⏱ 30 мин · 🧩 заданий: 5

Два режима процессора

Процессор умеет работать в разных режимах привилегий. На x86 их четыре («кольца» 0–3), но используются два:

Режим ядра (кольцо 0) Режим пользователя (кольцо 3)
Кто работает ядро ОС процессы
Привилегированные инструкции (mov cr3, hlt, in/out, cli) можно исключение
Доступ к памяти вся только страницы с битом U/S = 1
Ошибка может «уронить» всю систему (kernel panic) ядро убьёт только этот процесс

Именно аппаратная защита делает ОС возможной: без неё любая программа могла бы переписать таблицы страниц и читать чужую память.

Три двери в ядро

режим пользователя ┌────────────┬─────────────────┬──────────────┐ │ syscall │ исключение │ прерывание │ │ (fork, │ (деление на 0, │ (таймер, │ │ write) │ page fault) │ клавиатура) │ ▼ ▼ ▼ ═══════════════ граница режимов ═══════════════════ режим ядра обработчик из таблицы (IDT / таблица syscall)
  1. Аппаратные прерывания (interrupts) — асинхронные сигналы от устройств: таймер, клавиатура, сетевая карта, диск. Не связаны с текущей инструкцией.
  2. Исключения (exceptions) — синхронные, вызваны конкретной инструкцией: - отказы (faults) — инструкцию можно повторить после исправления: страничный отказ (page fault); - ловушки (traps) — после инструкции: точка останова отладчика; - аварии (aborts) — неисправимые: аппаратная ошибка.
  3. Системные вызовы — программа намеренно просит ядро об услуге специальной инструкцией.

Что ядро делает с исключением

Исключение Реакция ядра
Страничный отказ, страница допустима, но не в памяти загрузить страницу, повторить инструкцию — процесс ничего не заметит
Страничный отказ на недопустимом адресе послать SIGSEGV
Деление на ноль послать SIGFPE
Недопустимая инструкция послать SIGILL
Ошибка шины / невыровненный доступ / доступ за конец файла в mmap послать SIGBUS

Связь с задачей 1

Столбец «связан с аппаратным событием» в таблице сигналов — это сигналы, которые ядро посылает в ответ на исключения процессора: SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGTRAP.

Как выполняется системный вызов

На x86-64 Linux:

/* то, что делает обёртка write() в libc, упрощённо */
long ret;
asm volatile ("syscall"
              : "=a"(ret)
              : "a"(1),          /* rax = номер вызова: 1 = write */
                "D"(fd),         /* rdi = 1-й аргумент */
                "S"(buf),        /* rsi = 2-й */
                "d"(count)       /* rdx = 3-й */
              : "rcx", "r11", "memory");
if (ret < 0) { errno = -ret; ret = -1; }
  1. Обёртка кладёт номер вызова в rax, аргументы — в регистры.
  2. Инструкция syscall переключает процессор в кольцо 0 и прыгает на точку входа ядра.
  3. Ядро сохраняет регистры пользователя (контекст) на стеке ядра этого процесса.
  4. По номеру выбирает обработчик из таблицы системных вызовов (sys_call_table).
  5. Обработчик проверяет аргументы (указатели должны вести в память пользователя!) и выполняет работу.
  6. Результат — в rax; sysret возвращает в режим пользователя.

На старых 32-битных системах для этого использовалась программная ловушка int 0x80.

Аналогия

Системный вызов — окошко в банке. Посетитель (процесс) не может зайти в хранилище сам; он заполняет бланк (регистры), отдаёт в окошко (syscall), сотрудник (ядро) проверяет бланк и выполняет операцию.

Переключение контекста

Когда ядро решает запустить другой процесс (например, по прерыванию таймера), оно выполняет переключение контекста:

  1. сохраняет регистры текущего процесса в его структуре (task_struct в Linux);
  2. переключает адресное пространство — загружает в cr3 адрес таблиц страниц нового процесса (на большинстве процессоров это сбрасывает TLB);
  3. восстанавливает регистры нового процесса и возвращается в режим пользователя уже в нём.

Переключение контекста дорогое: кроме самих действий, после него TLB и кеши «холодные».

strace

$ strace -f ./prog
execve("./prog", ["./prog"], ...) = 0
mmap(NULL, 16, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0x7f...
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, ...) = 4242
wait4(-1, NULL, 0, NULL) = 4242
write(1, "4241: 4242\n", 11) = 11
exit_group(0) = ?

Обратите внимание: fork() в glibc выполняется системным вызовом clone с флагом SIGCHLD — к этому вернёмся в уроке о потоках.

Итог

  • Процессор различает режимы ядра и пользователя; привилегированные инструкции в режиме пользователя вызывают исключение.
  • В ядро попадают через прерывания (асинхронно), исключения (синхронно) и системные вызовы.
  • Аппаратные исключения превращаются в сигналы SIGSEGV, SIGBUS, SIGFPE, SIGILL.
  • Системный вызов: номер в rax, инструкция syscall, таблица вызовов, возврат через sysret.
  • Переключение контекста меняет cr3 и сбрасывает TLB.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Заполнить таблицу

К какому типу перехода в ядро относится каждое событие?

Аппаратное прерываниеИсключение (синхронное)Системный вызов
Нажата клавиша на клавиатуре
Деление целого числа на ноль
Обращение к странице, которой нет в памяти
Программа вызвала fork()
Сработал таймер — истёк квант времени
Диск закончил чтение блока (DMA)
Программа вызвала write()
Отметьте ячейку — «+», пустая — «−».
Задание 2 Расставить по порядку

Расставьте шаги выполнения системного вызова write(1, buf, 5) на x86-64 Linux:

  1. Выполняется инструкция syscall: процессор переходит в режим ядра на адрес входа из регистра MSR
  2. Результат кладётся в rax, регистры восстанавливаются, sysret возвращает в режим пользователя
  3. Обёртка libc кладёт номер вызова (1 = write) в rax, аргументы — в rdi, rsi, rdx
  4. Ядро сохраняет регистры пользователя на стеке ядра
  5. По номеру из rax ядро находит обработчик в таблице системных вызовов и вызывает sys_write
  6. Обёртка libc при отрицательном результате записывает код в errno и возвращает −1
Задание 3 Один вариант ответа

Что произойдёт, если программа в режиме пользователя попытается выполнить привилегированную инструкцию (например, записать в cr3)?

Задание 4 Несколько вариантов

Какие сигналы ядро посылает процессу в ответ на аппаратные исключения?

Задание 5 Ввод ответа

Какой утилитой можно посмотреть все системные вызовы, которые делает программа? (только название)

Войдите, чтобы отмечать пройденные уроки.