Раздел 02 — Архитектура ОС

Как программа просит ядро о помощи, из чего состоит ядро и что происходит от нажатия кнопки питания до входа в систему

Прогресс курса Раздел 2 из 12

Что вы освоите в этом разделе

6 академических часов: 2 теории, 4 практики. Команды Windows — в терминале от имени администратора (bcdedit и Confirm-SecureBootUEFI без этого не работают). Примеры Linux повторите в своей ВМ после раздела 03; strace ставится командой sudo apt install strace.
01

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

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

У процессоров x86 режимы называют кольцами защиты: кольцо 0 — ядро, кольцо 3 — программы; кольца 1 и 2 современные ОС не используют. Если программа в кольце 3 попробует выполнить запрещённую команду или обратиться к чужой памяти, процессор остановит её и передаст управление ядру — а ядро завершит программу с ошибкой.

Windows считает, сколько времени процесс провёл в каждом режиме. UserProcessorTime — работа в пользовательском режиме, PrivilegedProcessorTime — в режиме ядра:

PS> Get-Process -Name lsass, explorer, System | Select-Object ProcessName, UserProcessorTime, PrivilegedProcessorTime
ProcessName UserProcessorTime PrivilegedProcessorTime
----------- ----------------- -----------------------
explorer    00:00:26.7187500  00:00:27.7343750
lsass       00:01:12.8593750  00:01:20.4062500
System      00:00:00          00:31:52.1718750

У процесса System пользовательского времени нет совсем: это не программа, а потоки самого ядра. У lsass (проверка паролей и прав) и Проводника время делится примерно поровну: значительную часть работы за них делает ядро — читает файлы, рисует окна, проверяет доступ.

02

Системный вызов

Программе в пользовательском режиме всё равно нужно читать файлы и выводить текст. Для этого она делает системный вызов — просит ядро выполнить действие за неё:

  1. Программа кладёт номер вызова и параметры в регистры и выполняет специальную команду процессора (syscall на x86-64).
  2. Процессор переключается в режим ядра и переходит в заранее заданную точку ядра — программа не может выбрать, куда прыгнуть.
  3. Ядро проверяет параметры и права: есть ли файл, можно ли его читать этому пользователю.
  4. Ядро выполняет работу и возвращает результат, процессор переключается обратно в пользовательский режим.

Программы обычно не делают вызовы сами, а пользуются библиотекой: в Linux — libc (функция read вызывает системный вызов read), в Windows — функции Win32 API из kernel32.dll, которые через ntdll.dll вызывают ядро (ReadFile → NtReadFile).

В Linux системные вызовы любой программы показывает strace. Посмотрим, как cat выводит файл /etc/hostname — имя компьютера. Трассу пишем в файл trace.txt, чтобы она не смешалась с выводом самой программы, а при просмотре убираем вызовы загрузки библиотек и закрытия служебных файлов:

$ strace -o trace.txt -e trace=openat,read,write,close cat /etc/hostname
mypc
$ grep -v -e "\.so" -e ELF -e "^close(3)" trace.txt
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/hostname", O_RDONLY) = 3
read(3, "mypc\n", 262144)               = 5
write(1, "mypc\n", 5)                   = 5
read(3, "", 262144)                     = 0
close(1)                                = 0
close(2)                                = 0
+++ exited with 0 +++

Первым openat программа открыла файл языковых настроек (локали) — так начинает почти любая программа. Дальше вся полезная работа: openat открыл /etc/hostname и получил номер 3 (дескриптор), read прочитал 5 байт, write записал их в дескриптор 1 — стандартный вывод, то есть на экран; второй read вернул 0 — конец файла. В конце close закрывает стандартный вывод и вывод ошибок. Ключ -c вместо подробностей считает вызовы:

$ strace -c -f ls /etc > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 32,44    0,000133          66         2           getdents64
 20,24    0,000083           8        10           close
 11,46    0,000047           5         8           openat
  9,76    0,000040           4         9           fstat
  5,85    0,000024           1        23           mmap
  5,85    0,000024          12         2         2 ioctl
  4,39    0,000018          18         1           statx
  4,15    0,000017           8         2         2 access
  3,17    0,000013          13         1           write
  2,68    0,000011           1         6           read
  0,00    0,000000           0         6           mprotect
  0,00    0,000000           0         1           munmap
  0,00    0,000000           0         3           brk
  0,00    0,000000           0         2           pread64
  0,00    0,000000           0         1           execve
  0,00    0,000000           0         2         2 statfs
  0,00    0,000000           0         6         4 prctl
  0,00    0,000000           0         1           arch_prctl
  0,00    0,000000           0         1           set_tid_address
  0,00    0,000000           0         1           set_robust_list
  0,00    0,000000           0         1           prlimit64
  0,00    0,000000           0         1           getrandom
  0,00    0,000000           0         1           rseq
