GNU Linux - andyceo/documentation GitHub Wiki

Операционная система GNU-Linux

История Linux:

  • GNU
  • Minix (Эндрю Танненбаум, не очень свободная лицензия, не приняли энтузиасты)
  • 386BSD (появилась чуть раньше Linux, но на момент появления Linux была недоступна для персональных компьютеров)
  • Linux (25.08.1991 - Торвальдс опубликовал в списке рассылки Minix (comp.os.minix) сообщение о том, что написал операционку)

Нити процесса внутри линукса называются tasks - это термин, который произвела Intel.

Каким был загрузчик первой версии Linux?

Его еще не было! не было Lilo, Grub.

Как грузился Linux?

ls /boot

там лежат ядра.

file /boot/vmlinuz....

Увидим информацию о файле - что это ядро Linux в формате BZ-image.

file по характерным признакам определяет тип файла, на основании управляющего файла magic:

ls -l /usr/share/file

less /usr/share/file/magic

можно поискать слово kernel, чтобы определить, по каким критериям file решила, что это ядро: по смещению такому-то, если находится определенное число, то...

Теперь посмотрим файл ядра просмотрщиком бинарных файлов hexdump:

hexdump -C -n512 /boot/vmlinuz....

-C - каноническое представление, -n512 - количество байт для вывода

в выведенном дампе, в конце отрезка 512-и байт, будет магическое число 55 aa. Это - MBR. Сравним с MBR вашего жесткого диска:

sudo hexdump -C -n512 /dev/sda

И там последними двумя байтами будет 55 aa - признак загрузочной записи.

Таким образом, Linux загружался с дискеты, загружал самого себя. Ядро записывалось на дискету, без всякой файловой системы, с нулевого смещения и ядро содержало встроенный загрузчик. Сейчас в начале ядра содержится текст о том, что прямая загрузка с флоппи-дисков больше не поддерживается. От загрузчика в ядре осталась только заглушка. Поддержка флоппи была убрана, потому что ядро перестало влезать на дискету.

