Multicall-бинарник
- title
- Multicall-бинарник
- type
- concept
- summary
- Один исполняемый файл, запускающий встроенные подпрограммы по argv[0]; паттерн BusyBox, Toybox, sbase и минималистичных userland'ов Unix
- tags
- unix, linux, executables, embedded
- created
- 2026-05-13
- updated
- 2026-05-13
- lang
- ru
- translation_of
- multicall-binary
- source_updated
- 2026-05-13
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Multicall-бинарник - это единый скомпилированный исполняемый файл, содержащий множество подпрограмм (часто называемых applet'ами) и во время работы выбирающий нужную по значению argv[0], то есть имени, под которым он был вызван. Символические (или жёсткие) ссылки связывают имя каждой Unix-утилиты (ls, wget, cp, sh, ...) с одним и тем же исполняемым файлом; при вызове любой из них бинарник считывает собственное имя и выполняет диспетчеризацию.
Каноническая реализация - BusyBox; разбор кода диспетчеризации см. в specular-what-is-busybox. Этот же приём используют Toybox, sbase, консолидированные CLI в стиле Pi и userland'ы многих исследовательских ядер.
Механизм работы
Путь запуска внутри BusyBox выглядит примерно так:
applet_name = argv[0];
if (applet_name[0] == '-') // login shells: argv[0] starts with -
applet_name++;
applet_name = bb_basename(applet_name);
int applet = find_applet_by_name(name);
run_applet_no_and_exit(applet, name, argv);
// internally:
xfunc_error_retval = applet_main[applet_no](argc, argv);
У каждого applet'а есть своя функция вида main (wget_main, ls_main, ...), и управление передаётся в неё так, будто это точка входа в программу. Для пользователя вызовы wget URL и busybox wget URL эквивалентны: multicall-бинарник просто берёт имя applet'а из argv[0], а при прямом вызове зонтичного бинарника берёт его из argv[1].
busybox --install -s наполняет каталог симлинками на каждый applet (флаг -s задаёт символические ссылки; жёсткие ссылки также поддерживаются). busybox --list выводит список всех вкомпилированных applet'ов.
Каждый файл applet'а также содержит метаданные в стиле Kconfig в комментариях C:
//config:config WGET
//config: bool "wget (41 kb)"
//config: default y
//config: help wget is a utility for non-interactive download ...
//applet:IF_WGET(APPLET(wget, BB_DIR_USR_BIN, BB_SUID_DROP))
//kbuild:lib-$(CONFIG_WGET) += wget.o
На этапе сборки эти комментарии преобразуются в записи Kconfig, благодаря чему дистрибутивы (Alpine, OpenWrt, Buildroot) могут выбрать нужные applet'ы и скомпилировать в свой бинарник BusyBox только их.
Зачем это нужно
Три взаимодополняющих преимущества:
- Размер на диске. Один бинарник, множество симлинков. Все applet'ы делят между собой общую среду исполнения (инициализацию libc, обработку строк, getopt и т. д.), поэтому добавление очередного applet'а обходится дёшево. В Alpine около 130 applet'ов BusyBox в
/usr/binоформлены как симлинки на один бинарник размером ~1 МБ. - Память и время запуска. Multicall-бинарник кэшируется в памяти страниц (page cache) один раз и переиспользуется при каждом вызове applet'а вместо обращения к десяткам отдельных файлов ELF.
- Выбор состава при сборке. Включение или исключение applet'а задаётся флагом конфигурации, а не отдельным пакетом. Дистрибутивы для встраиваемых систем могут собирать ровно тот набор утилит, который им нужен.
Компромисс, на который обращают внимание пользователи Alpine в статье Specular, заключается в том, что applet'ы BusyBox - это не полноценные утилиты coreutils: многие флаги, локали, диалекты регулярных выражений и редкие сценарии поведения намеренно урезаны. awk работает, но некоторые краевые случаи POSIX отличаются. wget работает, но набор опций заметно скромнее, чем у GNU wget. Для большинства задач в контейнерах это незаметно, однако при переносе скрипта с машины на Debian может вызывать недоумение.
Место в архитектуре
- Отличается от монолитных CLI с подкомандами вроде git,
cargoилиgh: это единые исполняемые файлы, принимающие подкоманды в аргументах (git commit, а неcommit), а не симлинки, притворяющиеся отдельными утилитами. Паттерну multicall симлинки необходимы именно для того, чтобы каждый applet был доступен под своим оригинальным именем Unix. - Ближайшая параллель в этой wiki - подход cc-mirror к вариантам Claude Code: одна базовая установка и множество имён команд верхнего уровня, запускающих её с разной конфигурацией. Механизм диспетчеризации здесь другой (скрипт-обёртка вместо argv[0]), но пользовательская модель идентична: одна программа, много имён команд, и каждое задаёт поведение среды исполнения.
- См. также fil-c и retrofitting-jit-c-interpreters как примеры более широкой темы переиспользования субстрата при вариативности точек входа: единый механизм с множеством пользовательских интерфейсов.
Особенности эксплуатации
- Создавать симлинк на бинарник нужно с именем applet'а, а не как произвольный псевдоним. Диспетчеризация читает
argv[0], поэтому приln -s busybox fooзапускfooвызоветfind_applet_by_name("foo"), что вернёт "applet not found", еслиfooне является реальным именем applet'а в BusyBox. - Оболочки входа (login shells) получают
argv[0]с префиксом-(например,-bash); код диспетчеризации обрабатывает это проверкойif (applet_name[0] == '-'), отсекающей первый символ. busybox <applet> args...всегда доступен как альтернативный способ вызова, что удобно, когда симлинки ещё не созданы.
Известные реализации
- BusyBox - GPL-2, более 300 applet'ов, стандарт де-факто для Alpine, OpenWrt и Buildroot.
- Toybox - лицензия 0BSD, меньший объём возможностей, используется в Android примерно с 2015 года.
- sbase + ubase (suckless) - по умолчанию собирают applet'ы как отдельные бинарники, но также поддерживают сборку в виде единого multicall-файла.
- GNU coreutils - не multicall-бинарник; каждая утилита представляет собой отдельный ELF-файл. Экономия места на диске - это именно то, от чего GNU coreutils отказывается.