------ ----------- ----------- --------- --------- ----------------
100,00    0,000410           4        91        10 total

Чтобы вывести список одной папки, ls сделал почти сто вызовов; большая часть — mmap и mprotect: загрузка библиотек при старте программы.

Переход в ядро и обратно стоит времени. Скопируем 2 МБ двумя способами: по 1 байту за вызов и по 1 МБ:

$ time dd if=/dev/zero of=/dev/null bs=1 count=2000000
2000000+0 records in
2000000+0 records out
2000000 bytes (2,0 MB, 1,9 MiB) copied, 3,70452 s, 540 kB/s

real	0m3,707s
user	0m1,832s
sys	0m1,872s
$ time dd if=/dev/zero of=/dev/null bs=1M count=2
2+0 records in
2+0 records out
2097152 bytes (2,1 MB, 2,0 MiB) copied, 0,000633011 s, 3,3 GB/s

real	0m0,002s
user	0m0,002s
sys	0m0,000s

Данных столько же, а разница — в тысячи раз. Строка sys — время в режиме ядра, user — в пользовательском. При bs=1 программа сделала четыре миллиона вызовов (два миллиона read и два миллиона write), и около половины времени ушло на работу в ядре. Отсюда правило, которое пригодится и администратору, и программисту: читать и писать крупными блоками, а не по байту.

03

Как устроено ядро

Главный вопрос устройства ядра — что работает в режиме ядра, а что вынести в пользовательский режим.

АрхитектураЧто в режиме ядраПлюсыМинусыПримеры
Монолитное ядроВсё: процессы, память, файловые системы, сеть, драйверыБыстро: части ядра вызывают друг друга напрямуюОшибка любого драйвера роняет всю системуLinux, FreeBSD
МикроядроМинимум: процессы, память, передача сообщенийНадёжно: упавший драйвер перезапускается, как программаМедленнее: части общаются сообщениями через ядроQNX, MINIX 3
Гибридное ядроПочти всё, как в монолитном, но устроено слоями и модулямиСкорость монолита и часть гибкости микроядраОшибка драйвера всё равно роняет системуWindows NT, macOS (XNU)

Различие на практике видно по тому, что бывает при ошибке драйвера. В Windows — «синий экран» (BSOD) и перезагрузка, в Linux — kernel panic. В QNX упавший драйвер перезапускается, а система работает дальше — поэтому микроядро выбирают там, где остановка недопустима: в автомобилях и на производстве.

Монолитное ядро Linux при этом расширяется на ходу: драйверы и файловые системы оформлены модулями ядра, которые загружаются по необходимости. Модуль, загруженный в ядро, работает в режиме ядра с теми же правами, что и само ядро.

Ядро Windows — файл ntoskrnl.exe. В нём две части: исполнительная система (диспетчер процессов, памяти, ввода-вывода, безопасности) и собственно ядро (планирование потоков, прерывания). Под ними — уровень абстракции оборудования HAL, который прячет различия материнских плат.

PS> Get-Item C:\Windows\System32\ntoskrnl.exe | Select-Object Name, @{n="Описание"; e={$_.VersionInfo.FileDescription}}, @{n="Версия"; e={$_.VersionInfo.ProductVersion}}
Name         Описание           Версия
----         --------           ------
ntoskrnl.exe NT Kernel & System 10.0.26100.9711
04

Драйверы и процессы ядра

Драйвер — программа, которая знает, как управлять конкретным устройством, и даёт ядру единый интерфейс к нему. В Windows драйверы — файлы .sys в C:\Windows\System32\drivers:

PS> (Get-CimInstance Win32_SystemDriver | Where-Object State -eq Running).Count
123
PS> Get-CimInstance Win32_SystemDriver | Where-Object State -eq Running | Select-Object -First 8 Name, PathName
Name          PathName
----          --------
ACPI          C:\WINDOWS\system32\drivers\ACPI.sys
acpiex        C:\WINDOWS\system32\Drivers\acpiex.sys
AFD           C:\WINDOWS\system32\drivers\afd.sys
afunix        C:\WINDOWS\system32\drivers\afunix.sys
ahcache       C:\WINDOWS\system32\DRIVERS\ahcache.sys
amdwps        C:\WINDOWS\system32\drivers\amdwps.sys
AppID         C:\WINDOWS\system32\drivers\appid.sys
applockerfltr C:\WINDOWS\system32\drivers\applockerfltr.sys

