Pull to refresh

Comments 16

Уберите, пожалуйста, куда-нибудь из статьи стену исходника. Хотя бы на pastebin, а ещё лучше куда-нибудь в tar.gz с приложенным Makefile.
Простите, ошибся блогом. Перенёс в правильное место.
Да какая разница, какой блог, читать невозможно же. Тег code существует для небольших вставок, а не выгладывания простыни в несколько килобайт.
Я Вас понимаю и прошу прощения за доставленные неудобства, но прошу выслушать и мои аргументы:
  1. Вышеприведённый код готов к компиляции — это законченная программа.
  2. Этот код проиндексируется поисковыми роботами.
  3. Этот код демонстрирует возможности системы Хамелеон. Надеюсь, Вы представляете, какой объем работы необходим, чтобы чтобы сделать прослойку между этим кодом и железом?
  4. Я честно предупредил, что «нелюбопытным» читателям дальше можно не читать.
    Наконец, есть надежда на то, что кто-то интересующийся вопросом внимательнее глянет на код и укажет на ошибку. Непосредственно в статье. Вот Вы уже указали мне на одну.


  5. Ещё раз прошу прощения за доставленные неудобства.

А чем Ваша Хамелеон принципиально отличается от Linux и *BSD? Микроядро на базе L4? Тогда чем отличается от того же Darwin'а?
Простите, промахнулся кнопкой и ответил в теме. Не со зла — сильно спешил.
А чем Ваша Хамелеон принципиально отличается от Linux и *BSD?


Прежде всего Хамелеон иначе спроектирован.
Изначально заложена возможность исполнять драйверы в UserSpace без ощутимой потери производительности. (Да, я знаю, что в Linux есть Fuse)
Заложена возможность прозрачного слоения драйверов — например, между драйвером жесткого диска и файловой системой можно вставить кэширующий сервер, при этом лишь незначительно подправив конфигурацию.

Микроядро на базе L4? Тогда чем отличается от того же Darwin'а?


Darwin, если не ошибаюсь, на базе микроядра Mach. Позволю себе небольшую цитату из Википедии:

Например, ядро L4 включает только 7 функций и использует 12 килобайт памяти, тогда как Mach 3 включает около 140 функций и использует 330 килобайт памяти. IPC вызов на L4 на 486DX-50 занимает только 5 микросекунд — быстрее, чем Unix вызов на этой же системе, и в 20 раз быстрее, чем Mach. Конечно здесь не учитывается тот факт, что L4 не оперирует разрешениями и безопасностью, оставляя их на усмотрение непривилегированным программам.


Минимализм L4 накладывает определённые ограничения на реализацию системы, которые пока мало кому удалось обойти. Итак, в копилку Хамелеона можно занести следующие достоинства:
  • Оригинальный алгоритм развязывания асинхронных системных вызовов через синхронные IPC
  • Оригинальный алгоритм, использующий потоки виртуальных страниц для динамического выделения массивов
  • Использование L4 страниц виртуальной памяти для описания блоков памяти любого размера.

Практически, отличий гораздо больше, я описал лишь базовые. Наконец, если я что-то заимствовал из кода BSD, то это лишь некоторые заголовочные файлы. Причём, не очень то и много — многие хидеры взяты из документации POSIX с сайта www.opengroup.org, чьим членом я был (пока платил членские взносы).

Вы какие-то детские вещи говорите. Во-первых, init не _ПЕРВЫЙ_ в большинстве дистрибутивов процесс. Сначала отрабатывает пачка процессов в initrd, и только потом делается pivot_root на init с подмонтированной (кем? ага, ага) файловой системы.

Далее. Вы говорите, что init есть родитель всех и вся. А как же udev? Его-то кто выполняет? Святой дух? У ядра даже есть опция компиляции с указанием имени udev'а.

Ну и третье — init вовсе не обязан быть исполняемым файлом. Это запросто может быть шелловый скрипт, и в этом случае ядро выполнит то, что указано в хэштеге скрипта.

… а потом ядро сначала вызовет ld.so для линковки шелла (или даже питона/перла и т.д.), и только потом начнёт выполняться сам init.

