open atlas
↑ К треку
Linux: операционная система LIN · 01 · 01

Ядро и пользовательское окружение

Ядро работает в ring 0 и владеет каждым CPU, страницей памяти и устройством. Твои программы работают в ring 3 и могут взаимодействовать с оборудованием только через syscall — контролируемое пересечение границы привилегий.

LIN Junior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

Ты вводишь ls в терминале. Оболочка вызывает openat(), чтобы прочитать директорию, затем write(), чтобы отправить имена на экран. Обе операции касаются железа — диска и дисплея — но код оболочки никогда не общается с железом напрямую. Между ними есть посредник: ядро.

Разделение между «кодом, владеющим железом» (ядро) и «кодом, который не владеет» (всё остальное — оболочка, интерпретатор Python, nginx) — это важнейшая структурная граница в операционной системе. Каждый сбой, каждая ошибка прав доступа, каждый segfault, каждый побег из контейнера, которые ты когда-либо будешь отлаживать, затрагивают эту границу. Понимание её — предпосылка для понимания всего остального в этом треке.

Цель

После этого урока ты сможешь объяснить, чем владеет ядро, что такое userland и зачем он существует, что такое syscall и почему он является единственной точкой пересечения, а также что означают кольца привилегий CPU на практике.

1

Ядро владеет железом — единолично. При загрузке Linux ядро загружается первым и немедленно берёт под контроль каждое ядро процессора, каждую страницу памяти, каждую сетевую карту, каждый дисковый контроллер. Никакое другое программное обеспечение не может напрямую прочитать байт с диска или отправить пакет без участия ядра. Это не соглашение и не рекомендация — это аппаратное ограничение CPU.

Процессоры реализуют уровни привилегий, называемые кольцами. Ring 0 (режим ядра) — наиболее привилегированный: код здесь может выполнять любую инструкцию, отображать любую память, работать с любым устройством. Ring 3 (пользовательский режим) — наименее привилегированный: код здесь не может напрямую обращаться к железу. Современные процессоры x86-64 и ARM на практике используют только кольца 0 и 3.

Ядро работает в ring 0. Каждый процесс, который ты запускаешь — твоя оболочка, база данных, редактор — работает в ring 3.

2

Userland — это всё, что выше ядра. Как только ядро заканчивает загрузку, оно запускает первый процесс userland (PID 1, которым становится systemd или другой init). Дальше каждая программа — bash, nginx, Python, весь экосистемный стек пакетов — это процесс userland. Их объединяет одно: они не могут работать с железом напрямую.

Это разделение защищает систему. Ошибка в коде твоего приложения не может повредить память ядра — процессор сначала поднимет исключение. Вредоносная программа не может открыть сырой сокет и перехватывать сетевой трафик без запроса к ядру, которое проверит права доступа.

Практическое следствие: если твоя программа падает, она падает. Ядро продолжает работать. Если падает ядро — вся машина идёт вниз. Это называется kernel panic.

3

Syscall — это то, как userland просит ядро выполнить привилегированную работу. Когда твоей программе нужно прочитать файл, записать в stdout, открыть сокет или форкнуть дочерний процесс, она выполняет syscall. Процессор переходит из ring 3 в ring 0 контролируемым образом — программа не может выбрать, что происходит в ring 0, только какой syscall вызвать и с какими аргументами.

# strace показывает каждый syscall процесса
strace ls /tmp 2>&1 | head -20

Ты увидишь вызовы вроде openat(AT_FDCWD, "/tmp", O_RDONLY|O_DIRECTORY) и getdents64(...). Это ls просит ядро открыть директорию и прочитать её содержимое. Ядро проверяет, есть ли у ls права доступа, выполняет аппаратную работу, затем возвращает результат в ring 3.

В Linux около 400 системных вызовов. Ты используешь десятки из них при каждой команде, но они скрыты за обёртками библиотек. fopen() в C вызывает openat() под капотом. Python-вский open() тоже в итоге вызывает openat().