Больше сотни драйверов работают даже в виртуальной машине без принтеров и камер: шины, диски, файловые системы, сеть, защита. Многие из них — не драйверы устройств, а части ядра, оформленные как драйверы: AFD — сокеты, AppID — проверка разрешённых программ.

В Linux драйверы — модули ядра, их список показывает lsmod:

$ lsmod | wc -l
104
$ lsmod
Module                  Size  Used by
dm_mod                221184  0
cpuid                  12288  0
xt_nat                 12288  0
xt_tcpudp              16384  0
xt_conntrack           12288  1
…

У ядра есть свои процессы — точнее, потоки, которые работают только в режиме ядра. В Windows это процесс System с номером 4 и Idle с номером 0 — «процесс простоя», который занимает процессор, когда больше нечего делать:

PS> Get-Process -Id 0, 4 | Select-Object Id, ProcessName
Id ProcessName
-- -----------
 0 Idle
 4 System

В Linux процесс номер 1 — systemd, первый процесс пользовательского режима: он запускает все службы. Номер 2 — kthreadd, родитель всех потоков ядра; в списке процессов их имена в квадратных скобках:

$ ps -o pid,ppid,comm -p 1,2
    PID    PPID COMMAND
      1       0 systemd
      2       0 kthreadd
$ ps -ef | grep -F "[" | grep -v grep
root           2       0  0 авг26 ?     00:00:02 [kthreadd]
root           3       2  0 авг26 ?     00:00:00 [pool_workqueue_release]
root           4       2  0 авг26 ?     00:00:00 [kworker/R-kvfree_rcu_reclaim]
root           5       2  0 авг26 ?     00:00:00 [kworker/R-rcu_gp]
root           6       2  0 авг26 ?     00:00:00 [kworker/R-sync_wq]
root           7       2  0 авг26 ?     00:00:00 [kworker/R-slub_flushwq]
root           8       2  0 авг26 ?     00:00:00 [kworker/R-netns]
root          11       2  0 авг26 ?     00:00:00 [kworker/0:0H-events_highpri]
…
$ ps -e --no-headers | wc -l
137
$ ps --ppid 2 --no-headers | wc -l
72

Половина «процессов» на сервере — потоки ядра: они пишут данные на диск, обслуживают сеть, освобождают память.

05

Загрузка: от кнопки питания до входа

При включении в памяти нет ОС. Её загружает цепочка программ, каждая из которых запускает следующую.

ЭтапЧто происходитWindowsLinux
1. ПрошивкаПроверка оборудования (POST), выбор диска загрузкиUEFI (или старый BIOS) на материнской плате
2. ЗагрузчикНаходит ОС, показывает меню, загружает ядроДиспетчер загрузки bootmgfw.efi, настройки — хранилище BCDGRUB, настройки — /boot/grub/grub.cfg
3. ЯдроИнициализирует память, процессор, драйверы, монтирует системный дискwinload.efi загружает ntoskrnl.exe и драйверы загрузкиvmlinuz и initrd.img — временная система с драйверами дисков
4. Первый процессЗапускает службы и вход в системуsmss.exe → wininit.exe (службы), winlogon.exe (вход)systemd (PID 1)
5. ВходПользователь вводит пароль, запускается оболочкаЭкран входа → ПроводникЭкран входа или login → bash

UEFI заменил BIOS: работает с дисками больше 2 ТБ (разметка GPT, раздел 03), загружает файлы .efi с отдельного небольшого раздела ESP и поддерживает безопасную загрузку (Secure Boot) — запускает только загрузчики с доверенной цифровой подписью, чтобы вредоносная программа не встроилась до запуска ОС. Windows 11 требует UEFI и Secure Boot.

PS> $env:firmware_type
UEFI
PS> Confirm-SecureBootUEFI
True

Настройки Диспетчера загрузки Windows хранятся в базе BCD и выводятся командой bcdedit. {bootmgr} — сам Диспетчер, {current} — запись загруженной сейчас Windows:

PS> bcdedit /enum "{bootmgr}"
Диспетчер загрузки Windows
--------------------
идентификатор           {bootmgr}
device                  partition=\Device\HarddiskVolume1
path                    \EFI\Microsoft\Boot\bootmgfw.efi
description             Windows Boot Manager
locale                  ru-RU
inherit                 {globalsettings}
default                 {current}
resumeobject            {3e8baebd-d2f9-11f1-9894-f58ab4346bd2}
displayorder            {current}
toolsdisplayorder       {memdiag}
timeout                 30
PS> bcdedit /enum "{current}"
Загрузка Windows
-------------------
идентификатор           {current}
device                  partition=C:
path                    \WINDOWS\system32\winload.efi
description             Windows 11
locale                  ru-RU
inherit                 {bootloadersettings}
recoverysequence        {3e8baebf-d2f9-11f1-9894-f58ab4346bd2}
displaymessageoverride  Recovery
recoveryenabled         Yes
isolatedcontext         Yes
allowedinmemorysettings 0x15000075
osdevice                partition=C:
systemroot              \WINDOWS
resumeobject            {3e8baebd-d2f9-11f1-9894-f58ab4346bd2}
nx                      OptIn
bootmenupolicy          Standard

