один структурный выбор 1991 года и то, что из него следует до сих порone 1991 structural choice, and what still follows from it today
Linux — монолитное ядро: весь код ядра работает в одном адресном пространстве, а не разнесён по изолированным серверам, общающимся через сообщения. Это не забытая деталь ранней реализации, а осознанный выбор, публично защищённый Торвальдсом в 1992 году, — и он же определяет, почему в Linux нет отдельного системного вызова для создания потока: clone() создаёт и процессы, и потоки одним и тем же механизмом, различие — просто в том, что из ресурсов родителя расшарено.Linux is a monolithic kernel: all kernel code runs in one address space, rather than being split across isolated servers talking via messages. This isn't a forgotten detail of an early implementation — it's a deliberate choice, publicly defended by Torvalds in 1992 — and it's the same choice behind why Linux has no separate syscall for creating a thread: clone() creates both processes and threads through the same mechanism, the difference being purely which of the parent's resources get shared.
Архитектура ядра определяет, где физически выполняется код разных подсистем ОС (планировщик, драйверы, файловая система, сеть) и как они друг с другом взаимодействуют. Два полюса спектра:A kernel's architecture determines where the code for different OS subsystems (scheduler, drivers, filesystem, networking) physically runs, and how they talk to each other. Two ends of the spectrum:
| монолитное ядроmonolithic kernel | микроядроmicrokernel | |
|---|---|---|
| где выполняется кодwhere the code runs | весь код ядра — в одном привилегированном адресном пространствеall kernel code — in one privileged address space | в ядре — только минимум (планировщик, IPC, базовая память); драйверы, ФС — отдельные процессы в пространстве пользователяonly the bare minimum in the kernel (scheduler, IPC, basic memory); drivers, filesystems — separate user-space processes |
| как подсистемы общаютсяhow subsystems communicate | прямой вызов функции — почти бесплатно по накладным расходамa direct function call — almost free in overhead | сообщения через IPC — переключение контекста и копирование данных на каждый вызовmessages via IPC — a context switch and data copy on every call |
| цена бага в драйвереthe cost of a driver bug | потенциально роняет всё ядро — общее адресное пространствоcan potentially bring down the whole kernel — shared address space | падает только сам процесс драйвера, ядро продолжает работатьonly the driver's own process crashes, the kernel keeps running |
| примерыexamples | Linux, традиционныйtraditional Unix | seL4, QNX |
Между этими полюсами лежат гибридные ядра — Windows NT и XNU (macOS/iOS) держат в привилегированном режиме больше, чем «чистое» микроядро (ради производительности), но структурно разделяют компоненты сильнее монолита. Linux к гибридным не относится — он монолитный по структуре с самого начала, а модульность реализована другим механизмом (см. ниже).Between these poles sit hybrid kernels — Windows NT and XNU (macOS/iOS) keep more in privileged mode than a "pure" microkernel (for performance), but structurally separate components more than a monolith does. Linux isn't hybrid — it's been monolithic in structure from the start, with modularity handled by a different mechanism (see below).
29 января 1992 года Эндрю Таненбаум (Andrew Tanenbaum), автор Minix и профессор, публично раскритиковал молодое ядро Линуса Торвальдса в Usenet-группе comp.os.minix, в посте, озаглавленном «LINUX is obsolete». Его аргумент был предельно конкретным, не общими словами о «плохом дизайне»: Minix была построена как микроядро — файловая система и управление памятью работали как отдельные процессы вне ядра, драйверы ввода-вывода — тоже отдельные процессы. Linux же был монолитным — «весь ОС — один a.out файл, который выполняется в 'kernel mode'», по формулировке Таненбаума — что он назвал «гигантским шагом назад в 1970-е».On January 29, 1992, Andrew Tanenbaum, Minix's author and a professor, publicly criticized Linus Torvalds's young kernel in the Usenet group comp.os.minix, in a post titled "LINUX is obsolete." His argument was precise, not vague hand-waving about "bad design": Minix was built as a microkernel — the filesystem and memory management ran as separate processes outside the kernel, I/O drivers too were separate processes. Linux, meanwhile, was monolithic — "the whole OS is a single a.out file that executes in 'kernel mode'," in Tanenbaum's own words — which he called "a giant step back to the 1970s."
Торвальдс не оспаривал техническую характеристику — он согласился, что Linux монолитный и что «микроядра красивее», и что «с теоретической и эстетической точки зрения Linux проигрывает». Его контраргумент был не архитектурным, а прагматичным: если бы ядро GNU (планировавшееся как микроядро) было готово к тому моменту, он бы вообще не начинал Linux — но оно готово не было, а Linux уже реально работал. «Побеждает тем, что доступен уже сейчас», а не теоретическим изяществом. К дискуссии присоединились и другие ранние разработчики ядра — Питер МакДональд (один из первых мейнтейнеров дистрибутивов), Дэвид Миллер, Теодор Тсо — все на стороне Торвальдса. Смысл этого спора важен не как курьёз давно минувших дней: он и есть точная формулировка компромисса, актуального по сей день — производительность и доступность здесь и сейчас против изоляции и теоретической элегантности.Torvalds didn't dispute the technical characterization — he agreed Linux was monolithic and that "microkernels are nicer," and that "from a theoretical and aesthetical standpoint Linux loses." His counterargument wasn't architectural, it was pragmatic: if the GNU kernel (planned as a microkernel) had been ready by then, he wouldn't have started Linux at all — but it wasn't ready, and Linux was already actually working. It "wins heavily on the point of being available now," not on theoretical elegance. Other early kernel developers joined the discussion too — Peter MacDonald (an early distro maintainer), David S. Miller, Theodore Ts'o — all on Torvalds's side. The debate's substance matters not as a historical curiosity from long ago: it's the exact formulation of a trade-off still live today — performance and availability right now, versus isolation and theoretical elegance.
Довод Таненбаума про «жёсткость» монолита адресован механизмом, появившимся уже в 2.0 (1996) — Loadable Kernel Modules (LKM). Драйвер устройства, оформленный как модуль, можно подключить к работающему ядру и отключить обратно без перезагрузки и без перекомпиляции всего ядра целиком. Технически модуль после загрузки становится частью того же самого адресного пространства, что и остальное ядро — то есть архитектурно это всё ещё монолит, просто модульно собираемый в рантайме, а не единственный статический бинарник времени сборки. LKM не решает проблему изоляции (баг в модуле всё ещё может уронить всё ядро), но снимает практическую боль «пересобирать ядро ради каждого нового драйвера», на которую и указывал спор 1992 года.Tanenbaum's point about the monolith's "rigidity" is addressed by a mechanism that arrived already in 2.0 (1996) — Loadable Kernel Modules (LKM). A device driver packaged as a module can be attached to a running kernel and detached again without a reboot or recompiling the whole kernel. Technically, once loaded a module becomes part of the exact same address space as the rest of the kernel — architecturally it's still a monolith, just modularly assembled at runtime instead of a single static build-time binary. LKM doesn't solve the isolation problem (a bug in a module can still bring down the whole kernel), but it removes the practical pain of "rebuild the kernel for every new driver" that the 1992 debate pointed at.
Ядро Linux не проводит на своём уровне строгого различия между «процессом» и «потоком» — обе сущности представлены одной и той же структурой task_struct и планируются одним и тем же планировщиком одинаково. Разница между ними — не в типе структуры, а в том, какие ресурсы конкретный task_struct расшаривает с родителем: адресное пространство (память), таблицу открытых файловых дескрипторов, обработчики сигналов. Все эти сущности порождаются одним системным вызовом — clone() — которому передаётся набор флагов, определяющий, что именно унаследовать совместно, а что получить своё собственное.The Linux kernel doesn't draw a strict line at its own level between a "process" and a "thread" — both are represented by the same task_struct structure and scheduled the same way by the same scheduler. The difference between them isn't in the structure's type, but in which resources a given task_struct shares with its parent: the address space (memory), the open file descriptor table, signal handlers. All of these entities are spawned by one system call — clone() — passed a set of flags that determines exactly what to inherit jointly and what to get its own private copy of.
// fork() по сути — clone() с почти без общих флагов:
// новый task_struct получает свою копию памяти, свои файловые дескрипторы
clone(SIGCHLD, ...);
// pthread_create() по сути — clone() с флагами полного расшаривания:
// новый task_struct работает в ТОЙ ЖЕ памяти, что и родитель — это и есть "поток"
clone(CLONE_VM | CLONE_FILES | CLONE_FS |
CLONE_SIGHAND | CLONE_THREAD, ...);
CLONE_VM — ключевой флаг: если он установлен, новый task_struct получает не копию адресного пространства родителя, а ссылку на то же самое mm_struct — запись в памяти, сделанная одним, немедленно видна другому. Это буквально и есть определение потока на уровне ядра: не отдельная сущность с урезанными правами, а обычный task, которому просто не выделили собственную память. CLONE_FILES аналогично расшаривает таблицу файловых дескрипторов — открыл файл в одном потоке, он открыт и в другом.CLONE_VM is the key flag: if it's set, the new task_struct doesn't get a copy of the parent's address space — it gets a reference to the exact same mm_struct; a memory write by one is immediately visible to the other. That's literally the kernel-level definition of a thread: not a separate stripped-down entity, but an ordinary task that simply wasn't given its own memory. CLONE_FILES similarly shares the file descriptor table — open a file in one thread, and it's open in the other too.
Сам Торвальдс не оспаривал теоретическое превосходство микроядерного дизайна в чистоте изоляции — он согласился с этим прямо. Победа Linux была не архитектурным доказательством, а результатом того, что рабочее ядро существовало здесь и сейчас, пока конкурирующий микроядерный проект (GNU Hurd) ещё не был готов к использованию. Путать «Linux оказался более практичным выбором для 1990-х» с «монолит архитектурно превосходит микроядро» — неточность, которую не совершал даже сам Торвальдс.Torvalds himself didn't dispute microkernel design's theoretical superiority in isolation purity — he agreed with it outright. Linux's win wasn't an architectural proof, it was the result of a working kernel existing right then, while the competing microkernel project (GNU Hurd) wasn't yet ready for use. Conflating "Linux was the more practical choice for the 1990s" with "a monolith architecturally beats a microkernel" is an inaccuracy Torvalds himself never made.
LKM решает проблему удобства (не нужно пересобирать ядро под каждый драйвер), но не проблему изоляции — загруженный модуль исполняется в том же привилегированном адресном пространстве, что и остальное ядро, и его баг способен уронить всю систему точно так же, как баг в статически встроенном коде. Модульность в смысле «можно подгружать и выгружать код в рантайме» и микроядерная изоляция в смысле «баг в одном компоненте не роняет остальные» — независимые оси, а не синонимы.LKM solves the convenience problem (no need to rebuild the kernel for every driver), not the isolation problem — a loaded module executes in the exact same privileged address space as the rest of the kernel, and a bug in it can bring down the whole system exactly as a bug in statically built-in code could. Modularity in the sense of "code can be loaded and unloaded at runtime" and microkernel-style isolation in the sense of "a bug in one component doesn't take down the others" are independent axes, not synonyms.
Формулировка «поток — облегчённая версия процесса» подразумевает, что процесс первичен, а поток — урезанный вариант. На уровне ядра вернее обратное: базовая единица — task_struct, порождаемая clone(), а «процесс» в привычном смысле — это просто частный случай clone(), где не расшарено вообще ничего из ресурсов родителя. Ни та, ни другая сущность не является урезанной версией другой — обе получаются одним и тем же механизмом с разным набором флагов.The phrasing "a thread is a lightweight version of a process" implies the process is primary and the thread is a stripped-down variant. At the kernel level it's more accurate the other way around: the base unit is task_struct, spawned by clone(), and a "process" in the familiar sense is just a specific case of clone() where none of the parent's resources are shared at all. Neither entity is a cut-down version of the other — both come out of the same mechanism with a different set of flags.