Всё сложнее, чем кажется. И всё сложнее, чем у Рубчинского (Рубачевского? не помню). Gnu is not Unix, однако.
Во-первых, init не _ПЕРВЫЙ_ в большинстве дистрибутивов процесс. Сначала отрабатывает пачка процессов в initrd, и только потом делается pivot_root на init с подмонтированной (кем? ага, ага) файловой системы.

Далее. Вы говорите, что init есть родитель всех и вся. А как же udev? Его-то кто выполняет? Святой дух? У ядра даже есть опция компиляции с указанием имени udev'а.

Спасибо. Я хорошо понимаю, что Вы имеете в виду. Однако, раз уж всё так просто, то как Вы объясните вывод команды pstree в Debian:
image
или, например, попробуйте на своей системе такую команду: "ps -faA | less" и обратите внимание на поле PPID.

Как видно, процесс номер 1 — init. Даже не смотря на использование initrd. А процесс udevd — всего лишь его потомок процесса номер 1.

Ну и третье — init вовсе не обязан быть исполняемым файлом.


# chmod -x /sbin/init; reboot

Таки загрузится?

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

В этом вопросе полностью согласен. Но хочу уточнить, что это не особенность процесса старта системы, а обычная особенность системного вызова exec — ядро анализирует заголовок любого исполняемого файла и в зависимости от заголовка вызовет файл на исполнение или выполнит программу указанную в хештеге, передав ей код скрипта на стандартный поток ввода (stdin).
Кстати, Хамелеон тоже так умеет и не только так — плюс к этому он умеет загружать и PE бинарники, главное, чтобы они были слинкованы с правильной libc.

… а потом ядро сначала вызовет ld.so для линковки шелла (или даже питона/перла и т.д.), и только потом начнёт выполняться сам init.

Если процесс init (или его аналог) собран с ключом -static, то не вызовет. Раз уж Вы захотели углубиться в дебри, то завсегда пожалуйста:

в случае ELF, компоновщик сохраняется в секции .interp запускаемой программы) или непосредственно через запуск: /lib/ld-linux.so.* [ОПЦИИ] [ПРОГРАММА [АРГУМЕНТЫ]]

То есть по сути обработчик системного вызова exec (который, разумеется, является частью ядра) анализирует секцию .interp исполняемого файла и выполняет указанные в нём команды. Что никак не противоречит статье.

Всё сложнее, чем кажется. И всё сложнее, чем у Рубчинского (Рубачевского? не помню). Gnu is not Unix, однако.

Да ничего там сложного нет. Несколько лет назад стало модным использовать начальный виртуальный диск — обычный cpio архив упакованный gzip. При этом реальный скрипт init (который вызывается ядром) выглядит следующим образом:

#!/bin/sh

echo "Loading, please wait..."

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid none /sys
mount -t proc -o nodev,noexec,nosuid none /proc

