- PVSM.RU - https://www.pvsm.ru -
Привет всем!
В большинстве книг по Linux, ядро — это такая священная корова или, как говорят, "черный ящик". Мы работаем в командной строке, юзаем утилиты, а где-то там, за занавесом, этот ящик творит чудеса, чтобы всё работало.
Я решил сам разобраться и доказать: ядро Linux — это просто исполняемый файл. Никакой магии. Его можно взять, скомпилировать (или просто скопировать) и запустить, как любой другой бинарник.
Сейчас мы проделаем пару простых но крутых экспериментов. Цель не столько повторить их, сколько построить в голове четкую картину, как вообще Linux устроен и как его компоненты общаются.
Но сперва — а что это вообще за ядро?
Ваш компьютер — это куча железа: процессор, память, сетевуха, видеокарта. Все это добро от разных производителей, и каждое требует своего языка.
Ядро ОС — это посредник.
Оно дает нам единый API для общения с железом. Не надо писать код под NVIDIA, Realtek и AMD одновременно. Мы просто просим у ядра доступ, а оно уже разруливает с конкретным устройством.
Оно — главный диспетчер. Решает, какой программе дать процессорное время, сколько оперативы и какой доступ к файлам.
По сути, ядро — это runtime для всего компьютера.
Найти ядро легко, оно лежит в /boot. Переходим туда и смотрим:
~$ cd /boot
/boot$ ls -1
System.map [1]-6.12.48+deb13-amd64
vmlinuz-6.12.48+deb13-amd64 # Вот оно!
Наш главный файл — vmlinuz-6.12.48+deb13-amd64. Это и есть сжатый образ ядра.
Кстати, если интересно, что значит имя: vmlinuz — это vm (виртуальная память), linu (Linux) и z (сжатый). А дальше идет версия и архитектура (amd64). В общем, просто имя файла.
Давайте скопируем его и запустим, но не на реальном железе (не хотим ломать основную систему), а внутри QEMU — виртуальной машины-эмулятора.
Подготовка:
~$ mkdir kernel-play
~$ cd kernel-play/
~/kernel-play$ cp /boot/vmlinuz-6.12.48+deb13-amd64
Установка QEMU:
~$ sudo apt update
~$ sudo apt install -y qemu-system-x86 qemu-utils
Запускаем ядро! Даем ему 256 МБ памяти, указываем, какой файл использовать как ядро, и выводим консоль в наш терминал
~/kernel-play$ qemu-system-x86_64
-m 256M
-kernel vmlinuz-6.12.48+deb13-amd64
-append "console=ttyS0"
-nographic
И вот тут начинается самое интересное. Ядро стартует, вываливает кучу логов про инициализацию, а потом... падает с ошибкой:
[ 2.181875] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
Ожидаемо. Ядро запустилось, сделало все свои внутренние дела, а потом говорит: "Окей, я готов. Где моя файловая система и где программа, которую мне надо запустить первой (process init)?" А их нет. Поэтому — паника.
Это отлично доказывает: Ядро — это просто программа, которая выполняет свою часть работы, а потом ждет, что его работу продолжит кто-то еще.
Чтобы ядро не рухнуло, нам нужно дать ему минимальный набор файлов, включая ту самую первую программу, которая называется процесс init (PID 1).
Для этого мы сделаем initramfs (Initial RAM filesystem) — временную файловую систему, которая грузится прямо в память, пока ядро не найдет нормальный диск.
Наша программа init на Go Go идеален, потому что делает статические бинарники — им не нужны никакие внешние библиотеки, они работают везде. Я написал простую программу, которая просто печатает "Hello" и "тикает" каждую пару секунд.
# Создаем и компилируем ~/kernel-play$ mkdir init && cd init # ... создаем файл main.go и компилируем ... ~/kernel-play/init$ CGO_ENABLED=0 go build -o init .
Сборка минимальной ФС Нам нужны каталоги /proc, /sys, /dev и, конечно, наша программа init.
~/kernel-play$ mkdir -p rootfs/{proc,sys,dev}
~/kernel-play$ cp ./init/init rootfs/init
# Создаем нужные узлы для консоли
~/kernel-play$ sudo mknod rootfs/dev/console c 5 1
# ...
Теперь пакуем все это в архив, который и будет нашим initramfs.img:
~/kernel-play$ ( cd rootfs && find . | cpio -H newc -o ) > initramfs.img
Второй запуск! Теперь мы даем QEMU ядро (-kernel), нашу маленькую файловую систему (-initrd) и явно говорим ядру, какую программу запускать первой (rdinit=/init).
~/kernel-play$ qemu-system-x86_64
-m 256M
-kernel vmlinuz-6.12.48+deb13-amd64
-initrd initramfs.img
-append "console=ttyS0 rdinit=/init"
-nographic
Успех!
Ядро снова инициализируется, но в этот раз оно находит нашу initramfs.img и запускает наш Go-бинарник.
...
[ 2.672446] Run /init as init process
Hello from Go init!
PID: 1
tick 0
tick 1
...
Наша простая программа на Go получила PID 1! Это и есть самый первый процесс, который теперь, по идее, должен запускать systemd или sysvinit или что у вас там — то есть все остальное, что нужно для работы ОС. Мы только что сделали свою, пусть и крайне примитивную, но рабочую сборку Linux.
Ядро — всего лишь файл, который грузится первым и инициализирует железо.
Дистрибутив Linux — это просто ядро, к которому прикрутили набор утилит и конфигурационных файлов (наш Go-файл и rootfs).
PID 1 — это всегда первый процесс, который запускает всё.
Всё, что до запуска init — это пространство ядра (Kernel Space). Всё, что после — пользовательское пространство (User Space), где работают все наши приложения.
Черный ящик стал прозрачным. Это просто код, который можно собрать и запустить.
Автор: PrincePercia
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/441726
Ссылки в тексте:
[1] System.map: http://System.map
[2] Источник: https://habr.com/ru/articles/984706/?utm_source=habrahabr&utm_medium=rss&utm_campaign=984706
Нажмите здесь для печати.