Принципы разработки
Прежде чем перейти к подробному рассмотрению свойств надежности нашей системы, кратко обсудим принципы разработки, которыми мы руководствовались в стремлении к надежности:
- Простота.
- Модульность.
- Наименьшая авторизация.
- Отказоустойчивость.
Во-первых, мы сохраняем свою систему настолько простой, насколько это возможно, так что ее легко понять, и можно с большей вероятностью поддерживать ее в корректном состоянии. Это относится как к высокоуровневому проектированию, так и к реализации. Наша разработка позволяет структурно избежать известных проблем, таких как исчерпание ресурсов. При потребности мы явно обмениваем ресурсы и эффективность на надежность. Например, в ядре статически объявляются все структуры данных вместо того, чтобы динамически выделять память при необходимости. Хотя мы можем недоиспользовать некоторую память, этот подход является очень простым и никогда не приводит к ошибкам. Другим примером является то, что мы умышленно не реализовали нити. Может быть, мы заплатили за это некоторой потерей эффективности (а может быть, и нет), но зато не должны беспокоиться о потенциальных «состояниях гонок» (race condition) и синхронизации, что существенно облегчает жизнь программистам.
Во-вторых, мы разделили свою систему на набор небольших независимых модулей. Использование свойств модульности, таких как ограничение распространения сбоев, является ключевым элементом разработки нашей системы. Путем полного разделения операционной системы на модули мы можем установить «брандмаэры», сквозь которые не могут распространяться ошибки, что приводит к более надежной системе. Для предотвращения косвенного влияния сбоев в одном модуле на какой-либо другой модуль мы структурным образом уменьшаем их взаимозависимость, насколько это возможно. В тех случаях, когда это невозможно из-за природы модулей, мы применяем дополнительные средства поддержки безопасности. Например, файловая система зависит от драйверов устройств, но она разрабатывается таким образом, чтобы быть готовой к обработке сбоев драйвера.
В третьих, мы обеспечиваем соблюдение принципа наименьшей авторизации. Хотя изоляция сбоев помогает сдерживать их распространение, сбой в полномочном модуле все еще может вызвать значительный ущерб. Поэтому мы понижаем уровень привилегий всех пользовательских процессов до предельно допустимого минимума. В ядре поддерживаются битовые массивы и списки, определяющие возможности процессов. В частности, имеются шкала допустимых вызовов ядра и список допустимых адресов назначения сообщений. Эта информация сохраняется в элементах таблицы процессов, и поэтому ее можно строго контролировать, и ею просто управлять. Информация об авторизации инициируется во время загрузки системы, главным образом, на основе конфигурационных таблиц, создаваемых системным администратором.
В четвертых, при разработке системы мы явным образом учитываем возможность к устойчивости к некоторым сбоям. Все серверы и драйверы управляются и отслеживаются специальным сервером, называемым сервером реинкарнации, который может справляться с двумя видами проблем. Если системный процесс завершается непредвиденным образом, это немедленно распознается, и процесс перезапускается. Кроме того, периодически проверяется состояние каждого системного процесса для проверки его правильного функционирования. Если процесс функционирует неправильно, он принудительно завершается и перезапускается. Так работает механизм отказоустойчивости: сбойный компонент заменяется, но система все время продолжает работать.