FreeBSD поддерживает абстракцию, называемую ``загрузчик исполнимых классов'', который
фактически является первой стадией системного вызова execve(2).
На самом деле, FreeBSD имеет несколько загрузчиков вместо одного, который, в случае
неудачи, выполняет программу как сценарий (скрипт).
Исторически сложилось, что единственный загрузчик в UNIX® системах проверял ``магическое число'' (чаще всего
первые 4 или 8 байт файла), чтобы определить, известен ли формат исполняемого файла
системе, и если да, то вызвал соответствующий загрузчик.
Если файл не опознавался системой как исполнимый, execve(2) возвращал
ошибку, и текущий командный интерпретатор начинал выполнять файл как скрипт.
Позднее, sh(1) был
модифицирован, так, чтобы проверять первые два символа в файле, и если они оказывались
:\n, то файл выполнялся как сценарий для csh(1) (утверждается,
что SCO были первыми, кто сделал эту модификацию).
FreeBSD ведет себя по-другому: пробегает по списку загрузчиков, включая специальный
#! загрузчик, который вызывает нужный интерпретатор или /bin/sh, если не нашел подходящего.
Формат исполняемого файла FreeBSD определяет по ``магическому числу''. На этой стадии
пока не различается, для какой операционной системы предназначен файл (Linux, Solaris™, или любой другой, использующей ELF-формат
исполняемых файлов).
Далее, ELF-загрузчик определяет ``марку'' (специальный комментарий; отсутствует в
исполняемых файлах SVR4/Solaris) исполняемого файла, то
есть для какой операционной системы он предназначен.
Соответственно, Linux программы должны быть ``маркированы'' для Linux (например, с помощью утилиты brandelf(1)):
# brandelf -t Linux file
Когда ELF-загрузчик находит ``марку'' Linux, он заменяет
соответствующий указатель в структуре proc. Все системные
вызовы индексируются через этот указатель (в традиционной UNIX системе это массив sysent[],
содержащий системные вызовы). Некоторые особые ситуации и системные вызовы обрабатываются
специальным модулем ядра поддержки Linux.
Плюс ко всему, Linux эмулятор динамически ``изменяет корень'' файловой системы при
поиске файлов; фактически так же, как и параметр union при
монтировании файловых систем (не путать с unionfs!). Сперва,
файл ищется в каталоге /compat/linux/original-path и только затем, в случае неудачи, в /original-path. Это дает возможность
Linux программам выполнять FreeBSD команды, если не найдется соответствующих Linux
команд. Например, скопировав FreeBSD uname(1) в каталог /compat/linux/bin/, можно ``заставить'' Linux программы сообщать,
что они запускаются под FreeBSD.
На самом деле, ядра FreeBSD и Linux во многом похожи: системные операции, виртуальная
память, система сигналов и сообщений, межпроцессное взаимодействие и прочее. Разница в
том, что FreeBSD программы обращаются к системным вызовам FreeBSD, Linux программы
соответственно к системным вызовам Linux. Во многих операционных системах прошлого адреса
системных вызовов были зашиты в
статический глобальный массив sysent[], вместо обращения по
указателю в структуре proc, который инициализируется
динамически, позволяя таким образом запускать программы, написанные для разных
операционных систем.
В чем же разница между системными вызовами Linux и FreeBSD? Фактически никакой.
Единственное различие (на данный момент, в будущем все может и, вероятно, изменится),
пожалуй, в том, что функции системных вызовов FreeBSD зашиты в ядро, а для Linux они
могут быть либо в ядре, либо в динамически загружаемом модуле.
Можно ли назвать это эмуляцией? Нет. Это реализация ABI, а не эмуляция. Как таковой,
эмулятор (или симулятор) отсутствует.
В таком случае, почему же тогда говорят ``Linux эмуляция''? Чтобы ``насолить''
FreeBSD?!. На самом деле, это вопрос терминологии: не существовало слова, которое бы
точнее описывало этот процесс. Нельзя сказать, что FreeBSD запускает приложения Linux
(без перекомпиляции или загрузки соответствующего модуля ядра). Поэтому и придумали
термин ``Linux эмуляция''.