# Note that this only becomes /dev on the real filesystem if udev's scripts
# are used; which they will be, but it's worth pointing out
tmpfs_size="10M"
if [ -e /etc/udev/udev.conf ]; then
. /etc/udev/udev.conf
fi
mount -t tmpfs -o size=$tmpfs_size,mode=0755 udev /dev
[ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1
[ -e /dev/null ] || mknod /dev/null c 1 3
> /dev/.initramfs-tools
mkdir /dev/.initramfs

# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf

# Set modprobe env
export MODPROBE_OPTIONS="-qb"

# Export relevant variables
export ROOT=
export ROOTDELAY=
export ROOTFLAGS=
export ROOTFSTYPE=
export break=
export init=/sbin/init
export quiet=n
export readonly=y
export rootmnt=/root
export debug=
export panic=
export blacklist=
export resume_offset=

# Bring in the main config
. /conf/initramfs.conf
for conf in conf/conf.d/*; do
[ -f ${conf} ] && . ${conf}
done
. /scripts/functions

# Parse command line options
for x in $(cat /proc/cmdline); do
case $x in
init=*)
init=${x#init=}
;;
root=*)
ROOT=${x#root=}
case $ROOT in
LABEL=*)
ROOT="/dev/disk/by-label/${ROOT#LABEL=}"
;;
UUID=*)
ROOT="/dev/disk/by-uuid/${ROOT#UUID=}"
;;
/dev/nfs)
[ -z "${BOOT}" ] && BOOT=nfs
;;
esac
;;
rootflags=*)
ROOTFLAGS="-o ${x#rootflags=}"
;;
rootfstype=*)
ROOTFSTYPE="${x#rootfstype=}"
;;
rootdelay=*)
ROOTDELAY="${x#rootdelay=}"
case ${ROOTDELAY} in
*[![:digit:].]*)
ROOTDELAY=
;;
esac
;;
nfsroot=*)
NFSROOT="${x#nfsroot=}"
;;
ip=*)
IPOPTS="${x#ip=}"
;;
boot=*)
BOOT=${x#boot=}
;;
resume=*)
RESUME="${x#resume=}"
;;
resume_offset=*)
resume_offset="${x#resume_offset=}"
;;
noresume)
noresume=y
;;
panic=*)
panic="${x#panic=}"
case ${panic} in
*[![:digit:].]*)
panic=
;;
esac
;;
quiet)
quiet=y
;;
ro)
readonly=y
;;
rw)
readonly=n
;;
debug)
debug=y
quiet=n
exec >/dev/.initramfs/initramfs.debug 2>&1
set -x
;;
debug=*)
debug=y
quiet=n
set -x
;;
break=*)
break=${x#break=}
;;
break)
break=premount
;;
blacklist=*)
blacklist=${x#blacklist=}
;;
esac
done

if [ -z "${noresume}" ]; then
export resume=${RESUME}
else
export noresume
fi

depmod -a
maybe_break top

# Don't do log messages here to avoid confusing usplash
run_scripts /scripts/init-top

maybe_break modules
log_begin_msg "Loading essential drivers"
load_modules
log_end_msg

maybe_break premount
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"
run_scripts /scripts/init-premount
[ "$quiet" != "y" ] && log_end_msg

maybe_break mount
log_begin_msg "Mounting root file system"
. /scripts/${BOOT}
parse_numeric ${ROOT}
maybe_break mountroot
mountroot
log_end_msg

maybe_break bottom
[ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"
run_scripts /scripts/init-bottom
[ "$quiet" != "y" ] && log_end_msg

# Move virtual filesystems over to the real filesystem
mount -n -o move /sys ${rootmnt}/sys
mount -n -o move /proc ${rootmnt}/proc

# Check init bootarg
if [ -n "${init}" ] && [ ! -x "${rootmnt}${init}" ]; then
echo "Target filesystem doesn't have ${init}."
init=
fi

# Search for valid init
if [ -z "${init}" ] ; then
for init in /sbin/init /etc/init /bin/init /bin/sh; do
if [ ! -x "${rootmnt}${init}" ]; then
continue
fi
break
done
fi

# No init on rootmount
if [ ! -x "${rootmnt}${init}" ]; then
panic "No init found. Try passing init= bootarg."
fi

# don't leak too much of env - some init(8) don't clear it
# (keep init, rootmnt)
unset debug
unset MODPROBE_OPTIONS
unset DPKG_ARCH
unset ROOTFLAGS
unset ROOTFSTYPE
unset ROOTDELAY
unset ROOT
unset blacklist
unset break
unset noresume
unset panic
unset quiet
unset readonly
unset resume
unset resume_offset

# Chain to real filesystem
maybe_break init
exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console
panic "Could not execute run-init."


/sbin/init вызывается в предпоследней строчке этого скрипта.
Собственно говоря, подменить этот init на рамдиске вообще не проблема — достаточно перепаковать cpio+gz архив, заменив скрипт на свой процесс init. Только зачем?

Кстати, этот скрипт с рамдиска вполне объясняет, почему init остаётся процессом номер 1 — именно из за функции exec, которая запускает /sbin/init (или файл, заданный параметром init). Старый процесс затирается новым, при этом новый сохраняет pid и отношения родитель-потомок.

То есть я, конечно, благодарен Вам за интерес к статье и комментарий, но всё же, как ни крути, init — это первый процесс. И если я быстро соглашусь с Вашими доводами, то мне цена — грош.
Строго говоря, init НЕ первый запущенный процесс в системе. До того, как pid 1 сделает sys_exec() и обретёт userspace VA, ядром могут быть запущены другие процессы, например, hotplug.
hotplug — это же не юзерспейс? Хотя, давно ли Linux так умеет?
Впрочем, не только GNU/Linux так умеет:



Да, это юзермод хелпер, googleto:CONFIG_UEVENT_HELPER_PATH. Не сомневаюсь, что кроме него есть ещё хелперы, которые спаунятся до запуска инита.

В любом случае, _поддержка_ юзерхелперов до вызова инита есть, даже если они не используются в конкретном дистрибутиве. Т.о. говорить о том, что init — первый _созданный_ процесс в системе, неверно.
Да, это юзермод хелпер, googleto:CONFIG_UEVENT_HELPER_PATH. Не сомневаюсь, что кроме него есть ещё хелперы, которые спаунятся до запуска инита.


Первая же ссылка ввела меня в замешательство:
Path to uevent helper program forked by the kernel for every uevent. Before the switch to the netlink-based uevent source, this was used to hook hotplug scripts into kernel device events. It usually pointed to a shell script at /sbin/hotplug. This should not be used today, because usual systems create many events at bootup or device discovery in a very short time frame. One forked process per event can create so many processes that it creates a high system load, or on smaller systems it is known to create out-of-memory situations during bootup.

Killer feature?

Т.о. говорить о том, что init — первый _созданный_ процесс в системе, неверно.


Если склероз мне не изменяет, то в Unix System V — init — первый процесс. Простите, всё время забываю, что GNU — Not Unix.

К счастью, мир не ограничивается современным Линуксом — вот какую картинку нашёл — просто прелесть. Подозреваю, что мой init соберётся и заработает на большинстве из этих систем систем.

После написания статьи я решил ещё побаловаться с Линуксом и перенёс на него ash и deco, затем собрал busybox, затем binutils, pcc и make. Скопировал у Дебиана некоторые библиотеки и вот что получилось:


^^ небольшой само-сборно-скомпонованный Линукс с компилятором. У меня и в мыслях нет писать очередной дистрибутив Linux, просто за несколько дней построена небольшая система на базе Linux для обкатки идей, тестов и, возможно, сборки. Не бейте за псевдографику, так выглядит deco под Linux c установленным TERM=ansi и дефолтными настройками stty.

Чуть не забыл — в Хамелеоне драйвера работают как нити (программные потоки в адресном пространстве супервизора), но есть возможность запускать драйвера в выделенном адресном пространстве (как процессы) — в таком случае Ваше утверждение:
Т.о. говорить о том, что init — первый _созданный_ процесс в системе, неверно.

Становится истинным для Хамелеона. Но только с точки зрения ядра. С точки зрения userspace, pid 1 это init.

… а потом ядро сначала вызовет ld.so для линковки шелла (или даже питона/перла и т.д.), и только потом начнёт выполняться сам init.

Какая разница, всё равно это всё будет выполнено в контексте процесса №1.
В его контексте в разное время может выполняться много разного кода, начиная от инита со стартового рамдиска и заканчивая финальным инитом, сам процесс от этого не перестаёт быть первым.
А чего бы не использовать init=/bin/sh? Всегда так делаю когда что-то сильно ломается.
init=/bin/sh — самое простое решение, но тогда статья бы получилась очень короткая. :)

Есть некоторый момент, отличающий init в Unix'ах и хамелеоновский init. Linux ядро — не просто монолит, но представляет собой законченную систему. Хамелеоновское ядро — набор модулей, которые могут быть использованы в различных комбинациях. Поэтому задача хамелеоновского init — связать различные модули в единую логическую систему. Т.е. часть функциональности ядра вынесена в init.
Sign up to leave a comment.

Articles