Каждый школьник-линуксоид знает, что MINIX с самого своего рождения представляла собой микроядерную ОС. А вот какая он, эта микроядерность?
Для начала зададимся вопросом, а что же такое ядро вообще? Традиционно ядро определяется как программа, обеспечивающая взаимодействие всего остального системного и прикладного софта с аппаратной частью компьютера, и распределение его ресурсов между приложениями. В соответствие с этим ядро функционирует в отдельной области памяти, которая так и называется – пространством ядра. Память же, отводимая под все остальные программы, именуется пользовательским пространством; протекающим там процессам доступ в пространство ядра закрыт, и как-либо влиять на ядро, в том числе и негативно (вследствие ошибок в программе или злого умысла), они не могут. Но все процессы внутри пространства ядра взаимодействуют друг с другом, и ошибка в одном из компонентов может повлечь за собой тяжкие последствия, вплоть до краха системы.
Понятие аппаратуры компьютера, однако, оказывается двояким. С одной стороны, это те узлы, без которых машина в принципе не может функционировать – процессор и память. Эта сторона взаимодействия охватывается такими службами ядра, как обработчик прерываний, средства запуска и останова процессов, планировщик задач, механизм межпроцессного взаимодействия.
С другой стороны, в понятие аппаратной части включаются и внешние, по отношению к системе процессор/память, устройства, от видеокарт и жестких дисков до принтеров, сканеров, сетевых адаптеров и многого, многого другого.
Более того, у этой аппаратной медали есть еще и третья сторона – сервисы доступа к файловым системам, сетевым протоколам и так далее. Они представляют собой связующее звено между собственно внешними устройствами и пользовательскими программами. Например, сервисы доступа к файловым системам обеспечивают возможность взаимодействия между дисковыми устройствами, несущими файловые системы, и обращающимися к ним приложениями.
Ядра, обеспечивающие все три рассмотренные функции, именуются монолитными. И они вполне успешно функционировали до тех пор, пока внешних устройств и сервисов было мало. Однако со временем количество и тех, и других стало расти, как снежный ком. Вспомним, сколько на наших глазах появилось только критически важных устройств, таких, как новые дисковые интерфейсы или файловые системы.
В результате ядра стали катастрофически увеличиваться в размерах. Что влекло за собой а) непроизводительные расходы ресурсов, в первую очередь памяти, и б) рост числа ошибок, обусловленный огромными объемами «ядрёного» кода, недоступного восприятию человека.
С первой болезнью научились бороться посредством модульного подхода: многие части ядра, обеспечивающие работу отдельных внешних устройств (т.н. драйверы устройств) или ядерных сервисов (в Linux их подчас также называют драйверами, например, драйвер файловой системы «имя рек»), могут не встраиваться жестко в исполняемый файл – образ ядра, а подключаться к нему в виде внешних модулей. И ныне ядра всех активно развивающихся UNIX-подобных систем, таких, как Linux или BSD, являются модульно-монолитными (подчас их для краткости называют и просто модульными).
Внешние модули могут как загружаться в память при старте системе, так и подгружаться в работающей системе, по необходимости. Нередко они обладают и способностью выгружаться, когда эта необходимость пропадёт. Однако в любом случае модули функционируют в пространстве ядра, так что их введение проблемы общей устойчивости не решает: криво написанный драйвер устройства все равно сохраняет способность обрушить систему. А поскольку, как уже говорилось, сложность кода ядра растёт, возрастает и вероятность появления ошибок, в том числе и критичных для работы системы.
Один из «косметических» вариантов решения проблемы был предложен Мэттом Диллоном и реализован им в системе DragonFlyBSD, о которой рассказывалось в предыдущей главе. Прошедшие с её появления годы доказали жизнеспособность этой реализации. Однако проблемы «большого» монолитного ядра это не снимало.
Для кардинального решения проблемы устойчивости ядра и были придуманы микроядра. Идея их – в том, чтобы, по словам Таненбаума, «вынести ядро за пределы ядра». То есть оставить в ядре только средства управления базовой аппаратурой, а драйверы устройств и сервисы выделить в отдельные программы, функционирующие в пользовательском пространстве памяти. При необходимости они обращаются к функциям ядра через специальные процедуры, но влиять на него каким-либо образом не могут. Такой подход приводит к повышению надежности системы, но снижает производительность, поскольку требует дополнительных накладных расходов.