История ОС: от Multics и Unix до BSD, Linux и наших дней
История ОС — не музейная витрина. Это единственный способ объяснить, почему creat() пишется без «e», почему ps aux и ps -ef — это два разных синтаксиса одной команды, почему на Linux вы пишете epoll, а на FreeBSD kqueue, и почему #define _GNU_SOURCE вообще существует. Каждая странность современного системного API — это окаменевшее решение, принятое конкретным человеком в конкретный год под конкретное железо. Разобравшись в цепочке решений, вы перестаёте зубрить API и начинаете его выводить.
Эта статья — карта родословной. Дальше по треку мы будем разбирать устройство ядер, планировщиков и файловых систем; здесь мы выясняем, откуда взялись сами понятия и почему семейства ОС различаются именно так, а не иначе.
Что было до Unix: пакетная обработка и дефицит машин
В 1950-х компьютер был редким и дорогим ресурсом, а «операционная система» — набором библиотек для ввода-вывода. Работа шла пакетами (batch): вы приносили колоду перфокарт оператору, он ставил её в очередь, и через сутки вы получали распечатку. Ошибка в одной карте стоила дня.
Ключевая идея, изменившая всё, — разделение времени (time-sharing): пусть машина быстро переключается между пользователями, и каждому кажется, что она его. Джон Маккарти предложил это в 1959-м, а первая работающая система — CTSS (Compatible Time-Sharing System, MIT, Фернандо Корбато, 1961) — доказала, что идея реализуема. CTSS дала нам вещи, которые сегодня кажутся воздухом: интерактивный терминал, пользовательские каталоги, команду для отправки сообщения другому залогиненному пользователю (прародитель почты) и понятие «редактор текста».
Из CTSS выросло всё остальное — в том числе и амбиции, которые породили Multics.
Multics: великолепный провал, из которого выросло всё
В 1964-м MIT, Bell Labs и General Electric начали проект Multics (MULTiplexed Information and Computing Service). Цель формулировалась как «computing utility» — вычисления как коммунальная услуга: подключаетесь к розетке и берёте столько процессора, сколько нужно, платите по счётчику. В 1964 году это звучало как научная фантастика; в 2026-м это называется AWS.
Multics был технически амбициозен настолько, что многие его идеи вернулись в мейнстрим только через 30-40 лет:
- Сегментированная виртуальная память с отображением файлов. В Multics файл — это сегмент, который вы просто адресуете. Никакого
read()/write()— вы обращаетесь к данным как к памяти. Идея вернулась какmmap(2)в 4.2BSD и как основа современных баз данных. - Кольца защиты (8 колец, а не два режима). Промежуточные уровни привилегий позволяли писать «полупривилегированные» подсистемы. x86 унаследовал 4 кольца от этой линии мышления — и индустрия де-факто использует два.
- Динамическое связывание. Символ разрешался при первом обращении. Наши
.soи.dll— оттуда. - Иерархическая файловая система с произвольной вложенностью и списки контроля доступа (ACL).
- Написан на языке высокого уровня (PL/I), а не на ассемблере — тогда это было почти ересью.
- Защита от переполнения стека на уровне архитектуры: сегменты имели границы, стек рос в сторону, где переполнение упиралось в нечитаемую область.
Проблема была одна: Multics опаздывал и был чудовищно сложен. Bell Labs вышла из проекта в апреле 1969 года, не получив ничего, кроме опыта. Сам Multics дожил до 30 октября 2000 года — последняя система была выключена в министерстве обороны Канады, — и это единственная ОС, получившая рейтинг безопасности B2 по «Оранжевой книге» при живом сетевом использовании.
Но главное наследие Multics — не код, а фрустрация двух инженеров, которые вернулись из проекта в Bell Labs и решили сделать то же самое, только маленькое.
1969: Unix на списанном PDP-7
Летом 1969-го Кен Томпсон остался на три недели один (жена уехала с ребёнком к родителям) и написал на списанном PDP-7 ядро, командный интерпретатор, редактор и ассемблер — по неделе на компонент. Формальным поводом была игра Space Travel, которую он портировал с GE-645; настоящим — желание получить обратно комфортную интерактивную среду, которая была в Multics.
Название UNICS (позже UNIX) придумал Брайан Керниган — как каламбур: «кастрированный Multics», система «на одного» вместо «на многих».
Ключевое отличие от Multics — сознательное упрощение до предела:
| Multics | Unix |
|---|---|
| Сегментная память, файлы отображаются в адресное пространство | Файл — просто последовательность байт, читается через read() |
| 8 колец защиты | 2 режима: user / kernel |
| ACL на каждом сегменте | 9 бит прав: rwxrwxrwx |
| PL/I, сложный компилятор | ассемблер → B → C |
| Устройства — особый класс объектов | Устройства — файлы в /dev |
| Мощный, но монолитный набор возможностей | Много маленьких программ, соединяемых конвейером |
«Файл — это просто поток байт без структуры» — самое важное решение во всей истории Unix. Multics, VMS, MVS, а позже и Windows имели типизированные файлы с записями. Unix сказал: типизацию делает прикладная программа, ядро не знает и не хочет знать. Именно поэтому grep работает и с исходником, и с логом, и с выводом ps — а на VMS каждая утилита должна была знать формат.
В 1970-м проект получил финансирование под предлогом системы подготовки документов для патентного отдела Bell Labs (отсюда troff, ed и вообще вся текстовая культура Unix) и переехал на PDP-11/20. Первое издание руководства — 3 ноября 1971 года; отсюда мы отсчитываем UNIX v1.
1973: переписывание на C — момент, который изменил индустрию
Деннис Ритчи развил язык B (сам производный от BCPL) в C, добавив типы и структуры. В 1973 году ядро Unix переписали на C — примерно 90% кода перестало быть машинно-зависимым.
До этого момента ОС была неотделима от железа: чтобы получить ОС на новой машине, её писали заново. После — ОС стала портируемой. В 1977-1978 Unix перенесли на Interdata 8/32, доказав, что перенос возможен, и с этого момента любой производитель железа мог получить приличную ОС за месяцы, а не годы.
Практический след этого решения виден прямо сейчас:
# Заголовки системных вызовов Linux — до сих пор C ABI, придуманный в 1973-м
$ grep -n "SYSCALL_DEFINE3(read" /usr/src/linux/fs/read_write.c
622:SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
# Каждый язык — Go, Rust, Python — в конечном счёте вызывает этот C-совместимый интерфейс
$ strace -e trace=read python3 -c "open('/etc/hostname').read()" 2>&1 | head -3
read(3, "myhost\n", 4096) = 7
read(3, "", 4096) = 0
Статья Ритчи и Томпсона «The UNIX Time-Sharing System» (CACM, июль 1974) — до сих пор лучшие 11 страниц про то, что такое ОС. Прочитайте её; она читается легче большинства современных блогпостов.
Как Unix вышел из Bell Labs: юридическая случайность
AT&T была монополистом связи и по антимонопольному постановлению 1956 года не имела права торговать компьютерными продуктами. Поэтому Unix раздавали университетам практически бесплатно — вместе с исходниками, потому что «это не продукт, это исследовательский артефакт».
Так родилась уникальная ситуация: тысячи студентов получили полный исходник работающей ОС. Джон Лайонс в Университете Нового Южного Уэльса напечатал в 1976-м Lions’ Commentary on UNIX 6th Edition — построчный разбор всех ~9000 строк ядра V6. AT&T запретила распространение книги, и она стала самой копируемой самиздатовской книгой в истории CS. Сегодня она легально доступна, и это до сих пор лучший способ увидеть ядро целиком.
Version 7 Unix (1979) — последняя «настоящая» research-версия от Bell Labs: Bourne shell, awk, make, find, полноценный набор системных вызовов. Практически всё, что вы набираете в терминале, существовало в V7.
Раскол первый: Berkeley против AT&T
Университет Беркли получил V6 в 1974-м. Билл Джой и коллеги начали доводить систему, и распространяли результат как BSD (Berkeley Software Distribution). Хронология важна, потому что каждая версия принесла что-то, чем вы пользуетесь ежедневно:
- 1BSD (1978) — редактор
ex, компилятор Pascal. - 2BSD (1979) —
viи C shell (csh). Да,vim— это правнук патча 1979 года. - 3BSD (1979) — порт на VAX и страничная виртуальная память (до этого Unix только свопил процессы целиком).
- 4.2BSD (1983) — переломный релиз, профинансированный DARPA: сокеты и TCP/IP в ядре, Fast File System (FFS) с длинными именами файлов и цилиндрическими группами, надёжные сигналы,
select(2). - 4.3BSD (1986), 4.3BSD-Tahoe (1988), 4.3BSD-Reno (1990).
- 4.4BSD (1993) и 4.4BSD-Lite (1994) — последние релизы группы CSRG.
Сокеты Беркли — это API, которым вы пишете сеть в 2026 году, вне зависимости от языка и ОС. Даже Windows Sockets (Winsock) — это калька с BSD-интерфейса.
Параллельно AT&T, после разделения компании в 1984-м получившая право продавать софт, коммерциализировала свою линию: System III (1982) → System V (1983) → SVR2 → SVR3 (1987, STREAMS, разделяемая память SysV IPC) → SVR4 (1988).
SVR4 был попыткой примирения: AT&T и Sun совместно слили System V, BSD, SunOS и Xenix в одну систему. Технически удалось; политически — вызвало панику остальных вендоров и запустило Unix wars: коалиция OSF (IBM, DEC, HP) против UNIX International (AT&T, Sun). Пока Unix-вендоры судились и фрагментировали рынок, Microsoft спокойно занимала desktop.
Что от раскола осталось в вашем терминале
# BSD-синтаксис (без дефиса) и System V-синтаксис (с дефисом) — оба живы в procps
$ ps aux | head -2
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 168404 12996 ? Ss 09:12 0:03 /sbin/init
$ ps -ef | head -2
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 09:12 ? 00:00:03 /sbin/init
# Флаг -a у echo, поведение echo -n, семантика tail -f — всё это точки расхождения BSD/SysV.
# Поэтому в скриптах используют printf, а не echo:
$ printf '%s\n' "переносимо всегда"
Другой живой след — семантика сигналов. В System V обработчик сбрасывался в SIG_DFL после первого срабатывания (ненадёжные сигналы), в BSD — нет. Поэтому signal(2) до сих пор считается непортируемым, и правильный код выглядит так:
#include <signal.h>
#include <stdio.h>
static volatile sig_atomic_t stop = 0; /* только sig_atomic_t безопасен в обработчике */
static void on_sigint(int signo) {
(void)signo;
stop = 1; /* никакого printf: он не async-signal-safe */
}
int main(void) {
struct sigaction sa;
sa.sa_handler = on_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* перезапускать прерванные системные вызовы */
/* sigaction(2) стандартизован POSIX и ведёт себя одинаково на Linux,
FreeBSD, OpenBSD, NetBSD и macOS. signal(2) — нет. */
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
while (!stop) pause();
puts("завершились чисто");
return 0;
}
POSIX: попытка склеить осколки
Когда стало ясно, что «Unix» — это дюжина несовместимых систем (SunOS, HP-UX, AIX, Ultrix, Xenix, Irix, Dynix…), IEEE выпустил стандарт POSIX 1003.1-1988. Название придумал Ричард Столлман. Смысл: зафиксировать общее подмножество, чтобы исходник компилировался везде.
POSIX не столько создал будущее, сколько зафиксировал прошлое — и это важно понимать. Он не описывает epoll, kqueue, cgroups, namespaces, io_uring. Всё интересное, что появилось после 1990-х, стандартом не покрыто. Отсюда практическое правило: POSIX — это ваш фундамент переносимости, но продакшн-производительность всегда живёт в непортируемых расширениях.
# Какую версию POSIX заявляет ваша система
$ getconf _POSIX_VERSION
200809 # Linux/glibc: POSIX.1-2008
# FreeBSD 14: 200112
# macOS 15: 200112
# OpenBSD 7.x: 200809
# Полный список ограничений, которые обязана декларировать система
$ getconf -a | grep -E 'PATH_MAX|ARG_MAX|OPEN_MAX'
Feature-test-макросы — прямое следствие исторического раскола. В одном и том же <unistd.h> живут разные наборы функций в зависимости от того, какое наследство вы просите:
#define _POSIX_C_SOURCE 200809L /* только POSIX.1-2008 — максимальная переносимость */
/* #define _DEFAULT_SOURCE */ /* + традиционные BSD/SysV-расширения (бывший _BSD_SOURCE) */
/* #define _GNU_SOURCE */ /* + всё, что есть в glibc: memfd_create, pipe2, gettid... */
#include <unistd.h>
Раскол второй: суд, из-за которого мир получил Linux, а не BSD
К 1991 году в Беркли шла работа по вычищению всего AT&T-кода из BSD, чтобы систему можно было распространять свободно. Результат — Networking Release 2 (1991), из которого Билл и Линн Джолиц сделали 386BSD (1992) — первый свободный Unix для массового железа.
И тут случился суд. USL (Unix System Laboratories, дочерняя структура AT&T) подала иск против BSDi и Университета Беркли в 1992 году, утверждая, что в BSD остался их код. Дело тянулось до начала 1994 года и закончилось мировым соглашением: из 18 000 файлов спорными признали три, ещё несколько потребовали правок в копирайтах. Появился 4.4BSD-Lite — юридически чистая база.
Но два года неопределённости оказались решающими. Именно в это окно — 25 августа 1991 года — студент из Хельсинки Линус Торвальдс написал в comp.os.minix:
«I’m doing a (free) operating system (just a hobby, won’t be big and professional like gnu) for 386(486) AT clones.»
Почему Linux, а не BSD? Не из-за технического превосходства. BSD в 1992-м был зрелее по всем параметрам. Причины были такие:
- Юридическая чистота. Никто не хотел строить бизнес на системе, которую судят.
- Лицензия GPL. Торвальдс переключил Linux на GPL в версии 0.12 (январь 1992). Копилефт заставлял корпорации возвращать доработки в общий ствол — Linux не фрагментировался так, как коммерческие Unix.
- Готовое окружение GNU. Проект GNU Ричарда Столлмана (анонс — сентябрь 1983) к 1991 году имел
gcc,bash,binutils,coreutils,emacs— всё, кроме ядра (Hurd, начатый в 1990-м, до сих пор не готов к продакшену). Linux оказался ровно недостающим куском. - Модель разработки. «Release early, release often» — Торвальдс выпускал версии несколько раз в неделю, принимая патчи от кого угодно. Эрик Реймонд описал это в «The Cathedral and the Bazaar» (1997).
Показательный эпизод — дебаты Танненбаума и Торвальдса (январь 1992, тред «LINUX is obsolete»). Автор MINIX утверждал, что монолитное ядро — устаревшая архитектура, а будущее за микроядрами. Технически Танненбаум был во многом прав; практически победил тот, кто отдавал работающий код каждую неделю. Полная переписка — обязательное чтение перед статьёй про https://courses.digitable.life/post/operating-systems/02-kernel-architectures/.
Родословная: одна картинка
Обратите внимание на единственную стрелку слияния — SVR4 (1988) действительно вобрал код и идеи линии BSD (сокеты, FFS, работу с сигналами), и на этой базе выросла Solaris 2. Все остальные ветвления в истории Unix были расколами, а не слияниями: код расходился, а обратно сходились только стандарты.
Хронология ключевых событий
Ветви, живые сегодня
Linux
Хронология развития самого ядра важна для понимания, откуда взялись знакомые механизмы:
| Версия | Год | Что принесла |
|---|---|---|
| 0.01 | 1991 | 10 239 строк, только 386, только Minix FS |
| 1.0 | 1994 | сеть TCP/IP, ~176 000 строк |
| 2.0 | 1996 | SMP (правда, через один big kernel lock), загружаемые модули |
| 2.4 | 2001 | USB, ext3, iptables/netfilter |
| 2.6 | 2003 | O(1)-планировщик, epoll, futex, NPTL, sysfs, cgroups (2007), namespaces |
| — | 2005 | переход на git, написанный Торвальдсом за две недели после разрыва с BitKeeper |
| 3.x–6.x | 2011–… | CFS→EEVDF, eBPF, io_uring (5.1, 2019), Rust (6.1, 2023) |
Ключевая вещь, которую стоит понимать про Linux: у него нет спецификации. Есть код и правило «we do not break userspace». Торвальдс формулирует его жёстко: если обновление ядра ломает работающую программу — это баг ядра, а не программы. Это правило — причина, по которой бинарник, собранный в 2005 году, до сих пор запускается.
# Проверить границу стабильности API: syscall-таблица никогда не переиспользует номера
$ ausyscall --dump | head -5 # Linux
0 read
1 write
2 open
3 close
4 stat
# Номер 0 = read с 1969 года — по сути, тот же контракт, что в V1
Семейство BSD
386BSD распался на две ветви в 1993-м, а из NetBSD в 1995-м Тео де Раадт (после конфликта с core team) отпочковал OpenBSD. Различие философий стоит запомнить, оно последовательно проявляется во всём:
Практическая разница, которую вы почувствуете первой, — модель асинхронного I/O. kqueue(2) появился во FreeBSD 4.1 (2000, Джонатан Лемон), epoll — в Linux 2.5.44 (2002). Это разные API с разной семантикой:
/* FreeBSD / macOS / OpenBSD / NetBSD: kqueue — единый механизм
для сокетов, файлов, сигналов, таймеров, процессов и событий FS */
int kq = kqueue();
struct kevent ev;
EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
kevent(kq, &ev, 1, NULL, 0, NULL); /* регистрация */
struct kevent out[64];
int n = kevent(kq, NULL, 0, out, 64, NULL); /* ожидание */
/* Linux: epoll — только для файловых дескрипторов; сигналы, таймеры
и файловые события пришлось отдельно «одескрипторивать»:
signalfd(2), timerfd_create(2), inotify(7), eventfd(2), pidfd_open(2) */
int ep = epoll_create1(0);
struct epoll_event e = { .events = EPOLLIN, .data.fd = fd };
epoll_ctl(ep, EPOLL_CTL_ADD, fd, &e);
struct epoll_event outs[64];
int m = epoll_wait(ep, outs, 64, -1);
Это не вкусовщина: BSD решил задачу обобщённо и сразу, Linux — точечно и итеративно. Обе стратегии имеют цену. У kqueue — сложный интерфейс с 6-полевой структурой; у Linux — зоопарк из шести разных *fd-механизмов, который в итоге и подтолкнул к созданию io_uring. Библиотеки вроде libuv и libevent существуют ровно затем, чтобы этот раскол спрятать.
macOS и XNU: третья линия
Отдельная ветвь тянется от Mach — микроядра, разработанного в Carnegie Mellon (Рик Рашид, Ави Тевэниан, 1985-1994). Стив Джобс, уйдя из Apple, построил на Mach 2.5 + BSD 4.3 систему NeXTSTEP (1989). Apple купила NeXT в конце 1996-го, и NeXTSTEP стал основой Mac OS X 10.0 (март 2001).
Ядро называется XNU («X is Not Unix») и представляет собой гибрид: Mach отвечает за IPC, задачи, память и порты; BSD-слой поверх него даёт POSIX-процессы, сокеты, VFS и системные вызовы; драйверы живут в IOKit — объектной подсистеме на подмножестве C++.
# macOS: где видны обе половины
$ uname -a
Darwin host 24.0.0 Darwin Kernel Version 24.0.0: ... root:xnu-11215.1.10~2/RELEASE_ARM64_T6000 arm64
$ sysctl kern.ostype kern.osrelease
kern.ostype: Darwin
kern.osrelease: 24.0.0
# Mach-порты — не POSIX. Это IPC-примитив уровня микроядра
$ lsmp -p $$ | head -5
# А вот kqueue — чистое BSD-наследие, работает точно как во FreeBSD
$ man 2 kqueue
macOS формально сертифицирован как UNIX 03 — то есть это один из немногих оставшихся «настоящих» UNIX по букве стандарта, в отличие от Linux, который UNIX-подобен, но не сертифицирован.
Windows NT: линия, идущая не от Unix вообще
Важно понимать: NT — \ast \ast не\ast \ast ветвь Unix. Его родословная идёт от \ast \ast VMS\ast \ast (DEC), а конкретно — от Дэйва Катлера, архитектора VMS и RSX-11M, которого Microsoft переманила в 1988-м вместе с командой. Windows NT 3.1 вышла в июле 1993-го.
Отличия архитектурные, а не косметические:
| Аспект | Unix-линия | Windows NT |
|---|---|---|
| Создание процесса | fork() + exec() — два шага, наследование по умолчанию |
CreateProcess() — один вызов с 10 параметрами, наследование явное |
| Дескрипторы | малые целые числа int fd, наследуются при fork |
непрозрачные HANDLE, наследование задаётся флагом |
| Асинхронный I/O | readiness-модель (select/epoll/kqueue) |
completion-модель (\ast \ast IOCP\ast \ast ) — ближе к io_uring |
| Ядро | монолитное с модулями | гибридное, слой HAL, подсистемы (Win32, POSIX, OS/2) |
| Пути | /, чувствительность к регистру, единое дерево |
\, регистронезависимость, буквы дисков, объектный менеджер \??\ |
| Права | 9 бит + ACL опционально | ACL с самого начала, SID вместо uid |
Ирония в том, что в 2016 году круг замкнулся: Microsoft выпустила \ast \ast WSL1\ast \ast — подсистему, транслировавшую Linux-системные вызовы в NT-вызовы (ровно то, для чего в NT изначально был механизм подсистем), а в 2019-м \ast \ast WSL2\ast \ast — с настоящим ядром Linux в лёгкой виртуальной машине. Транслировать оказалось дороже, чем виртуализировать: fork() принципиально плохо ложится на модель NT.
Solaris, illumos и то, что они подарили всем
Sun прошла интересный путь: SunOS 1-4 были BSD-based, а SunOS 5 / \ast \ast Solaris 2\ast \ast (1992) перешли на SVR4. В Solaris 10 (2005) появились три вещи, которые изменили индустрию:
- \ast \ast ZFS\ast \ast — файловая система с контрольными суммами, снапшотами и встроенным RAID (сегодня в FreeBSD, Linux через OpenZFS, TrueNAS);
- \ast \ast DTrace\ast \ast — динамическая трассировка живого ядра без перекомпиляции (идейный предок Linux eBPF);
- \ast \ast Zones\ast \ast — контейнеры за 8 лет до Docker.
Oracle закрыла исходники в 2010-м, но сообщество форкнуло последнюю открытую версию как \ast \ast illumos\ast \ast — на нём живут OmniOS и SmartOS. Подробнее об этой ветви — в статье https://courses.digitable.life/post/operating-systems/16-other-operating-systems/, а о ZFS — в https://courses.digitable.life/post/operating-systems/05-filesystems/.
Практическая археология: как история проступает в вашем коде
Это самая полезная часть. Пройдёмся по вещам, которые вы видите ежедневно, и объясним их происхождение.
\ast \ast /dev/tty и слово «tty».\ast \ast TTY = teletype, механический телетайп. Все настройки терминала — скорость, эхо, обработка ^H — это эмуляция железки 1960-х.
$ stty -a
speed 38400 baud; rows 50; columns 204; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; ...
# «speed 38400 baud» на локальном терминале — чистая фикция, поле осталось от модемов
$ stty sane # рецепт «починить сломанный терминал», которому 40 лет
\ast \ast ETXTBSY — «Text file busy».\ast \ast «Text» здесь означает сегмент кода (см. SVG выше). Ядро V6 запрещало писать в файл, который кто-то исполняет. Живо до сих пор:
$ cp /bin/sleep ./mysleep && ./mysleep 100 &
$ echo x > ./mysleep
bash: ./mysleep: Text file busy # errno 26 — привет из 1975 года
\ast \ast creat() без «e».\ast \ast Кен Томпсон, отвечая на вопрос, о чём он жалеет в дизайне Unix: «I’d spell creat with an e». Функция до сих пор в POSIX, хотя это просто open(path, O_WRONLY|O_CREAT|O_TRUNC, mode).
\ast \ast Разделение /bin и /usr/bin.\ast \ast В 1971-м диск RK05 на PDP-11 кончился, и часть системы перенесли на второй диск, смонтированный в /usr. Позже придумали обоснование («/usr — для не-критичных программ»), и это разделение прожило 50 лет. Только около 2012 года дистрибутивы начали делать /usr merge — сливать их обратно симлинками.
$ ls -ld /bin
lrwxrwxrwx 1 root root 7 Apr 3 2025 /bin -> usr/bin # современный Debian/Fedora/Arch
$ man hier # каноническое описание иерархии
\ast \ast dd с его if=/of=/bs=.\ast \ast Единственная утилита Unix с синтаксисом в стиле IBM JCL — потому что её писали для чтения лент с мейнфреймов, и автор скопировал привычный операторам синтаксис.
\ast \ast /proc.\ast \ast Придуман Томом Килианом для 8-го издания Research Unix (1984), развит в Plan 9 до тотальности («всё — файл, действительно всё»), скопирован в Solaris и Linux. Разные семейства пришли к разным выводам:
# Linux: /proc — основной интерфейс наблюдаемости, разросшийся далеко за процессы
$ cat /proc/self/status | head -4
$ cat /proc/meminfo | head -3
$ cat /proc/sys/vm/swappiness
# FreeBSD: procfs объявлен устаревшим, данные берутся через sysctl(3)
$ sysctl kern.proc.pid.$$
$ sysctl vm.stats.vm.v_free_count
# OpenBSD: /proc удалён полностью в 5.7 (2015) как источник уязвимостей
$ ls /proc
ls: /proc: No such file or directory
# macOS: /proc нет, есть sysctl и proc_pidinfo(3)
$ sysctl kern.boottime
Это отличная иллюстрация философского различия: Linux наращивает интроспекцию, OpenBSD её сокращает как поверхность атаки. Подробно — в https://courses.digitable.life/post/operating-systems/14-observability-and-performance/.
Init. BSD имел один скрипт /etc/rc, System V — /etc/inittab с runlevel’ами и /etc/rc.d/. Раскол дожил до наших дней: FreeBSD использует rc.d в BSD-стиле, Linux в основном перешёл на systemd, macOS — на launchd. Об этом целая статья: https://courses.digitable.life/post/operating-systems/11-init-systems/.
Как выбирать API сегодня, зная историю
pthread, open, fcntl"] C -->|Да| D D -->|Только Linux| F["epoll / io_uring / cgroups /
namespaces / eBPF"] D -->|Только BSD-семейство| G["kqueue / jails / pledge /
capsicum"] D -->|Только Windows| H["IOCP / Job Objects / ETW"] D -->|Несколько| I{Готовы поддерживать N бэкендов?} I -->|Да| J["Абстракция с #ifdef,
по бэкенду на семейство"] I -->|Нет| K["Возьмите libuv, tokio, mio,
Boost.Asio — они уже это сделали"] E --> L[Тестируйте на всех целевых ОС в CI] F --> L G --> L H --> L J --> L K --> L
Правило, выведенное из истории: пишите бизнес-логику на POSIX, а горячий путь — на нативном API конкретной ОС, спрятанном за интерфейсом. Именно так устроены PostgreSQL (src/backend/port/), nginx (src/event/modules/), Go runtime (runtime/netpoll_epoll.go и netpoll_kqueue.go).
Типичные заблуждения
«Linux — это Unix». Нет. Linux — Unix-подобная система, написанная с нуля, без единой строки AT&T-кода, и не сертифицированная по Single UNIX Specification. Сертифицированы macOS, AIX, HP-UX, z/OS UNIX System Services и (исторически) Solaris. Это юридический факт, а не оценка качества.
«BSD проиграл Linux технически». В 1992-м BSD был зрелее: лучше сеть, лучше VM, лучше документация. Решили лицензия, юридический риск и модель разработки. Технические аргументы в истории ОС редко бывают решающими.
«GPL всегда лучше BSD-лицензии для проекта». Это компромисс, а не превосходство. GPL защищает от форков-без-возврата и удерживает ствол единым — так Linux избежал фрагментации, погубившей коммерческий Unix. BSD-лицензия обеспечивает максимальное распространение кода: сетевой стек BSD оказался в Windows, стек и libc — в PlayStation, kqueue — в macOS. Разные цели.
«Микроядра проиграли». Не проиграли. Mach живёт в каждом iPhone и Mac. L4 крутится в baseband-процессорах миллиардов телефонов. seL4 — формально верифицированное микроядро — используется в авиации и обороне. QNX управляет автомобилями. Проиграла лишь идея «микроядро для универсального сервера общего назначения». Разбор — в https://courses.digitable.life/post/operating-systems/02-kernel-architectures/.
«Всё это устарело, сейчас всё в контейнерах». Контейнеры — это namespaces + cgroups + chroot, то есть механизмы, растущие прямо из Unix-модели процессов и файловой системы. chroot(2) появился в V7 в 1979 году. FreeBSD jails — 2000 год. Solaris Zones — 2004. Docker (2013) сделал упаковку удобной, не изобретя изоляции. См. https://courses.digitable.life/post/operating-systems/18-virtualization-and-containers/.
«POSIX гарантирует переносимость». POSIX гарантирует переносимость исходника в рамках описанного подмножества. Он не описывает производительность, не описывает поведение при исчерпании ресурсов, оставляет десятки мест «implementation-defined» и не покрывает ничего из того, что вы реально используете в высоконагруженном сервисе.
Что действительно стоит унести из этой истории
- Простой интерфейс переживает сложный. «Файл — поток байт» победил типизированные записи Multics и VMS.
read/writeпережили всё. - Портируемость важнее оптимальности. Переписывание Unix на C стоило производительности, но дало ОС полвека жизни.
- Лицензия — архитектурное решение. GPL против BSD определила судьбу двух систем сильнее, чем любой технический выбор.
- Ограничения железа превращаются в стиль. 64 KiB на PDP-11 породили культуру маленьких программ и конвейеров, которая пережила железо в тысячи раз.
- Обратная совместимость — это функция. «We do not break userspace» — причина, по которой Linux захватил серверы.
- Хорошие идеи возвращаются. Multics ждал 40 лет своей реабилитации: computing utility стал облаком, сегменты —
mmap, кольца — SMEP/SMAP и виртуализации, ACL — POSIX ACL и SELinux.
Источники
Книги:
- Dennis Ritchie, Ken Thompson. The UNIX Time-Sharing System. CACM, 1974 — первоисточник, 11 страниц.
- John Lions. Lions’ Commentary on UNIX 6th Edition, 1976. Доступна онлайн — построчный разбор всего ядра.
- Marshall Kirk McKusick et al. The Design and Implementation of the FreeBSD Operating System, 2nd ed., 2014 — каноническая книга по BSD-линии.
- Peter Salus. A Quarter Century of UNIX, 1994 — лучшая историческая монография.
- Andrew Tanenbaum. Modern Operating Systems, 5th ed. — исторические главы и разбор архитектурных развилок.
- Eric Raymond. The Art of Unix Programming, 2003 — про философию, местами спорно, но исторические главы сильные.
Первоисточники и архивы:
- The Unix Heritage Society — исходники V1–V7, PDP-11-эмуляторы, архивы почты.
- Multics History — сайт, поддерживаемый участниками проекта.
- Unix History Repository — вся история Unix с 1970 по сегодня в одном git-репозитории с непрерывной историей коммитов. Клонируйте и походите по
git log --reverse, это лучше любой статьи. - Тред «LINUX is obsolete» — полная переписка Танненбаума и Торвальдса.
- Linux kernel archives и git-история ядра.
- POSIX / Single UNIX Specification — сам стандарт, свободно доступен.
- The Open Group UNIX certification register — кто формально UNIX, а кто нет.
Man-страницы, которые стоит прочитать целиком: man 7 standards (FreeBSD), man 7 feature_test_macros (Linux), man 7 hier, man 2 intro.
Что дальше
История объяснила, почему ядра выглядят так, как выглядят. Следующий шаг — разобрать, как они устроены внутри: чем монолит отличается от микроядра, что такое гибрид XNU и NT на самом деле, зачем нужны экзоядра и unikernel’ы, и почему спор Танненбаума с Торвальдсом не закончен до сих пор.
Архитектуры ядер: монолитное, микроядро, гибридное, экзоядро, unikernel