Введение
Операционная система микроядро Mach (/m//ɑː//k/) – ядро, разработанное в Университете Карнеги — Меллона Ричардом Рашидом и Ави Теваньян для поддержки исследований операционных систем, в первую очередь распределённых и параллельных вычислений. Mach часто считается одним из самых ранних примеров микроядра. Однако не все версии Mach являются микроядрами. Производные Mach лежат в основе ядра операционной системы GNU Hurd и ядра XNU от Apple, используемого в macOS, iOS, iPadOS, tvOS и watchOS. Проект в Карнеги — Меллоне продолжался с 1985 по 1994 год и завершился выпуском Mach 3.0, который представляет собой истинное микроядро. Mach был разработан как замена ядра в версии BSD Unix, чтобы не требовалось разрабатывать новую операционную систему с нуля. Mach и его производные используются в ряде коммерческих операционных систем, включая все системы, использующие ядро XNU, которое включает в себя более раннюю версию Mach, не являющуюся микроядром, в качестве основного компонента. Система управления виртуальной памятью Mach также была принята в 4.4BSD разработчиками BSD в CSRG и присутствует в современных Unix-системах, основанных на BSD, таких как FreeBSD. Mach является логическим преемником ядра Accent, разработанного в Карнеги — Меллоне. Ведущий разработчик проекта Mach, Ричард Рашид, работает в Microsoft с 1991 года и основал подразделение Microsoft Research. Другой из первоначальных разработчиков Mach, Ави Теванян, ранее занимал должность руководителя отдела разработки программного обеспечения в NeXT, а затем был главным специалистом по программным технологиям в Apple Inc. до марта 2006 года. Позже он спросил руководителя проекта Рика Рашида о текущем названии проекта и получил ответ "MUCK", который произносился, но не был написан. Ричард, опираясь на итальянский алфавит, записал его как Mach. Рашиду так понравилось написание "Mach", предложенное Джузеппе, что оно и было принято.
Mach (/m//ɑː//k/) is a kernel developed at Carnegie Mellon University by Richard Rashid and Avie Tevanian to support operating system research, primarily distributed and parallel computing. Mach is often considered one of the earliest examples of a microkernel. However, not all versions of Mach are microkernels. Mach's derivatives are the basis of the operating system kernel in GNU Hurd and of Apple's XNU kernel used in macOS, iOS, iPadOS, tvOS, and watchOS. The project at Carnegie Mellon ran from 1985 to 1994, ending with Mach 3.0, which is a true microkernel. Mach was developed as a replacement for the kernel in the BSD version of Unix, so no new operating system would have to be designed around it. Mach and its derivatives exist within a number of commercial operating systems. These include all using the XNU operating system kernel which incorporates an earlier non microkernel Mach as a major component. The Mach virtual memory management system was also adopted in 4.4BSD by the BSD developers at CSRG, and appears in modern BSD derived Unix systems such as FreeBSD. Mach is the logical successor to Carnegie Mellon's Accent kernel. The lead developer on the Mach project, Richard Rashid, has been working at Microsoft since 1991; he founded the Microsoft Research division. Another of the original Mach developers, Avie Tevanian, was formerly head of software at NeXT, then Chief Software Technology Officer at Apple Inc. until March 2006. later asked project leader Rick Rashid about the project's current title and received "MUCK" as the answer, though not spelled out but just pronounced: which he, according to the Italian alphabet, wrote like Mach. Rashid liked Giuse's spelling "Mach" so much that it prevailed.
Трубы Unix
Ключевой концепцией в оригинальной операционной системе Unix была идея канала (pipe). Канал представлял собой абстракцию, позволявшую передавать данные как неструктурированный поток байтов от программы к программе. Используя каналы, пользователи (или программисты) могли объединять несколько программ для выполнения задач, последовательно передавая данные через ряд небольших программ. Это отличалось от типичных операционных систем того времени, которые требовали единой большой программы для обработки всей задачи, либо, альтернативно, использовали файлы для передачи данных, что было ресурсоемким и занимало много времени. Каналы строились на основе базовой системы ввода/вывода. Эта система, в свою очередь, основывалась на модели, в которой драйверы должны были периодически "блокироваться" в ожидании завершения задач. Например, драйвер принтера мог отправить строку текста на принтер и затем оставаться неактивным до завершения печати этой строки. В этом случае драйвер сообщал о блокировке, и операционная система позволяла другой программе выполняться, пока принтер не сигнализировал о готовности к приему новых данных. В системе каналов ограниченным ресурсом была память, и когда одна программа заполняла память, выделенную каналу, она автоматически блокировалась. Обычно это приводило к запуску потребляющей программы, которая снова освобождала канал. В отличие от файла, где весь файл должен быть прочитан или записан, прежде чем его сможет использовать следующая программа, каналы обеспечивали перемещение данных между несколькими программами поэтапно, без вмешательства программиста. Однако реализация каналов в буферах памяти вынуждала копировать данные из программы в программу, что было трудоемкой и ресурсоемкой операцией. Это делало концепцию каналов непригодной для задач, требующих быстрой обработки или низкой задержки, таких как большинство драйверов устройств. Ядро операционной системы и большая часть основной функциональности были написаны в виде единой большой программы. Когда в операционную систему добавлялась новая функциональность, например, компьютерные сети, размер и сложность ядра также увеличивались.
Новые концепции
Unix-трубы предложили концептуальную систему, которую можно было использовать для создания произвольно сложных решений из небольших взаимодействующих программ. Эти программы было легче разрабатывать и поддерживать, и они имели чётко определённые интерфейсы, упрощавшие программирование и отладку. Эти качества особенно важны для драйверов устройств, где малый размер и безошибочная работа критически необходимы. Существовало сильное стремление смоделировать ядро на той же основе – из небольших взаимодействующих программ. Одной из первых систем, использовавших систему, подобную трубам, в качестве основы операционной системы, стало ядро Aleph, разработанное в Университете Рочестера. Это привело к появлению концепции портов, которые по сути являлись реализацией общей памяти. В Aleph ядро было сведено к предоставлению доступа к аппаратному обеспечению, включая память и порты, в то время как обычные программы, использующие систему портов, реализовывали всё поведение – от драйверов устройств до пользовательских программ. Эта концепция значительно уменьшила размер ядра и позволила пользователям экспериментировать с различными драйверами, просто загружая их и соединяя во время выполнения. Это значительно упростило разработку нового кода операционной системы, которая в противном случае обычно требовала перезагрузки машины. Общая концепция небольшого ядра и внешних драйверов стала известна как микроядро. Aleph был реализован на миникомпьютерах Data General Eclipse и был тесно связан с ними. Эта машина была далека от идеала, поскольку требовала копирования памяти между программами, что приводило к значительным накладным расходам на производительность. Она также была довольно дорогой. Тем не менее, Aleph доказал надёжность базовой системы и продемонстрировал кластеризацию компьютеров, копируя память через ранний интерфейс Ethernet. Примерно в это же время на рынок вышло новое поколение центральных процессоров (CPU), предлагающих 32-битное адресное пространство и (первоначально опциональную) поддержку блока управления памятью (MMU). MMU обрабатывал инструкции, необходимые для реализации системы виртуальной памяти, отслеживая, какие страницы памяти используются различными программами. Это предложило новое решение для концепции портов, используя механизм копирования при записи, предоставляемый системой виртуальной памяти. Вместо копирования данных между программами, требовалось отправлять только данные, необходимые для указания MMU на предоставление доступа к той же памяти. Эта система обеспечивала межпроцессное взаимодействие с значительно более высокой производительностью. Эта концепция была подхвачена в Карнеги-Меллоне, где Aleph был адаптирован для рабочей станции PERQ и реализован с использованием копирования при записи. Порт был успешным, но полученное ядро Accent оказалось малопрактичным, поскольку не могло запускать существующее программное обеспечение. Более того, Accent был так же тесно связан с PERQ, как Aleph с Eclipse.
Разработка
Mach изначально разрабатывался как дополнительный код, написанный непосредственно в существующее ядро 4.2BSD, что позволило команде работать над системой задолго до её завершения. Работа началась с уже функционирующей системы межпроцессного взаимодействия Accent IPC/port, а затем перешла к другим ключевым компонентам ОС: задачам, потокам и виртуальной памяти. По мере завершения отдельных компонентов различные части системы BSD были переписаны для вызова функций Mach, и в процессе этого была произведена модификация 4.3BSD. К 1986 году система достигла стадии, когда могла работать автономно на DEC VAX. Хотя практической ценности это не имело, цель создания микроядра была достигнута. Вскоре после этого появились версии для IBM RT PC и рабочих станций Sun Microsystems на базе 68030, что продемонстрировало переносимость системы. К 1987 году список поддерживаемых платформ пополнился машинами Encore Multimax и Sequent Balance, что позволило протестировать возможности Mach на мультипроцессорных системах. В том же году состоялся выпуск первой публичной версии (Release 1), а в следующем году – второй (Release 2). На протяжении всего этого времени обещание "истинного" микроядра оставалось невыполненным. Эти ранние версии Mach включали в ядро большую часть 4.3BSD, систему, известную как POE Server, в результате чего ядро оказалось больше, чем UNIX, на котором оно базировалось. Однако идея заключалась в перемещении UNIX-уровня из ядра в пользовательское пространство, где его можно было бы легче модифицировать и даже полностью заменить. К сожалению, производительность оказалась серьёзной проблемой, и для её решения потребовалось внести ряд архитектурных изменений. Также исследователей беспокоили сложные вопросы лицензирования UNIX, поэтому эта ранняя попытка создать UNIX-подобную систему без лицензионных ограничений продолжала находить применение в ходе дальнейшей разработки Mach. В результате Mach 3 был выпущен в 1990 году и вызвал огромный интерес. Небольшая команда разработала Mach и портировала его на множество платформ, включая сложные мультипроцессорные системы, которые представляли серьёзные трудности для ядер старого типа. Это вызвало значительный интерес на коммерческом рынке, где ряд компаний рассматривали возможность смены аппаратных платформ. Если существующую систему можно было бы перенести на Mach, то, казалось, было бы легко изменить и платформу. Mach получил значительный импульс, когда Open Software Foundation (OSF) объявила о размещении будущих версий OSF/1 на Mach 2.5 и исследовании Mach 3. Mach 2.5 также был выбран для системы NeXTSTEP и ряда коммерческих производителей мультипроцессорных систем. Mach 3 послужил основой для ряда попыток портировать другие компоненты операционных систем для микроядра, включая Workplace OS от IBM и несколько проектов Apple по созданию кроссплатформенной версии классической Mac OS.
Проблемы с производительностью
Mach изначально задумывался как замена классической монолитной UNIX, и по этой причине содержал множество идей, схожих с UNIX. Например, Mach использовал систему разрешений и безопасности, основанную на файловой системе UNIX. Поскольку ядро было привилегированным (выполнялось в пространстве ядра) по отношению к другим серверам ОС и программному обеспечению, существовала возможность отправки ему команд от некорректно работающих или злонамеренных программ, которые могли бы повредить систему. Поэтому ядро проверяло каждое сообщение на предмет действительности. Кроме того, большая часть функциональности операционной системы должна была располагаться в программах пользовательского пространства, что означало необходимость предоставления этим программам дополнительных привилегий со стороны ядра, например, прямого доступа к аппаратному обеспечению. Некоторые из более эзотерических возможностей Mach также основывались на этом же механизме IPC. Например, Mach мог легко поддерживать многопроцессорные машины. В традиционном ядре требуется значительная работа для обеспечения его повторного входа или прерываемости, поскольку программы, работающие на разных процессорах, могли одновременно обращаться к ядру. В Mach компоненты операционной системы изолированы в серверах, которые, как и любая другая программа, могут выполняться на любом процессоре. Хотя теоретически ядро Mach также должно быть повторно входящим, на практике это не является проблемой, поскольку его время отклика настолько быстрое, что оно может просто ожидать и последовательно обслуживать запросы. Mach также включал сервер, который мог пересылать сообщения не только между программами, но и по сети, что было областью активной разработки в конце 1980-х и начале 1990-х годов. К сожалению, использование IPC практически для всех задач привело к серьезному снижению производительности. Тесты на аппаратном обеспечении 1997 года показали, что реализации UNIX на основе Mach 3.0 с одним сервером были примерно на 50% медленнее, чем нативная UNIX. Изучение точной природы проблем с производительностью выявило ряд интересных фактов. Один из них заключался в том, что сама IPC не была проблемой: существовали некоторые накладные расходы, связанные с отображением памяти, необходимым для ее поддержки, но это добавляло лишь небольшое время к выполнению вызова. Остальное, 80% времени, было связано с дополнительными задачами, которые ядро выполняло над сообщениями. Основными из них были проверка прав доступа к портам и проверка действительности сообщений. В тестах на 486DX 50 стандартный системный вызов UNIX занимал в среднем 21 мкс, в то время как эквивалентная операция с Mach IPC в среднем 114 мкс. Только 18 мкс из этого приходилось на аппаратное обеспечение; остальное – ядро Mach, выполняющее различные процедуры над сообщением. Для системного вызова, который ничего не делает, полный цикл в BSD требовал около 40 мкс, тогда как в пользовательской системе Mach – чуть менее 500 мкс. Когда Mach впервые серьезно использовался в версиях 2.x, производительность была ниже, чем у традиционных монолитных операционных систем, возможно, на 25%. Например, получение системного времени включает в себя вызов IPC к серверу пользовательского пространства, обслуживающему системные часы. Вызывающий процесс сначала переходит в ядро, вызывая переключение контекста и отображение памяти. Ядро затем проверяет, что вызывающий процесс имеет необходимые права доступа и что сообщение является действительным. Если это так, происходит еще одно переключение контекста и отображение памяти для завершения вызова к серверу пользовательского пространства. Затем процесс должен быть повторен для возврата результатов, что в сумме составляет четыре переключения контекста и отображения памяти, плюс две проверки сообщений. Эти накладные расходы быстро накапливаются при более сложных сервисах, где часто существуют пути кода, проходящие через множество серверов. Это был не единственный источник проблем с производительностью. Другой был связан с трудностями правильной обработки памяти, когда заканчивалась физическая память и требовалась подкачка. В традиционных монолитных операционных системах разработчики имели непосредственный опыт работы с тем, какие части ядра вызывали какие другие, что позволяло им точно настроить механизм подкачки, чтобы избежать выгрузки кода, который собирался использоваться. В Mach это было невозможно, поскольку ядро не имело реального представления о том, из чего состоит операционная система. Вместо этого им пришлось использовать единое универсальное решение, что усугубляло проблемы с производительностью. Mach 3 попытался решить эту проблему, предоставив простой механизм подкачки, полагаясь на механизмы подкачки пользовательского пространства для более специализированной работы. Но это оказалось малоэффективным. На практике любые преимущества, которые он давал, нивелировались дорогостоящей IPC, необходимой для его вызова. Другие проблемы с производительностью были связаны с поддержкой Mach многопроцессорных систем. С середины 1980-х до начала 1990-х годов производительность коммерческих процессоров росла примерно на 60% в год, в то время как скорость доступа к памяти росла всего на 7% в год. Это означало, что стоимость доступа к памяти значительно возросла за этот период, и поскольку Mach был основан на отображении памяти между программами, любое "промахивание кэша" замедляло вызовы IPC.
Потенциальные решения
IPC является серьезной проблемой для систем Mach 3. Однако концепция многосерверной операционной системы по-прежнему перспективна, хотя и требует дальнейших исследований. Разработчикам необходимо тщательно изолировать код в модули, избегая вызовов между серверами. Например, большая часть сетевого кода может быть размещена на одном сервере, что минимизирует IPC для обычных сетевых задач. Большинство разработчиков предпочли придерживаться первоначальной концепции POE – единого крупного сервера, обеспечивающего функциональность операционной системы. Для упрощения разработки они разрешили серверу операционной системы работать как в пользовательском пространстве, так и в пространстве ядра. Это позволяло разрабатывать в пользовательском пространстве, используя все преимущества оригинальной идеи Mach, а затем перемещать отлаженный сервер в пространство ядра для повышения производительности. С тех пор несколько операционных систем были созданы с использованием этого метода, известного как co-location, включая Lites, MkLinux, OSF/1 и NeXTSTEP/OPENSTEP/macOS. Микроядро Chorus сделало это встроенной функцией системы, позволяя серверам перемещаться в пространство ядра с помощью встроенных механизмов. Mach 4 предпринял попытку решить эти проблемы, представив более радикальный набор обновлений. В частности, было обнаружено, что программный код обычно не доступен для записи, поэтому вероятность накладных расходов из-за copy-on-write была невелика. Следовательно, было целесообразно не отображать память между программами для IPC, а вместо этого переносить используемый программный код в локальное адресное пространство программы. Это привело к концепции "шаттлов", и производительность, казалось, улучшилась, но разработчики продолжили работу над системой в полуработоспособном состоянии. Mach 4 также внедрил встроенные примитивы co-location, сделав их частью самого ядра. К середине 1990-х годов разработка микроядерных систем в значительной степени застопорилась, хотя рынок в целом полагал, что к 1990-м годам все современные операционные системы будут основаны на микроядрах. Основными широко распространенными применениями ядра Mach в настоящее время являются macOS от Apple и ее производная iOS, которые работают на сильно модифицированном гибридном ядре Open Software Foundation Mach Kernel (OSFMK 7.3), называемом "XNU", также используемом в OSF/1. В XNU файловые системы, сетевые стеки, а также функции управления процессами и памятью реализованы в ядре; файловые системы, сети и некоторые функции управления процессами и памятью вызываются из пользовательского режима через обычные системные вызовы, а не посредством передачи сообщений; сообщения Mach в XNU используются для связи между процессами пользовательского режима, а также для некоторых запросов из кода пользовательского режима в ядро и из ядра на серверы пользовательского режима.
Микроядра второго поколения
Дальнейший анализ показал, что проблема с производительностью IPC оказалась не такой очевидной, как могло показаться. Вспомним, что одно обращение к системному вызову (syscall) занимало 20 мкс в BSD – основе macOS, iOS, iPadOS, watchOS и tvOS.