Видно всю цепочку: Диспетчер \EFI\Microsoft\Boot\bootmgfw.efi лежит на разделе ESP, ждёт выбора timeout секунд и запускает {current} — загрузчик winload.efi с диска C:, который загружает систему из \WINDOWS. Если загрузка не удалась, используется recoverysequence — среда восстановления.

bcdedit /set и /delete меняют загрузку, и ошибка в них оставляет компьютер незагружаемым. Пробуйте их только в виртуальной машине и только после снимка (раздел 03). Безопасный способ поменять загрузку на учебном ПК — Win+R → msconfig → вкладка «Загрузка»: там включается безопасный режим и журнал загрузки драйверов (C:\Windows\ntbtlog.txt).

Когда система загрузилась, ядро записывает событие в журнал «Система». Время последней загрузки:

PS> Get-CimInstance Win32_OperatingSystem | Select-Object LastBootUpTime
LastBootUpTime
--------------
29.10.2026 21:06:18
PS> Get-WinEvent -FilterHashtable @{LogName="System"; ProviderName="Microsoft-Windows-Kernel-General"; Id=12} -MaxEvents 1 | Format-List TimeCreated, Message
TimeCreated : 29.10.2026 21:06:16
Message     : Системное время запуска операционной системы: 2026-10-29T18:06:16.500000000Z.

В Linux ядра лежат в /boot: по файлу vmlinuz и initrd.img на каждую версию. Старые версии хранятся, чтобы загрузиться с них, если новое ядро не заработает:

$ ls /boot
config-6.12.113+deb13-amd64
config-6.12.43+deb13-amd64
config-6.12.95+deb13-amd64
grub
initrd.img-6.12.113+deb13-amd64
initrd.img-6.12.43+deb13-amd64
initrd.img-6.12.95+deb13-amd64
System.map-6.12.113+deb13-amd64
System.map-6.12.43+deb13-amd64
System.map-6.12.95+deb13-amd64
vmlinuz-6.12.113+deb13-amd64
vmlinuz-6.12.43+deb13-amd64
vmlinuz-6.12.95+deb13-amd64
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.12.95+deb13-amd64 root=UUID=97c23eb6-eee2-49d6-917b-29ce316c376b ro quiet
$ systemd-analyze
Startup finished in 2.521s (kernel) + 3.211s (userspace) = 5.732s 
graphical.target reached after 3.210s in userspace.
$ systemd-analyze blame
2.427s postgresql@17-main.service
1.171s dev-sda1.device
 874ms docker.service
 803ms fstrim.service
 681ms apt-daily-upgrade.service
 677ms apt-daily.service
…

/proc/cmdline — параметры, с которыми GRUB запустил ядро: какой файл ядра, какой диск сделать корневым (root=UUID=…), ro — сначала смонтировать его только для чтения, quiet — меньше сообщений на экране. systemd-analyze делит время загрузки на ядро и пользовательский режим, а blame показывает, какие службы запускались дольше всех, — с этого начинают, когда сервер долго загружается.

Обратите внимание: в /boot есть ядро 6.12.113, а /proc/cmdline показывает загруженное 6.12.95. Значит, обновление ядра установлено, но сервер с тех пор не перезагружали, и работает старое ядро. Новое ядро начинает работать только после перезагрузки — это нужно планировать после каждого обновления безопасности.

Ключевые выводы раздела

Запомните главное

  • Ядро и драйверы работают в привилегированном режиме (кольцо 0), программы — в пользовательском (кольцо 3) и не могут напрямую трогать оборудование и чужую память
  • Системный вызов — просьба программы к ядру; каждый вызов стоит времени, поэтому данные читают и пишут крупными блоками
  • Linux — монолитное ядро с модулями, Windows — гибридное, QNX — микроядро; ошибка драйвера в монолитном и гибридном ядре роняет всю систему
  • Загрузка: UEFI → загрузчик (Диспетчер загрузки Windows или GRUB) → ядро → первый процесс (smss.exe или systemd) → вход
  • Загрузку Windows описывает BCD (bcdedit), Linux — GRUB и /boot; новое ядро работает только после перезагрузки
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Раздел 01: Операционная система Все разделы Раздел 03: Установка ОС и виртуальные машины