GNU Linux - andyceo/documentation GitHub Wiki
Операционная система GNU-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 загружался с дискеты, загружал самого себя. Ядро записывалось на дискету, без всякой файловой системы, с нулевого смещения и ядро содержало встроенный загрузчик. Сейчас в начале ядра содержится текст о том, что прямая загрузка с флоппи-дисков больше не поддерживается. От загрузчика в ядре осталась только заглушка. Поддержка флоппи была убрана, потому что ядро перестало влезать на дискету.
- 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
Ядро бывает по-разному собрано, и перекомпилировать его без крайней необходимости. Особенно этого хотят разработчики ядра.
Но можно и не пересобирать, а подгружать модули по мере необходимости.
-
Гибридное - драйверы дисковых и файловых подсистем загружаются модулями
-
Модули - файлы в
/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
Как работает 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 - позволяет собрать виртуальную машину, и экспериментировать с ней.