Семейство BSD: FreeBSD, OpenBSD, NetBSD, DragonFly — философия и различия
Вы почти наверняка каждый день пользуетесь кодом BSD, даже если никогда не устанавливали ни
одну из этих систем. Когда вы открываете терминал на macOS — вы работаете в userland, значительная
часть которого пришла из FreeBSD. Когда вы делаете ssh куда угодно с чего угодно — вы запускаете
OpenSSH, написанный командой OpenBSD. Когда ваш код вызывает socket(), bind(), listen(),
accept() — вы используете API, придуманный в Беркли в 1983 году и с тех пор не менявшийся
принципиально. Когда вы смотрите Netflix — ваш поток отдаёт FreeBSD-машина. Когда вы играете на
PlayStation — под вами ядро, выросшее из FreeBSD.
Но «BSD» — не одна система. Это четыре независимых операционных системы с общим предком, которые за тридцать лет разошлись настолько, что различаются сильнее, чем Debian и Fedora. У каждой есть одна главная идея, которой она подчиняет всё остальное, включая удобство. Понять BSD — значит понять эти четыре идеи и цену, которую каждая система за свою идею платит.
Для прикладного программиста это не музейная экскурсия. BSD — это место, где родились и до сих пор
живут вещи, которых в Linux либо нет, либо они устроены иначе: kqueue вместо зоопарка
epoll/inotify/timerfd/signalfd, pledge/unveil вместо seccomp-BPF, jails до всякого
Docker, ZFS как штатная корневая ФС. Знание BSD — это ещё и калибровка: когда вы видите, как одну
и ту же задачу решили четырьмя способами, вы перестаёте считать привычный способ единственно
возможным.
История линии Unix → BSD разобрана подробно в статье История ОС; здесь мы начнём с момента раскола.
Откуда взялись четыре системы
Ключевая точка — 1991–1994 годы. Berkeley Software Distribution была не операционной системой, а набором дополнений к AT&T Unix: чтобы легально запустить BSD, требовалась лицензия AT&T. Группа CSRG в Беркли методично выпиливала последний AT&T-код, и в 1991-м вышел Networking Release 2 (Net/2) — почти полная система, свободная от кода AT&T, но без шести файлов ядра.
Уильям и Линн Джолиц дописали недостающее и в 1992-м выпустили 386BSD — первую свободную BSD для дешёвого железа. Дальше произошло то, что определило всё: Джолицы не успевали интегрировать поток патчей от сообщества. Накопленный неофициальный «patchkit» породил две параллельные попытки продолжить работу — NetBSD и FreeBSD, обе в 1993 году.
Параллельно AT&T через дочернюю USL судилась с BSDi и Университетом Калифорнии. Иск урегулировали в начале 1994-го: несколько файлов удалили, несколько переписали, и вышел 4.4BSD-Lite — юридически чистая база, на которую перебазировались обе молодые системы. Эти два года неопределённости — важнейший «что если» в истории ОС: пока BSD судилась, Linux рос без юридических рисков и собрал критическую массу разработчиков.
В 1995-м Тео де Раадт после конфликта в NetBSD форкнул её и основал OpenBSD с явно сформулированной целью — корректность и безопасность важнее всего остального. В 2003-м Мэтт Диллон, не согласный с выбранным во FreeBSD 5.x подходом к SMP, форкнул FreeBSD 4.8 и создал DragonFly BSD.
Главное отличие от Linux: base system
Если запомнить из статьи одну вещь — пусть это будет она. BSD — это операционная система целиком,
разрабатываемая одной командой в одном репозитории. Ядро, libc, компилятор, ls, ssh,
ifconfig, установщик, man-страницы, файрвол — всё лежит в одном дереве /usr/src, версионируется
вместе и релизится вместе. Это называется base system.
Linux — ядро. Всё остальное в вашей системе — независимые проекты, которые кто-то (Debian, Red Hat, Arch) взял на себя труд собрать вместе и подружить. Дистрибутив — это слой интеграции.
Из этого различия растут абсолютно все остальные:
Документация. В BSD man-страница — часть исходников и обязательна для любого изменения.
Патч, меняющий поведение утилиты, но не правящий её man, в OpenBSD не примут. Поэтому
man tcp, man pf.conf, man 9 uvm — это реально исчерпывающие тексты, а не заглушка «see
info page». Практический совет: на BSD сначала читайте man, а не Stack Overflow — там обычно
уже есть ответ. man -k (он же apropos) ищет по всем разделам.
Граница «система/приложение» физическая. Всё из base лежит в /bin, /sbin, /usr/bin,
/usr/sbin. Всё, что вы поставили пакетами — в /usr/local. Не «по договорённости», а жёстко:
pkg физически не может писать в /usr/bin. Отсюда практическая привычка: в скриптах
#!/usr/bin/env bash, а не #!/bin/bash — bash в base нет ни в одной BSD, он лежит в
/usr/local/bin/bash.
Согласованность ядра и userland. Ядро и libc собираются вместе и меняются вместе. Отсюда —
важнейшее следствие, которое ломает интуицию линуксоидов: в BSD нет стабильного syscall ABI.
Прямые системные вызовы в обход libc — не поддерживаемый контракт. В Linux наоборот: syscall ABI
свят, а libc — просто библиотека, поэтому Go долгие годы вызывал syscalls напрямую. На
FreeBSD/OpenBSD так делать нельзя, и Go на этих платформах ходит через libc; OpenBSD c
msyscall(2) вообще убивает процесс, если syscall пришёл не из объявленного региона libc.
Апгрейд. freebsd-update fetch install или sysupgrade в OpenBSD обновляют всю базовую
систему как единое целое — комбинация «ядро + userland» тестировалась именно в таком составе.
Обратная сторона — меньшая гибкость: нельзя взять новое ядро и старый userland.
Про архитектуру самих ядер — все четыре BSD монолитные с загружаемыми модулями — см. Архитектуры ядер.
Лицензия: чем BSD отличается от GPL на практике
BSD-лицензия (2-clause) требует одного: сохранить копирайт. Можно взять код, изменить, встроить в закрытый продукт и не публиковать ничего. GPL требует открыть производные работы.
Это не абстракция, а причина, по которой BSD-код везде: сетевой стек в Windows долгое время нёс следы BSD; macOS взяла userland и часть ядра; Junos (Juniper), NetApp ONTAP, PlayStation 4/5, Nintendo Switch — всё это использует FreeBSD-код. Компании выбирают BSD именно потому, что не обязаны ничего возвращать.
Философский спор здесь честный: сторонники GPL говорят, что BSD-лицензия позволяет «забрать и не отдать», и это ослабляет проект; сторонники BSD — что настоящая свобода включает свободу использовать код как угодно, и что широкое проникновение важнее принудительного возврата. Практический вывод для вас: если пишете библиотеку и хотите, чтобы её взяли в коммерческий продукт, BSD/MIT/Apache — правильный выбор; если хотите гарантировать, что улучшения вернутся, нужна GPL/AGPL.
Четыре системы, четыре главные идеи
4.4BSD-Lite)) FreeBSD Идея — производительность и полнота ZFS как корневая ФС jails, bhyve, DTrace Capsicum, kTLS, sendfile Netflix, WhatsApp, PlayStation ports + pkg, poudriere OpenBSD Идея — корректность и безопасность pledge, unveil, W^X, KARL privsep как норма кода Аудит кода как процесс Родина OpenSSH, LibreSSL, pf, tmux, doas Релиз строго раз в 6 месяцев NetBSD Идея — портируемость Строгое разделение MI и MD кода Полсотни аппаратных архитектур rump kernels, anykernel pkgsrc — работает и на Linux, и на macOS DragonFly BSD Идея — своя модель SMP LWKT и per-CPU токены HAMMER2 — снапшоты и дедупликация vkernel — ядро как процесс Малое сообщество, нишевое применение
FreeBSD — «нам нужна работающая полная система, и быстрая»
FreeBSD — самая практичная и самая распространённая BSD. Её цель: цельная, производительная, хорошо документированная система общего назначения, с упором на серверы и сетевую нагрузку.
Что даёт FreeBSD прикладному программисту прямо сейчас:
# Версия ядра и userland — они по определению совпадают
$ freebsd-version -kru
14.2-RELEASE-p3 # ядро
14.2-RELEASE-p3 # userland
14.2-RELEASE-p3 # с учётом установленных патчей
$ uname -a
FreeBSD db01 14.2-RELEASE-p3 FreeBSD 14.2-RELEASE-p3 GENERIC amd64
# ZFS — корневая ФС по умолчанию в установщике
$ zfs list
NAME USED AVAIL REFER MOUNTPOINT
zroot 8.9G 421G 96K /zroot
zroot/ROOT 2.1G 421G 96K none
zroot/ROOT/default 2.1G 421G 2.1G /
zroot/usr/home 6.2G 421G 6.2G /usr/home
# Снапшот перед рискованным деплоем — мгновенный и почти бесплатный
$ zfs snapshot zroot/ROOT/default@before-upgrade
$ zfs rollback zroot/ROOT/default@before-upgrade # если всё сломалось
# Пакеты
$ pkg install -y nginx postgresql16-server
$ pkg info -l nginx | head -3 # что именно поставилось и куда
/usr/local/etc/nginx/nginx.conf
/usr/local/sbin/nginx
Jails — контейнеры до контейнеров, появившиеся в FreeBSD 4.0 (2000). В отличие от Linux, где
изоляция собирается из ортогональных namespaces + cgroups + seccomp, jail — это единый примитив
ядра: одним вызовом jail(2) процесс получает свой корень ФС, свой набор адресов, свой hostname
и запрет на всё, что затрагивает хост:
# /etc/jail.conf
web {
host.hostname = "web.internal";
path = "/jails/web";
ip4.addr = "10.0.0.10";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
mount.devfs;
allow.raw_sockets = 0; # запрет ping/raw-сокетов внутри
persist;
}
$ service jail start web
$ jls # список запущенных jail'ов
JID IP Address Hostname Path
1 10.0.0.10 web.internal /jails/web
$ jexec web /bin/sh # войти внутрь
Разница в философии заметна: jail безопасен по умолчанию — вы явно разрешаете отдельные возможности. Linux-контейнер по умолчанию делится с хостом почти всем, и безопасным его делает конфигурация рантайма. Подробный разбор обеих моделей — в Виртуализация и контейнеры и Безопасность и изоляция.
Capsicum — второй механизм изоляции FreeBSD, ортогональный jail’ам: capability-режим для
процесса. После cap_enter() процесс теряет доступ к глобальным пространствам имён — он больше не
может ни open() по пути, ни резолвить PID’ы; работать можно только с уже открытыми дескрипторами,
права которых дополнительно сужены:
#include <sys/capsicum.h>
int fd = open("/var/log/app.log", O_WRONLY | O_APPEND);
cap_rights_t rights;
cap_rights_init(&rights, CAP_WRITE, CAP_FSTAT);
if (cap_rights_limit(fd, &rights) < 0) err(1, "cap_rights_limit");
if (cap_enter() < 0) err(1, "cap_enter"); // обратной дороги нет
write(fd, "ok\n", 3); // работает
open("/etc/passwd", O_RDONLY); // ECAPMODE — глобальных путей больше нет
Логика та же, что у Linux seccomp, но выражена не в терминах «какие syscalls разрешены», а в терминах «какими объектами разрешено оперировать». Это чище концептуально, но требует переписывать программу под модель «всё через переданные дескрипторы».
Производительность сети. FreeBSD — платформа Netflix Open Connect: одна машина отдаёт сотни
гигабит в секунду шифрованного видео. Ключи к этому — sendfile(2), интегрированный с VM-подсистемой,
kernel TLS (шифрование в ядре, без копирования в userspace) и NUMA-aware выделение буферов.
Инженерные отчёты Дрю Гэллэтина с EuroBSDcon — лучшее чтение о том, где на самом деле упирается
сетевой стек: people.freebsd.org/~gallatin.
DTrace во FreeBSD — полноценный, включая пробы в ядре и USDT в приложениях. Про сравнение с eBPF — Наблюдаемость и производительность.
OpenBSD — «пусть будет медленнее, но правильно»
OpenBSD — это проект с одним категорическим приоритетом: корректность кода. Не «безопасность» в смысле фич, а именно корректность — потому что большинство уязвимостей это обычные баги. Отсюда всё остальное: непрерывный аудит исходников, беспощадное удаление сложности, отказ от кода, который нельзя понять целиком.
Самый ценный для прикладного программиста подарок OpenBSD — pledge(2). Идея гениально проста: программа сама заявляет ядру, что ей нужно, и всё остальное отключается необратимо.
#include <unistd.h>
#include <err.h>
int main(void)
{
/* Фаза 1: инициализация — нужно всё */
int sock = setup_listener(8080);
load_config("/etc/myapp.conf");
/* Фаза 2: работа — сети и stdio достаточно, файлы больше не открываем */
if (pledge("stdio inet", NULL) == -1)
err(1, "pledge");
serve_forever(sock); /* любой open() отсюда = SIGABRT и core dump */
}
Обратите внимание на три решения, каждое из которых спорно и каждое обосновано:
- Категории, а не отдельные syscalls.
"stdio"— это ~60 вызовов, включаяread,write,close,sigaction,clock_gettime. Не потому, что лень, а потому, что списком из 300 номеров syscall никто не сможет пользоваться правильно. Сравните с профилем seccomp-BPF на двести строк, который вы копируете из интернета, не понимая. - Нарушение убивает процесс, а не возвращает
EPERM. Если программа делает то, чего не обещала — это баг, и он обязан быть громким.EPERMбы утонул в логах. - Только сужение. Повторный
pledgeможет лишь убрать права. Захваченный процесс не вернёт себе возможности.
unveil(2) — вторая половина: ограничение видимой части ФС без root и без namespaces.
unveil("/var/www", "r"); /* только чтение */
unveil("/var/log/app", "rwc"); /* чтение, запись, создание */
unveil(NULL, NULL); /* закрыть список — больше ничего не добавить */
/* с этого момента остальная файловая система для процесса не существует:
open("/etc/passwd") вернёт ENOENT, а не EACCES — путь просто не виден */
Сравните трудозатраты: chroot требует root, копирования библиотек и /dev; namespaces требуют
unshare, монтирований и понимания propagation; SELinux требует политики. unveil — это три
строки в main(). Именно поэтому в OpenBSD им покрыты почти все программы base system.
Остальные механизмы включены по умолчанию, без вашего участия: W^X по всему адресному
пространству, ASLR + PIE, guard-страницы и junk-заполнение в malloc, RETGUARD (криптографическая
защита адресов возврата), KARL — пересборка ядра со случайным порядком объектных файлов при
каждой загрузке, msyscall(2) — запрет syscall’ов откуда угодно, кроме региона libc.
Проверить работу malloc-защит легко:
# Включить максимальную диагностику для одного запуска
$ MALLOC_OPTIONS=CFGJU ./myapp
# C — canary у чанков, F — освобождённые страницы недоступны,
# G — guard pages, J — junk-заполнение, U — free unmap
# use-after-free падает сразу и в точке использования, а не через час где-то ещё
Практический совет, который стоит того, чтобы запомнить: прогоняйте тесты своей C/C++/Rust-программы на OpenBSD, даже если деплоите на Linux. Комбинация agressive-malloc, guard pages и junk fill ловит use-after-free и переполнения буфера там, где glibc молча продолжает работать. Это дешёвый и очень эффективный фаззинг «бесплатно».
Что OpenBSD за это платит: производительность заметно ниже FreeBSD и Linux на многоядерных
нагрузках — распараллеливание ядра идёт медленно и осторожно; часть популярного софта требует
патчей; JIT-движки (JVM, Node.js, браузеры) конфликтуют с W^X и требуют раздела, смонтированного
с wxallowed; Linux-эмуляции нет вообще (удалена в 2016-м). Проект считает эту цену приемлемой и
не собирается её снижать.
Отдельно стоит отметить, сколько софта OpenBSD подарила остальному миру: OpenSSH (стоит
буквально везде), LibreSSL (форк OpenSSL после Heartbleed — с выкинутыми 200k строк),
pf (файрвол с человекочитаемым синтаксисом, портированный во FreeBSD, NetBSD и macOS),
tmux, doas (замена sudo на 300 строк вместо 30k), mandoc, OpenBGPD, OpenNTPD,
arc4random(3), strlcpy/strlcat.
NetBSD — «оно должно работать везде»
Девиз «Of course it runs NetBSD» — не шутка, а следствие архитектурного принципа: жёсткое
разделение машинно-независимого (MI) и машинно-зависимого (MD) кода. В дереве sys/ есть sys/kern,
sys/uvm, sys/net — код, не знающий об архитектуре, — и sys/arch/<arch>/ с минимально
необходимым машинным слоем. Портирование на новую архитектуру означает написание MD-части, а не
правку ядра повсюду.
Результат: NetBSD работает на полусотне аппаратных платформ — от amd64 и arm64 до VAX, SPARC, m68k-Amiga и Sega Dreamcast. Для встраиваемых систем и странного железа это единственный реальный вариант из BSD.
Самое интересное для программиста в NetBSD — rump kernels (Антти Кантее). Идея «anykernel»: драйверы ядра написаны так, что их можно собрать и запустить как обычную библиотеку в userspace. Это значит, что ФС, TCP/IP-стек и драйверы NetBSD можно отлаживать обычным gdb, фаззить обычным AFL и юнит-тестировать без виртуалки:
# Поднять TCP/IP-стек NetBSD как процесс в userspace
$ rump_server -lrumpnet -lrumpnet_net -lrumpnet_netinet unix:///tmp/rumpnet
# Обычные утилиты, но говорящие с ядром-процессом, а не с настоящим ядром
$ export RUMP_SERVER=unix:///tmp/rumpnet
$ rump.ifconfig -a
$ rump.netstat -rn
Это же превращает NetBSD-драйверы в переносимые компоненты: rump-ядра запускали внутри Xen как unikernel (проект Rumprun). Академическая ценность здесь огромна — диссертация Кантее «Flexible Operating System Internals: The Design and Implementation of the Anykernel and Rump Kernels» — одна из самых интересных работ по архитектуре ОС за последние пятнадцать лет: book.rumpkernel.org.
Второй вклад NetBSD — pkgsrc: система портов, которая работает не только на NetBSD, а на Linux, macOS, illumos, FreeBSD и десятке других систем. По сути, предшественник Homebrew и Nix по идее «единое дерево рецептов для разных ОС».
DragonFly BSD — «SMP надо делать иначе»
В начале 2000-х перед всеми BSD стоял один вопрос: как перейти от «большого блокирующего ядро замка» (Giant lock) к настоящей многоядерности. FreeBSD выбрала путь мелкогранулярных блокировок: разбивать структуры данных на всё более мелкие области, защищённые своими мьютексами. Путь рабочий, но трудный: каждая новая блокировка — потенциальный deadlock и точка конкуренции.
Мэтт Диллон считал, что это тупик, и в 2003-м форкнул FreeBSD 4.8. Ставка DragonFly: минимизировать разделяемое состояние, а не защищать его блокировками. Ключевые механизмы:
- LWKT (Light Weight Kernel Threads) — потоки ядра, привязанные к своему CPU. Планировщик каждого ядра работает со своей очередью и не трогает чужие; миграция между CPU — явная и редкая операция.
- Асинхронные сообщения между CPU вместо блокировок: вместо того чтобы взять мьютекс на чужую структуру, поток отправляет сообщение владельцу CPU.
- Токены вместо мьютексов. Токен можно удерживать, засыпая, и он автоматически отпускается при переключении контекста. Это делает deadlock структурно почти невозможным — ценой того, что критическая секция может быть прервана.
По-настоящему известной систему сделала другая вещь — HAMMER2: файловая система с криптографическими контрольными суммами, мгновенными снапшотами, онлайн-дедупликацией, сжатием и поддержкой multi-master репликации. Философски она ближе к ZFS/btrfs, но заметно проще; про устройство copy-on-write ФС — Файловые системы.
Плюс vkernel — ядро DragonFly, собираемое как обычная пользовательская программа и запускаемое как процесс: разработка ядра с обычным отладчиком, без виртуалки.
Честная оценка: DragonFly — самая маленькая из четвёрки по сообществу и охвату железа. Её ценность скорее исследовательская — это живая демонстрация альтернативного подхода к SMP. Для продакшна общего назначения выбирают FreeBSD.
Сравнение: что выбрать под задачу
| Критерий | FreeBSD | OpenBSD | NetBSD | DragonFly |
|---|---|---|---|---|
| Главный приоритет | производительность, полнота | корректность и безопасность | портируемость | модель SMP |
| Релизный цикл | ~2 года на мажор + патчи | строго раз в 6 месяцев | по готовности | по готовности |
| Поддержка релиза | 5 лет (STABLE-ветка) | 12 месяцев (2 релиза) | долгая | короткая |
| Файловая система | ZFS, UFS2 | FFS2 (без ZFS) | FFS, LFS, ZFS (базовая) | HAMMER2, UFS |
| Виртуализация | bhyve | vmm/vmd | NVMM | нет своей |
| Контейнеры | jails (зрелые) | chroot + pledge/unveil | нет аналога jail | нет |
| Файрвол | pf + ipfw | pf (родина) | npf, pf | pf |
| Пакеты | pkg / ports / poudriere | pkg_add / ports | pkgsrc | pkg / DPorts |
| Многоядерность | отличная | скромная, растёт | хорошая | своя модель |
| DTrace | да | нет | частично | нет |
| Linux-совместимость | linuxulator, зрелый | нет вообще | есть | ограниченно |
| Где применяют | серверы, storage, сеть | firewall, DNS, VPN, bastion | embedded, экзотика | нишевые сервисы |
Практическая эвристика:
- Файловый/медиа-сервер, база данных, высоконагруженная сеть, storage-appliance → FreeBSD. ZFS + jails + bhyve + отличный сетевой стек.
- Пограничный firewall, VPN-концентратор, DNS, bastion-хост, всё смотрящее в интернет →
OpenBSD.
pf+relayd+httpd+ikedв base, релизы по расписанию, минимум атакуемой поверхности. - Странное железо, встроенные системы, исследование ОС → NetBSD.
- Интерес к архитектуре SMP и HAMMER2 → DragonFly.
- Обычный сервер приложений с Docker/Kubernetes → всё-таки Linux, и это нормально: экосистема контейнеров живёт там.
Что реально ломается при портировании кода с Linux
Здесь начинается прикладная часть. Ниже — то, на чём спотыкается практически каждый проект.
1. make — это BSD make, а не GNU make. Синтаксис .if/.for несовместим с
ifeq/foreach. Ставьте gmake и вызывайте его явно.
2. Утилиты — BSD-версии, а не GNU coreutils. Список ловушек, которые надо знать наизусть:
# sed -i требует суффикс бэкапа (можно пустой, но аргумент обязателен)
sed -i '' 's/foo/bar/' file # BSD/macOS
sed -i 's/foo/bar/' file # GNU
# даты
date -v +1d # BSD
date -d '+1 day' # GNU
# рекурсивное разрешение симлинков
realpath path # есть в BSD
readlink -f path # GNU (во FreeBSD есть, в OpenBSD нет)
# сортировка, xargs, stat, ps — отличаются флагами
stat -f '%z' file # BSD: размер
stat -c '%s' file # GNU
# спасение: поставить GNU-версии, они с префиксом g
pkg install coreutils gsed gawk
gsed -i 's/foo/bar/' file
3. /proc может отсутствовать. В OpenBSD его нет вообще (procfs удалена). Во FreeBSD он
объявлен устаревшим и обычно не смонтирован. Код вида «прочитаю /proc/self/status, чтобы узнать
RSS» на BSD не работает. Правильный способ — sysctl:
/* FreeBSD: путь к собственному исполняемому файлу — аналог /proc/self/exe */
#include <sys/sysctl.h>
char path[PATH_MAX];
size_t len = sizeof(path);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
sysctl(mib, 4, path, &len, NULL, 0);
# То, что в Linux читают из /proc, в BSD спрашивают у sysctl
$ sysctl hw.ncpu hw.physmem vm.stats.vm.v_free_count
hw.ncpu: 16
hw.physmem: 68719476736
vm.stats.vm.v_free_count: 1204877
$ sysctl -a | wc -l # весь /proc/sys живёт здесь
1847
# аналоги привычных инструментов
$ procstat -v $$ # карта памяти процесса (≈ /proc/PID/maps)
$ sockstat -4l # открытые сокеты (≈ ss -ltn)
$ top -m io # ввод-вывод по процессам
$ vmstat -i # прерывания
4. Нет epoll/inotify/timerfd/signalfd/eventfd. Есть kqueue, и он один заменяет все пять.
Об этом ниже отдельно.
5. Другие libc-расширения. Нет memfd_create, pipe2 (есть во FreeBSD, нет кое-где ещё),
accept4 (во FreeBSD есть), gettid, sched_setaffinity (аналог — cpuset_setaffinity).
Зато strlcpy, strlcat, arc4random_buf, reallocarray, pledge — родные (в Linux их дают
только libbsd или musl).
6. SO_REUSEPORT означает другое. В Linux он балансирует входящие соединения между
процессами. В классическом BSD это про multicast. FreeBSD добавила отдельный
SO_REUSEPORT_LB (12.0+) с Linux-подобной семантикой; в OpenBSD аналога нет. Многопроцессные
серверы, полагающиеся на SO_REUSEPORT-балансировку, надо править.
7. Пути и линковка. Заголовки и библиотеки пакетов — в /usr/local/include и
/usr/local/lib, компилятор туда сам не смотрит:
$ cc -I/usr/local/include -L/usr/local/lib -o app app.c -lpq
# либо через pkg-config, который знает про префикс
$ cc $(pkg-config --cflags --libs libpq) -o app app.c
8. Docker нет. Есть jails, есть podman на FreeBSD (развивается), есть OCI-рантайм runj
(экспериментальный). Если ваша схема поставки — исключительно образы OCI, BSD пока неудобен.
9. Компилятор по умолчанию — clang (в FreeBSD и OpenBSD; в NetBSD исторически gcc). Код, завязанный на расширения GCC, может не собраться.
10. Шелл /bin/sh — не bash. Это POSIX-совместимый ash (FreeBSD) или pdksh (OpenBSD).
Bash-измы ([[ ]], массивы, source) в скриптах с #!/bin/sh молча ломаются.
kqueue: почему это самая красивая часть BSD
Если вы пишете сетевые сервисы — это раздел ради которого стоит читать статью. В Linux, чтобы
дождаться разнородных событий, приходится собирать зоопарк: epoll для сокетов, inotify для
файлов, timerfd для таймеров, signalfd для сигналов, eventfd для пробуждения из другого
потока, waitid/pidfd для детей. Пять разных API, каждый со своими правилами, и всё сводится в
один epoll через файловые дескрипторы.
В BSD есть один механизм — kqueue(2), введённый Джонатаном Лемоном во FreeBSD 4.1
(USENIX 2001, «Kqueue: A Generic and Scalable Event Notification Facility»).
Событие описывается фильтром: EVFILT_READ, EVFILT_WRITE, EVFILT_TIMER, EVFILT_SIGNAL,
EVFILT_VNODE (изменения файла), EVFILT_PROC (события процесса), EVFILT_USER (пробуждение
вручную), EVFILT_AIO.
искать в хеш-таблице по fd не нужно
Рабочий скелет event loop:
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <err.h>
int kq = kqueue();
if (kq == -1) err(1, "kqueue");
struct kevent changes[4];
int n = 0;
/* сокет: данные на чтение, edge-triggered */
EV_SET(&changes[n++], listen_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, conn);
/* таймер на 5 секунд — без отдельного timerfd */
EV_SET(&changes[n++], 1, EVFILT_TIMER, EV_ADD, NOTE_MSECONDS, 5000, NULL);
/* сигнал SIGHUP — без signalfd и без обработчика сигналов */
signal(SIGHUP, SIG_IGN); /* обязательно: kqueue не заменяет диспозицию */
EV_SET(&changes[n++], SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
/* смерть дочернего процесса — без SIGCHLD и waitpid в обработчике */
EV_SET(&changes[n++], child_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
struct kevent events[64];
for (;;) {
/* changes применяются и события забираются одним системным вызовом */
int nev = kevent(kq, changes, n, events, 64, NULL);
n = 0;
if (nev == -1) { if (errno == EINTR) continue; err(1, "kevent"); }
for (int i = 0; i < nev; i++) {
struct kevent *e = &events[i];
if (e->flags & EV_ERROR) { /* ошибка регистрации — в e->data */ continue; }
switch (e->filter) {
case EVFILT_READ:
/* e->data — сколько байт доступно ДО чтения. Можно сразу
выделить буфер нужного размера, а не читать вслепую. */
handle_read(e->udata, (size_t)e->data);
break;
case EVFILT_TIMER: on_tick(); break;
case EVFILT_SIGNAL: on_reload(); break; /* безопасный контекст! */
case EVFILT_PROC: reap((pid_t)e->ident); break;
}
}
}
Три вещи, которые kqueue делает лучше epoll и которые стоит понять:
- Регистрация и ожидание — один системный вызов. В epoll на каждое изменение интереса нужен
отдельный
epoll_ctl. При тысячах соединений с постоянным переключением read/write это заметный поток syscalls. Цена перехода в ядро разобрана в Системные вызовы и IPC. udata— произвольный указатель. epoll даёт 64-битное поле в union, и общепринятая практика — класть туда указатель, но kqueue делает это явной частью контракта, вместе с отдельнымident.dataнесёт полезную нагрузку. ДляEVFILT_READэто количество доступных байт, для слушающего сокета — длина очереди backlog, дляEVFILT_VNODE— флаги изменения. epoll сообщает только факт готовности.
Хорошая новость: писать это руками почти никогда не нужно. libevent, libev, libuv, Go runtime,
tokio (через mio), Erlang/BEAM — все умеют kqueue и выбирают его автоматически. Читайте
man 2 kqueue — это образцовая man-страница.
Как устроена работа с системой: rc.d и ports
Инициализация в BSD — это rc.d: shell-скрипты с зависимостями, разрешаемыми rcorder(8) по
ключевым словам REQUIRE/PROVIDE/BEFORE. Подробно — в
Системы инициализации; здесь минимум для работы:
# Включить сервис (запись в /etc/rc.conf) и запустить
$ sysrc nginx_enable=YES # FreeBSD
$ service nginx start
$ service nginx status
$ rcctl enable nginx # OpenBSD — один инструмент на всё
$ rcctl start nginx
$ rcctl set nginx flags "-c /etc/nginx/custom.conf"
Установка софта из портов, когда нужны нестандартные опции сборки:
# FreeBSD: дерево портов — это Makefile'ы, а не готовые бинарники
$ pkg install -y nginx # быстро, бинарный пакет
# либо со своими опциями:
$ cd /usr/ports/www/nginx
$ make config # интерактивное меню опций
$ make install clean
# Промышленный способ: собрать свой репозиторий пакетов с нужными опциями
$ pkg install poudriere
$ poudriere jail -c -j 14amd64 -v 14.2-RELEASE
$ poudriere bulk -j 14amd64 -f mylist.txt
Обновление системы:
# FreeBSD — бинарные обновления
$ freebsd-update fetch install
$ pkg upgrade
# OpenBSD — патчи безопасности и переход на следующий релиз
$ syspatch # бинарные патчи текущего релиза
$ sysupgrade # переход на следующий релиз, перезагрузка
$ pkg_add -u # обновление пакетов
Типичные заблуждения
«BSD — это такой Linux-дистрибутив». Нет. Это независимая система с собственным ядром, собственной libc и собственной родословной. Общее с Linux — только POSIX-стандарт и часть портированного софта. Ядро FreeBSD не имеет с Linux общего кода.
«BSD мёртв / это музей». FreeBSD отдаёт значительную часть мирового трафика Netflix; WhatsApp на пике держал ~2 млн TCP-соединений на один FreeBSD-сервер; Junos от Juniper — это FreeBSD; PlayStation — FreeBSD. OpenBSD выпускает релиз каждые полгода без единого срыва с 1996 года.
«BSD-лицензия — просто более слабая GPL». Это разные цели, а не разные уровни строгости. BSD максимизирует распространение кода, GPL — гарантирует возврат улучшений.
«В BSD нет софта». В ports FreeBSD ~35 тысяч пакетов, в pkgsrc — около 27 тысяч. Есть почти всё, кроме проприетарных бинарников (Docker Desktop, некоторые драйверы GPU, ряд коммерческих агентов мониторинга) — и вот их отсутствие как раз бывает блокером.
«OpenBSD безопасен, значит на нём мой сервис безопасен». Механизмы защиты OpenBSD спасают от
эксплуатации ошибок памяти. Ваша SQL-инъекция, ваш IDOR и ваш забытый /admin без аутентификации
остаются ровно там же. pledge/unveil ограничивают ущерб — но только если вы их вызовете.
«Jail = Docker». Jail — это изоляция, а не упаковка. Docker — это ещё и формат образа, реестр,
слои и воспроизводимая сборка. Ближайший аналог этой части в FreeBSD — не jail, а связка
poudriere + ZFS-снапшоты, и она про другое.
«kqueue быстрее epoll» (или наоборот). На реальных нагрузках разница между ними на порядки меньше, чем разница между «правильной архитектурой event loop» и неправильной. Ценность kqueue — в единообразии API, а не в микросекундах.
Как попробовать за двадцать минут
# Проще всего — виртуалка. Образы qcow2/vmdk есть на сайтах проектов.
$ curl -O https://download.freebsd.org/releases/VM-IMAGES/14.2-RELEASE/amd64/Latest/FreeBSD-14.2-RELEASE-amd64.qcow2.xz
$ unxz FreeBSD-14.2-RELEASE-amd64.qcow2.xz
$ qemu-system-x86_64 -m 2048 -smp 2 -drive file=FreeBSD-14.2-RELEASE-amd64.qcow2,if=virtio -nic user,hostfwd=tcp::2222-:22
# OpenBSD ставится с одного install-образа минут за пять — установщик текстовый,
# но самый понятный из всех, что я видел: он задаёт правильные вопросы и имеет
# разумные ответы по умолчанию.
$ curl -O https://cdn.openbsd.org/pub/OpenBSD/7.7/amd64/install77.img
Первые команды, которые стоит выполнить, чтобы почувствовать разницу:
$ man hier # ГДЕ ЧТО ЛЕЖИТ в файловой системе — с объяснениями
$ man tuning # FreeBSD: осмысленный текст про тюнинг системы
$ man afterboot # OpenBSD: что сделать сразу после установки
$ man pf.conf # синтаксис файрвола, который читается как английский
$ apropos socket # поиск по всем man-страницам
Мини-итог
- BSD — четыре независимые ОС с общим предком 4.4BSD-Lite и одним общим принципом:
система разрабатывается целиком, одной командой, в одном репозитории. Отсюда base system,
качество документации, физическая граница
/usrпротив/usr/localи отсутствие стабильного syscall ABI. - FreeBSD — производительность и полнота: ZFS, jails, bhyve, DTrace, kTLS. Выбор для серверов, storage и сетевых нагрузок.
- OpenBSD — корректность любой ценой:
pledge,unveil, W^X, KARL, privsep, аудит. Выбор для всего, что смотрит в интернет. Плюс бесплатный «фаззер» для вашего C-кода. - NetBSD — портируемость и rump kernels: ядро, которое запускается как процесс.
- DragonFly — альтернативная модель SMP и HAMMER2.
- Для прикладного программиста три главных практических отличия: kqueue вместо зоопарка
Linux-механизмов ожидания, отсутствие
/proc(всё черезsysctl), BSD-версии утилит вместо GNU coreutils.
Источники и что читать дальше
- Marshall Kirk McKusick, George Neville-Neil, Robert Watson, «The Design and Implementation of the FreeBSD Operating System», 2nd ed. — каноническая книга по внутренностям FreeBSD.
- McKusick, Bostic, Karels, Quarterman, «The Design and Implementation of the 4.4BSD Operating System» — исторический первоисточник.
- FreeBSD Handbook и FreeBSD Architecture Handbook — docs.freebsd.org
- OpenBSD FAQ и man-страницы — openbsd.org/faq, man.openbsd.org
- Theo de Raadt, «pledge(2) and unveil(2)» — доклады и слайды: openbsd.org/papers
- Jonathan Lemon, «Kqueue: A Generic and Scalable Event Notification Facility», USENIX 2001 — people.freebsd.org/~jlemon/papers/kqueue.pdf
- Poul-Henning Kamp, Robert Watson, «Jails: Confining the omnipotent root», SANE 2000 — phk.freebsd.dk/pubs/sane2000-jail.pdf
- Robert Watson et al., «Capsicum: practical capabilities for UNIX», USENIX Security 2010 — cl.cam.ac.uk/research/security/capsicum
- Antti Kantee, «Flexible Operating System Internals: The Design and Implementation of the Anykernel and Rump Kernels» — book.rumpkernel.org
- Otto Moerbeek о malloc в OpenBSD — openbsd.org/papers (доклады про omalloc и его диагностические возможности)
- NetBSD Guide — netbsd.org/docs/guide
- DragonFly BSD handbook и статьи по HAMMER2 — dragonflybsd.org
- Michael W. Lucas, «Absolute FreeBSD», 3rd ed. и «Absolute OpenBSD», 2nd ed. — лучшие практические книги для входа.
Что дальше
Мы разобрали ветвь Unix, которая осталась Unix’ом. Но мир ОС этим не исчерпывается: рядом живут macOS с гибридным XNU (где BSD-userland надет на mach-микроядро), Windows NT с совершенно иной родословной и объектной моделью ядра, Solaris/illumos с DTrace и зонами, радикальный Plan 9 и системы реального времени, где важна не пропускная способность, а предсказуемость задержки.
Другие ОС: macOS и XNU, Windows NT, Solaris/illumos, Plan 9, RTOS