Отличие новой системы заключалось ещё и в модели разработки. MINIX 1 и MINIX 2 были фактически личными творениями Эндрю Таненбаума, а все дополнения к ней, вроде ставшего знаменитым патча Брюса Эванса (именно с его дополнением использовал MINIX Линус Торвальдс для работы над своей будущей операционкой), носили, в силу условий распространения, сугубо неофициальный характер.
К разработке же MINIX 3 Таненбаум с самого начала привлёк широкий круг участников – от своего соавтора по второму изданию учебника Альберта Вудхалла до студентов, аспирантов и сотрудников Университета Врийе, а также волонтёров. Состав участников проекта, по понятным причинам, был весьма текучим, и перечислить их всех поимённо не представляется возможным. Однако нельзя не отметить среди них наших соотечественников – упомянутого выше Романа Игнатова и Евгения Иванова.
О микроядрах
В MINIX 3 воплотилось представление Таненбаума и его соратников о том, какова должна быть современная операционная система. Однако, чтобы говорить нём, надо опять обратиться к истории – на этот раз к истории микроядерных операционок (краем этот вопрос был затронут в предыдущей главе).
Каждый школьник-линуксоид знает, что MINIX с самого своего рождения представляла собой микроядерную ОС. А вот какая он, эта микроядерность?
Для начала зададимся вопросом, а что же такое ядро вообще? Традиционно ядро определяется как программа, обеспечивающая взаимодействие всего остального системного и прикладного софта с аппаратной частью компьютера, и распределение его ресурсов между приложениями. В соответствие с этим ядро функционирует в отдельной области памяти, которая так и называется – пространством ядра. Память же, отводимая под все остальные программы, именуется пользовательским пространством; протекающим там процессам доступ в пространство ядра закрыт, и как-либо влиять на ядро, в том числе и негативно (вследствие ошибок в программе или злого умысла), они не могут. Но все процессы внутри пространства ядра взаимодействуют друг с другом, и ошибка в одном из компонентов может повлечь за собой тяжкие последствия, вплоть до краха системы.
Понятие аппаратуры компьютера, однако, оказывается двояким. С одной стороны, это те узлы, без которых машина в принципе не может функционировать – процессор и память. Эта сторона взаимодействия охватывается такими службами ядра, как обработчик прерываний, средства запуска и останова процессов, планировщик задач, механизм межпроцессного взаимодействия.
С другой стороны, в понятие аппаратной части включаются и внешние, по отношению к системе процессор/память, устройства, от видеокарт и жестких дисков до принтеров, сканеров, сетевых адаптеров и многого, многого другого.
Более того, у этой аппаратной медали есть еще и третья сторона – сервисы доступа к файловым системам, сетевым протоколам и так далее. Они представляют собой связующее звено между собственно внешними устройствами и пользовательскими программами. Например, сервисы доступа к файловым системам обеспечивают возможность взаимодействия между дисковыми устройствами, несущими файловые системы, и обращающимися к ним приложениями.
Ядра, обеспечивающие все три рассмотренные функции, именуются монолитными. И они вполне успешно функционировали до тех пор, пока внешних устройств и сервисов было мало. Однако со временем количество и тех, и других стало расти, как снежный ком. Вспомним, сколько на наших глазах появилось только критически важных устройств, таких, как новые дисковые интерфейсы или файловые системы.
В результате ядра стали катастрофически увеличиваться в размерах. Что влекло за собой а) непроизводительные расходы ресурсов, в первую очередь памяти, и б) рост числа ошибок, обусловленный огромными объемами «ядрёного» кода, недоступного восприятию человека.
С первой болезнью научились бороться посредством модульного подхода: многие части ядра, обеспечивающие работу отдельных внешних устройств (т.н. драйверы устройств) или ядерных сервисов (в Linux их подчас также называют драйверами, например, драйвер файловой системы «имя рек»), могут не встраиваться жестко в исполняемый файл – образ ядра, а подключаться к нему в виде внешних модулей. И ныне ядра всех активно развивающихся UNIX-подобных систем, таких, как Linux или BSD, являются модульно-монолитными (подчас их для краткости называют и просто модульными).
Внешние модули могут как загружаться в память при старте системе, так и подгружаться в работающей системе, по необходимости. Нередко они обладают и способностью выгружаться, когда эта необходимость пропадёт. Однако в любом случае модули функционируют в пространстве ядра, так что их введение проблемы общей устойчивости не решает: криво написанный драйвер устройства все равно сохраняет способность обрушить систему. А поскольку, как уже говорилось, сложность кода ядра растёт, возрастает и вероятность появления ошибок, в том числе и критичных для работы системы.
Один из «косметических» вариантов решения проблемы был предложен Мэттом Диллоном и реализован им в системе DragonFlyBSD, о которой рассказывалось в предыдущей главе. Прошедшие с её появления годы доказали жизнеспособность этой реализации. Однако проблемы «большого» монолитного ядра это не снимало.
Для кардинального решения проблемы устойчивости ядра и были придуманы микроядра. Идея их – в том, чтобы, по словам Таненбаума, «вынести ядро за пределы ядра». То есть оставить в ядре только средства управления базовой аппаратурой, а драйверы устройств и сервисы выделить в отдельные программы, функционирующие в пользовательском пространстве памяти. При необходимости они обращаются к функциям ядра через специальные процедуры, но влиять на него каким-либо образом не могут. Такой подход приводит к повышению надежности системы, но снижает производительность, поскольку требует дополнительных накладных расходов.
Собственно, идея микроядра появилась очень давно, чуть ли не одновременно с самыми первыми UNIX'ами. Однако долгое время производительность машин не позволяла эффективно использовать микроядра в составе практически применяемых систем. Тем не менее, различных их реализаций было создано очень много. Время от времени то или иное микроядро пропагандировалось как основа операционной системы будущего, но удачных реализаций законченных микроядерных систем оказалось довольно мало.
Среди удачных решений на базе микроядра наибольшей известностью пользуется QNX. Правда, эта система лишь недавно начала то приоткрывать свой код, то закрывать его обратно, и потому об её устройстве известно немного. Да и как ОС общего назначения она никогда не позиционировалась.
Из свободных микроядерных реализаций наибольшую известность приобрело микроядро Mach, разрабатывавшееся вплоть до второй половины 90-х годов университетами – сначала Карнеги-Меллона, а затем штата Юта. Различные его версии в разное время составляли основу законченных систем, как свободных – BSD Mach (она же xMach) или Yamitt, так и проприетарных – NeXT и продолжившей её дело MacOS X. Все они имели в своем составе микроядро Mach, поверх которого запускалась драйверно-сервисная часть от ядра BSD, собранная в виде отдельного модуля.
Впрочем, из всех перечисленных систем только BSD Mach можно назвать по настоящему микроядерной, так как у него BSD-окружение ядра функционировало в пользовательском пространстве. И у NeXT, и у MacOS X BSD-окружение запускалось в том же пространстве ядра, так что микроядерными их можно назвать только по имени. А Yamitt в том виде, в каком я её наблюдал, просто не запускалась вообще.
Наконец, как мы видели, начиная с 1987 года и по настоящее время существует MINIX 1/2, основанная на собственной реализации микроядра. Каковую, в рамках очерченных для ннеё задач, можно считать удачной. А вот будет ли сопутствовать удача её потомку – микроядру нашей героини, MINIX 3, рассудит история.
Собственно о MINIX 3
Для начала заметим, что Таненбаум, по его же словам, не ставил самоцелью создание именно микроядерной ОС: задачей его команды было просто построение системы надежной и безопасной. Другое дело, что решение этой задачи им всегда виделось именно в микроядерной архитектуре. Почему он и предложил таковое, причем в виде, кардинально отличающемся от всех предлагавшихся ранее реализаций.