EnglishРусский Map

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 отказывается.