4

Граница syscall поддаётся аудиту и принуждению. Поскольку каждое взаимодействие с железом должно пересекать эту границу, ядро может логировать, фильтровать и блокировать syscall. seccomp (secure computing mode) позволяет ограничить, какие syscall вправе вызывать процесс. Docker использует это, чтобы запретить контейнерам вызывать опасные syscall, такие как reboot() или kexec_load(). strace перехватывает каждое пересечение для отладки.

# Посмотреть, какие syscall использует процесс (сводка)
strace -c ls /tmp

Вывод показывает счётчики и время на syscall. Процесс, который ни разу не вызвал socket(), почти наверняка не имеет сетевой активности — это значимое утверждение безопасности.

5

Стоимость syscall реальна, но невелика. Пересечение границы кольца требует от CPU сохранения состояния процесса, переключения на стек ядра, выполнения обработчика syscall, затем восстановления состояния и возврата. На современном железе это стоит примерно 100–300 нс на пересечение. Для большинства программ это пренебрежимо мало. Для высокопроизводительного I/O-кода (база данных, пишущая миллионы строк, прокси, обрабатывающий миллионы пакетов) суммарная стоимость имеет значение — поэтому разработчики ядра вкладываются в пакетные API вроде io_uring, позволяющие userland ставить в очередь много операций и пересекать границу один раз.

Понимание этой стоимости — вот почему ты позже узнаешь о sendfile(), zero-copy networking и почему отображение файлов в память может обгонять циклы read().

Разбор примера

Проследи, что происходит при запуске cat /etc/hostname.

strace cat /etc/hostname 2>&1 | grep -E 'open|read|write'

Ты увидишь:

  1. openat(AT_FDCWD, "/etc/hostname", O_RDONLY) — ядро открывает файл, возвращает файловый дескриптор (целое число).
  2. read(3, "myserver\n", 131072) — ядро читает байты с диска в буфер ядра, копирует их в userland. 3 — файловый дескриптор.
  3. write(1, "myserver\n", 9) — ядро записывает байты из памяти userland в stdout (fd 1), который отображает терминальный драйвер.

Три syscall, три пересечения кольца. Файл не «перемещается» с диска в терминал — ядро посредничает на каждом шаге. cat сам по себе ничего не делает, кроме как вызывает нужные syscall в нужном порядке.

Почему это работает

macOS использует ядро BSD-происхождения (XNU) с той же моделью колец, но разными номерами и соглашениями syscall. strace на macOS не существует — используй dtruss (требует root) или dtrace. Концептуальное разделение (ядро/userland/граница syscall) идентично; детали реализации различаются. Везде в этом треке используются имена и номера syscall Linux.

Частая ошибка

Распространённое заблуждение: «ядро — это просто программа». Это программа в том смысле, что это скомпилированный код на C — но работает она на уровне привилегий, который делает её категорически иной. Ядро нельзя убить с помощью kill. Его нельзя отлаживать с помощью gdb из userland. У него нет родительского процесса. Когда ты слышишь «ОС зависла», это ядро в панике — единственный способ восстановить работу — перезагрузка.

Проверь себя
Викторина

Процесс в ring 3 должен записать байт на диск. Что должно произойти сначала?

Итог

Ядро работает в ring 0 и владеет каждым аппаратным ресурсом машины. Процессы userland работают в ring 3 и физически лишены CPU возможности касаться железа. Единственная точка пересечения — граница syscall: контролируемая ловушка из ring 3 в ring 0, которую ядро использует для проверки прав и выполнения привилегированной работы. Каждое чтение файла, сетевой пакет и форк процесса пересекают эту границу. Инструменты вроде strace делают её видимой. Стоимость — ~100–300 нс на пересечение: пренебрежимо мало для большинства кода, существенно для высокопроизводительного I/O.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources1
expand
  1. 01

Trademarks belong to their respective owners. Editorial reference only.