Загрузчики Linux

  • Grub 1/2 (может грузить любые системы (Linux, Windows, BSD)) 2-й граб имеет встроеннный интерпретатор команд.
  • (e)Lilo - Linux Loader. (e) - поддерживает биосы UEFI.
  • Syslinux (ISOlinux - для загрузки с компакт-диска, PXELinux - для загрузки по сети (Pre-boot execution environment - сервер может по dhcp отдать инфу где сервер протокола PXE, и какой файл для загрузки надо взять, и с него будет получен этот файл загрузчика, и загрузчик дальше получает ядро и выполняет его)
  • uBoot ...

Конфигурация загрузчика

  • /boot/grub/grub.cfg
  • /boot/grub/menu.lst
  • /etc/lilo.conf
  • /syslinux.cfg

Если вы сделали ошибку, изменения в конфигурации Lilo, и забыли перед перезагрузкой выполнить команду lilo, то загрузка будет происходить по старому. Сложно откатиться, если сразу удаляешь старое ядро.

GUID/UUID - уникален во времени и пространстве.

uuidgen

Эта утилита генерирует эти UUID. Этот идентификатор нужен для того, чтобы уникально идентифицировать любой объект. В данном случае - файловую систему, т.к. количество разделов и их расположение, взаимное расположение дисков, непостоянно, и первый диск может стать вторым и т.п. Поэтому загрузчик смотрит все диски, все разделы, и ищет в суперблоке файловой системы указанный идентификатор. Удостоверимся, что этот идентификатор где-то есть:

sudo tune2fs -l /dev/sda1

или тот раздел вместо /dev/sda1, который у вас есть.

В конце вывода мы можем заметить похожую на UUID запись:

Directory Hash Seed:      7b60381b-878a-46e5-8f79-88ea8b66b23e

но это не UUID файловой системы, а вектор инициализации hash-функции, которая позволяет быстро искать файл по имени в каталоге, а вот вверху будет UUID:

Filesystem UUID:          01530837-c80f-4ac9-ad0e-0a48808c5488

Ядро Linux

Ядро бывает по-разному собрано, и перекомпилировать его без крайней необходимости. Особенно этого хотят разработчики ядра.

Но можно и не пересобирать, а подгружать модули по мере необходимости.

  • Гибридное - драйверы дисковых и файловых подсистем загружаются модулями

  • Модули - файлы в /lib/modules/:

      ls /lib/modules
    

Будет столько папок, сколько установлено версий ядра. Внутри каждой папки, лежат модули для данной версии ядра:

    ls /lib/modules/3.13.0-83-generic/kernel
    arch  crypto  drivers  fs  lib  mm  net  sound  ubuntu

Посмотрим, например, драйверы файловых систем:

    ls /lib/modules/3.13.0-83-generic/kernel/fs

Для каждой файловой системы - свой каталог. Мы также не найдем файловую систему ext, т.к. она включена непосредственно в ядро.

    ls /lib/modules/3.13.0-83-generic/kernel/fs | grep ext

или

    lsmod | grep ext

Вышеперечисленные команды ничего не выведут.

Это нетипичная ситуация, она свойственна для Ubuntu, в других системах, поддержка основной файловой системы тоже сделана модулем.

Файловая система ntfs - та что сделана модулем ядра - может только читать ntfs. На запись работает специальный модуль ntfs3g универсальной файловой системы fuse, и грузится модуль ядра fuse, а остальные ФС реализуются как библиотеки, подгружаемые к данному модулю. Можно попробовать найти ее в управляющем файле файловых систем:

    grep ntfs /proc/filesystems

а также:

    mount | grep ntfs

но может найтись fuseblk:

    mount | grep fuse

Как происходит загрузка ядра? 36:57

Как работает bootstrapping.

boot - ботинки, strap - у некоторых сапогов лямка сзади, чтобы было удлбно натягивать голенище, bootstrapping - приподнять себя за эти лямки. Как Мюнхгаузен из болота себя вытягивал. Термин появился до компьютеров, в финансовом мире означает, что компанию удалось сделать на свои же деньги, без заемных средств. Применительно к ядру Линукс означает, что ядро каким-то образом само себя загрузило.

Т.е. BIOS, чтеиние первого сектора диска, а там загрузчик. Загрузчик встроен в ядро (или отдельно от него), и таким образом оно получает управление.

После этого, ядро должно что-то сделать. Т.к. ядро одинаковое, а компьютеры разные, ядро должно об этом как-то узнать, т.е. мы должны его настроить еще до того, как оно запустилось. Здесь ядро ничем не отличается от утилит в командной строке, т.е. оно получает командную строку, даже в Windows так же.

У нас есть служеная ФС /proc, в которой ядро в вмде файлов и каталогов отображает служебную информацию, в частности, оно отображает там свою командную строку:

cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-3.13.0-83-generic root=UUID=01530837-c80f-4ac9-ad0e-0a48808c5488 ro

Туда заносятся разные параметры, например параметр BOOT_IMAGE=... был занесен загрузчиком, чтобы можно было узнать из какого файла произошла загрузка ядра. Самому ядру это не особо интересно, т.к. оно грузится в память целиком сразу. А вот параметр root=... очень важен, т.к. указывает ядру на корневую ФС. Может быть задан в человекопонятном виде, например /dev/sda1/..., или указывать на UUID файловой системы. Команда, которая позволяет посмотреть идентификаторы файловых систем:

sudo blkid

Параметр crashkernel - это указание, куда (по каким адресам) нужно поместить вторую копию ядра.

Параметр quite - не выводит информацию при загрузке ядра (только ошибки). Эти же строки пишутся в лог:

less /var/log/dmesg

Т.е. ядро до запуска настраивается передачей параметров ему.

Настроить ядро после его запуска без перезагрузки можно с помощью интерфейса sysctl. У ядра есть переменные, которые определяют его работу. Посчитаем навскидку их количество:

sudo sysctl -a | wc -l
925

925 переменных.

Есть совершенно классический пример (из большинства учебников по Linux):

sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

1 - означает, что эта машина может выступать в роли маршрутизатора, 0 - не может.

Т.е. если машина подключена по wifi, и по Ethernet, то сможет пересылать пакеты из одной сети в другую. Также нужна для виртуальных машин (заводится для них подсеть).

sudo sysctl net.ipv4.ip_forward=1

или

sudo sysctl -w net.ipv4.ip_forward=1

Позволяет записать новое значение в переменную.

Есть альтернативный интерфейс к этим переменным:

ls /proc/sys
abi  debug  dev  fs  kernel  net  vm

Вот как можно добраться к тому же параметру:

cat /proc/sys/net/ipv4/ip_forward
0

Запишем другое значение в переменную:

echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward

Мы не можем записать такой конструкцией:

sudo echo 0 > /proc/sys/net/ipv4/ip_forward
bash: /proc/sys/net/ipv4/ip_forward: Отказано в доступе

Потому что echo работает от суперпользователя, а перенаправление в файл > - от обычного пользователя.

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

cat /etc/sysctl.conf

Синтаксис очень простой название_переменной = значение_переменной

Изменив этот файл, чтобы применить настройки из него без перезагрузки:

sudo sysctl -p

Вот так можно посмотреть все переменные, объявленные в этом файле:

grep -v ^# /etc/sysctl.conf

ключ -v - означает "наоборот", т.е. вывести строки, НЕ совпадающие с шаблоном, в данном случае - ^#. Этот шаблон соответствует всем строкам, которые начинаются с решетки # (с этого символа начинаются комментарии в файле sysctl.conf.

56:19

В этом файле в виде комментариев перечислены наиболее важные переменные. Если мы хотии задать какую-либо переменную, то можно просто убрать знак комментария # перед ней и задав значение. Сделаем это через потоковый редактор sed:

sudo sed -i.bak /ip_forward/s/^#// /etc/sysctl.conf
  • -i.bak - означает редактировать файл (-i), .bak - это расширение файла-бекапа, который sed создаст на всякий случай. .bak можно было пропустить.
  • /ip_forward - означает что мы ищем только строку, содержащую подстроку ip_forward. "Для строки, которая содержит подстроку"...
  • /ip_forward/s/^#// - означает: для строки, содержащей подстроку ip_forward, сделать замену подстроки #, находящейся строго с начала строки, на пустую строку.

Команда sysctl -a выведет все известные переменные и их значения.

Еще один полезный параметр: kernel.panic. При сбое ядра, оно либо останавливается, либо перезагружается. Это "или" запрограммировано в переменной kernel.panic. Значение 0 означает ничего не делать, т.е. остановиться. Ядро будет сообщать миганием Scroll lock и Num lock о панике. Однако, компьютер все же можно перезагрузить (Alt-Scroll lock) - magick sys irq. Если в эту переменную записать какое-либо значение, то это будет означать количество секунд, после которого нужно перезагрузиться. Так что мы можем указать этот параметр до запуска ядра, и после.

less и more - программы - пейджеры. less - выдуман в насмешку more и умеет больше. more не умеет листать поток назад (файл - умеет), less - наслаивает вывод std.err (ошибок) на обычный вывод, что в результате может привести к неожиданным результатам. more - не наслаивает, выводит ошибки после основного вывода.

Как появляются новые процессы?

Новые процессы в Linux появляются с помощью вызова функции fork (а также ее усовершенствованных версий clone, clone2). Откуда берется тогда процесс init (проблема курицы и яйца)?

А что первично: fork или exec? См. исходные коды Linux: init/main.c. 1:14:16.

if (execute_command) {
    run_init_process(execute_command);
    printk(KERN_WARNING "Failed to execute %s."
        "Attempting defaults...\n",
        execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to
kernel. "
"See Linux Documentation/init.txt for guidance.");

Совет передать параметр init= в ядро - cпорный, т.к. если в корневой файловой системе не нашлось ни init, ни sh, то это значит что это не та файловая система! Поэтому лучше рекомендация передать root=.

Секция execute_command как раз обслуживает случай, когда в ядро передан параметр init=. Если был передан шелл (init=/bin/sh), то он загрузится в командную строку под root (поэтому опасно, когда кто-то имеет доступ к параметрам загрузки ядра - т.к. он может получить полный доступ над системой). Однако, если вы попытаетесь выйти из shell в таком случае, для ядра это будет нештатная ситуация - попытка убить init и как результат - kernel panic. Перезагрузиться можно с помощью команды reboot -f

sudo ls /proc/1/fd -l

Покажет открытые процессом init (PID=1) файлы. Из них видно, что это новый init, upstart, и видно открытые сокеты, т.е. init общается с процессами через открытый сокет. Бывают более простые init, которые слушают пайп и файл устройтва /dev/initctl. 1:18:19

Что будет делать init?

К какому пакету относится init? (Debian, Ubuntu):

dpkg -S /sbin/init

Напишет, что относится к пакету upstart.

BSD-стиль, SysV-стиль

есть файл /etc/rc.local, в котором хранятся сценарии, специфичные для данной системы - там хранятся вещи, которые надо грузить при каждом старте системы. По умолчанию он состоит только из комментариев и строки exit 0, перел этой строчкой можно вставить любые свои команды.

sysvinit: /etc/inittab, id:5:initdefault. 5-й уровень исполнения - графический вход в систему.

upstart: /etc/init/*.conf Следит постоянно за изменениями своих файлов, оттуда он узнает, что надо делать, не только по загрузке, но и по другим событиям. Линк появился/пропал например, старт/остановка других процессов.

ls /etc/init
cat /etc/init/gdm

Нас интересует последняя строчка, в которой происходит основная работа:

exec gdm-binary $CONFIG_FILE

exec - значит без создания форка процесса.

cat /etc/init/tty1.conf

systemd:

initrd, initramfs - решает проблему курицы и яйца - это образ файловой системы, который загружается в память вместе с ядром 1:28:10. если надо подключить новый дисковый контроллер, то надо прочитать файл с диска, чтобы прочитать файл с диска, нужна смонтированная файловая система. В ядре, в некой структуре в памяти, помечено, по какому адресу ядро загружено. Это называется boot-протокол, в документации описано.

file /boot/initrd.img-3.13.0-83-generic
/boot/initrd.img-3.13.0-83-generic: gzip compressed data, from Unix, last modified: Sun Mar 20 01:18:38 2016

Прочитаем распакованные данные:

zcat /boot/initrd.img-3.13.0-83-generic | file -
/dev/stdin: ASCII cpio archive (SVR4 with no CRC)

Видим, что внутри сжатого архива есть несжатый, в формате cpio. Копаем глубже:

zcat /boot/initrd.img-3.13.0-83-generic | cpio -t | head

Параметр -t для cpio означает тестирование, и мы оставляем первые 10 строк вывода с помощью команды head. Если заменить head на less, то можно посмотреть все файлы. Напоминаем, что это не настоящая корневая файловая система. Здесь можно найти файл init, это будет самый первый init, выполненный системой, но это не настоящий init.

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

Система после загрузки освобождает память initramfs, и честно об этом пишет:

grep -i free /var/log/dmesg
[    0.010691] Initializing cgroup subsys freezer
[    0.011068] Freeing SMP alternatives memory: 32K (ffffffff81e6e000 - ffffffff81e76000)
[    1.363830] Freeing initrd memory: 27068K (ffff880034b12000 - ffff880036581000)
[    2.362961] Freeing unused kernel memory: 1336K (ffffffff81d20000 - ffffffff81e6e000)
[    2.370578] Freeing unused kernel memory: 780K (ffff88000173d000 - ffff880001800000)
[    2.379629] Freeing unused kernel memory: 680K (ffff880001b56000 - ffff880001c00000)

Большинство драйверов уже есть в составе ядра. А вот тех, что нет, их придется добавить, установить и пересоздать initrd. Для этого есть команды mkinitrd, mkinitramfs.

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

dpkg -l virt*

Выведет пакеты, связанные с Virtual Box и похожие.

Чтобы понаблюдать работу dkms, можно поставить какой-нибудь пакет, который собирает модули для ядра, например, VirtualBox, или драйвера для видеокарт nvidia. Потом, посмотрим все файлы, измененные в каталоге модулей ядра, за последние 3 минуты:

find /lib/modules/3.13.0-83-generic/ -mmin 3

Затем посмотрим файл с зависимостями модулей:

less /lib/modules/3.13.0-83-generic/modules.dep

Синтаксис этого файла предельно простой: название_модуля: модуль_от_которого_зависит. 1:39:53

Корневая файловая система обычно не напрямую монтируется, а через сценарии внутри initramfs. Загрузчик grub наделен способностью читать файловую систему, поэтому он грузит образ initramfs в память с файловой системы. lilo этого не умеет, у него есть бинарный файл, в котором перечислено, какие блоки надо читать. lilo хранит информацию о положении файлов в бутсекторе, а grub - нет, он хранит первую часть загрузчика, она умеет найти вторую и загрузить ее. Там тоже не все так просто. В Mbr - grub1, в файловой системе - grub2 (stage 2), а в зазоре между mbr и файловой системе - лежит grub1.5, который умеет читать ФС. В этом зазоре около 15 секторов.

Заглянем в первые байты диска:

sudo hexdump -C -n512 /dev/sda

Увидим, что там явно есть слово GRUB. Смотрим дальше:

sudo hexdump -C -n1024 /dev/sda

Если используется grub v2, то мы можем и не увидеть части 1.5 от grub, т.к. она хранится в другом месте.

Как монтируются другие файловые системы

man 5 fstab

В этом файле указана информация об остальных (не корневых) файловых системах.

1:44:44

cat /etc/fstab

В этом файле можно увидеть корневую файловую систему. При загрузке корневая ФС монтируется только на чтение, и потом перемонтируется, как указано в этом файле. Дополнительные ФС будут смонтированы, как указанно в этом файле.

Подробнее про процесс загрузки можно посмотреть тут: linux/Documentation/x86/boot.txt

Как изучать процесс загрузки, будет рассказано в лекции по виртуализации. python-vm-builder - позволяет собрать виртуальную машину, и экспериментировать с ней.

⚠️ **GitHub.com Fallback** ⚠️