Современные подходы к повышению отказоустойчивости операционных систем

Современные операционные системы до сих пор опираются на архитектурные принципы и решения, заложенные еще в начале своего развития.

В то время их выбор был обоснован состоянием развития вычислительных машин и их стоимостью, в пользу обеспечения максимальной эффективности и плотности реализации, уменьшения любых накладных вычислительных расходов.

В настоящее время ситуация значительно изменилась, но по-прежнему используются принципы, которые уже не являются адекватными по отношению к текущему состоянию развития. Прежде всего, речь идет об общем подходе к проектированию и реализации операционных систем в пользу обеспечения надежности и отказоустойчивости.

В настоящей статье проводится анализ некоторых из современных подходов к повышению отказоустойчивости ОС.

Использовать подход на основе изоляции драйверов предлагают Свифт и Бершад. Ими была создана прототипная подсистема, получившая название Nooks[1].

Nooks – это подсистема монолитного ядра, которая позволяет расширениям ядра, таким как динамические драйверы устройств и драйверы файловых систем, выполняться изолированно в ядре операционной системы.

В традиционном монолитном ядре любая ошибка в драйвере может повредить важные структуры ядра. Чтобы уменьшить риск последствий ошибок, Nooks позволяет выполнять драйверы в особых областях, для которых устанавливаются ограничения записи в адресном пространстве ядра.

Nooks отслеживает все попытки доступа или произошедшие сбои, и обеспечивает возможность автоматического восстановления.

В своем подходе авторы не предлагают разрабатывать новую системную архитектуру, а напротив – улучшить надежность существующих систем, главным образом уменьшив вероятность фатальных сбоев, связанных с системными драйверами.

Авторами выделяются три ключевых принципа, которым соответствует Nooks.

Во-первых – совместимость. Архитектура должна быть совместима с существующими системами и расширениями, либо с минимальными изменениями.

Во-вторых – изоляция сбоев. Архитектура должна строиться таким образом, чтобы изолировать важные структуры ядра от ошибок в системных расширениях и драйверах.

И, в-третьих – самовосстановление. Архитектура должна поддерживать автоматическое восстановление после сбоев.




Рис. 1. Организация подсистемы изоляции драйверов.


В предлагаемой подсистеме можно выделить несколько ключевых элементов: менеджер изоляции и восстановления (см. рис. 1).

Каждый драйвер выполняется в своем собственном контексте исполнения с привилегиями уровня ядра, но с ограниченным доступом записи к определенной части адресного пространства. В задачи менеджера изоляции входят две задачи. Первая – это распределение и учет используемых контекстов исполнения драйверов, а вторая – контроль доступа между контекстами исполнения и сервисами ядра.

Если происходит сбой в драйвере устройства или другом подконтрольном расширении ядра, подсистема изоляции в состоянии определить источник сбоя до контекста исполнения (который является текущим) и драйвера. Также предусматриваются локализации и мониторинг системных обращений и потребляемых ресурсов.

При возникновении сбоя и локализации источника происходит остановка текущего контекста выполнения драйвера и начинает работу система самовосстановления, которая состоит из двух частей: менеджера восстановления и агента восстановления.

Менеджер восстановления выполняет низкоуровневые операции, такие как отключение прерываний для устройства, вызвавшего сбой. Далее вызывается специальный агент восстановления, который реализуется как обычный процесс в пространстве пользователя.

Агент самовосстановления, согласно определенной политике относительно сбоев в конкретных драйверах, может выполнять специфичные действия по восстановлению и оповещению. Восстановление работоспособности драйвера осуществляется путем полной выгрузки динамического модуля драйвера из ядра и загрузки его заново.

Для тестирования подсистемы изоляции Nooks в ядре ОС Linux и эмуляции ошибок авторы намеренно внесли ошибки в некоторые драйверы устройств общим количеством 365 изменений. Эффективность подсистемы изоляции составила при этом 99%, 360 из ошибок были изолированы. Однако на системе без подсистемы изоляции драйверов все 365 случаев привели к сбою в системе.

Накладные расходы, связанные с работой подсистемой изоляции и механизмом восстановления, варьировались от 10% до 60%.


Системы, построенные на языках, использующих безопасные типы данных

Следующим развивающимся современным подходом к построению надежных систем является идея использования языков, использующих безопасные типы.

Использование типо-безопасных языков программирования позволяет избежать некоторых традиционных проблем, связанных с таким языками как Си и Си++.

Такие языки, как правило, имеют более формальный, несколько ограничивающий диалект, который в частности может запрещать использование указателей на память, оставляя возможность работы с явно выделенными и определенными объектами, доступ к которым осуществляется по ссылкам.

Одним из важных отличий от традиционных языков является то, что таким языкам необходима специальная среда выполнения.

Среда выполнения обычно сочетает в себе компилятор, виртуальную машину, менеджер управления памятью и динамической оптимизации, верификатор, различные унифицированные классы языка в привязке к сервисам конкретной операционной системы.

Использование компилятора, как части общей среды выполнения, нашло широкое применение в современных интерпретируемых языках программирования.

Одной из возможностей использования встроенного компилятора может заключаться в возможности генерации кода непосредственно вовремя работы среды выполнения.

Такой подход представляет интерес для возможности динамического создания и изменения структуры кода, или использования мета-рефлексивных процедур[2]. Также одной из важных особенностей интерпретируемых языков является снижение сложности программирования в целом и уменьшение временных затрат на разработку и программирование кода.

В задачи виртуальной машины входит динамическое выполнение и интерпретирование специальных машинных команд (байткода). Одной из важных задач виртуальной машины является динамическая проверка границ доступа к массивам данных и объектам.

Особенно важно выделить тот факт, что использование виртуальных машин позволяет легко идентифицировать источники ошибок и создавать подконтрольную среду выполнения, толерантную к сбоям.

Поэтому использование виртуальных машин и специальных языков сред должно рассматриваться как одно из ключевых направлений в изучении и проектировании отказоустойчивых архитектур.

Для решения проблемы «утечки памяти» и упрощения процедур управления в современных языках программирования используются так называемые «сборщики мусора», которые освобождают программиста от необходимости освобождать объекты и выполняют управление памятью в автоматическом режиме.

Очевидно, что такой подход в целом накладывает значительные накладные расходы, по сравнению с выполнением программ на родном машинном языке[3, 4].

Для улучшения этой ситуации была создана динамическая JIT (Just-In-Time) компиляция, суть котором заключается в том, что байткод виртуальной машины компилируется в машинные команды родной машины, после чего исполняется как обычная программа[5].

Задача верификатора заключается в предварительном, статическом анализе байткода виртуальной машины на предмет ошибок и несоответствий по различным эвристическим правилам[6].

Также работа верификатора может повысить производительность и упростить работу виртуальной машины, поскольку множество проверок выполняется до исполнения кода и уже не требуется на момент непосредственного выполнения.

Из систем, построенных на использовании типо-безопасного языка программирования, можно выделить, прежде всего, построенные на языке Java (JavaOS, JNode, JX), и Oberon системах (Bluebottle, Oberon, XO/2), использующих язык Оберон, а также на специальном диалекте языка C#, используемом в новой операционной системе Singularity[7] от компании Microsoft.

Привлекательной чертой подхода, использующего специальные языки программирования в целом, заключается в том, что появляется очень большая свобода реализации и значительно повышается степень контроля исполнения в сравнении с аппаратной защитой.

Например, контроль вызовов объектов может осуществляться динамически, таким образом создавая привлекательную и изящную платформу для адекватной реализаций политик информационной безопасности в системе.

Семантику языка можно расширить, позволяя реализовать множество системных функций простым образом, например, передачу сообщений, создание потоков ядра, синхронизацию, специализированное управление ресурсами, безопасность и другие.

Также можно повысить стабильность системы, введя средства повышения отказоустойчивости, в частности, транзакционное кэширование критических структур данных, которое хорошо сочетается с персистентным состоянием объектов и с общей идеологией подхода с применением специализированного языка программирования.


Подходы к повышению отказоустойчивости операционных систем

Можно выделить несколько факторов способствующих повышению стабильности и надежности операционных систем, которые можно было бы принять во внимание при исследовании и изначальном проектировании архитектуры и системы в целом.

Во-первых, нужно выделить необходимость использования специального системного типо-безопасного языка программирования, который бы помимо общих, стандартных конструкций поддерживал бы более высокоуровневые механизмы, например межпроцессные коммуникации, механизмы, повышающие информационную безопасность в целом, контроль выполнения, конструкции позволяющие изолировать системные сбои.

Другими словами, свести многие стандартные механизмы и примитивы работы, реализуемые в операционных систем сразу на уровень специализированного системного языка.

Во-вторых, необходимо кардинально пересмотреть принципы построения системной архитектуры. Проблемы микроядерной архитектуры могут быть частично решены пересмотрением общих, классических принципов организации этой архитектуры.

В частности, повышение производительности межпроцессных коммуникаций и, следовательно, всей системы в целом может быть осуществлено с помощью отказа от использования множества различных контекстов защиты и помещением всего функционала системы в единое контролируемое пространство.

Для организации защиты между элементами в системе, вместо аппаратной защиты нужно использовать защиту программную, которая должна реализоваться на уровне системного языка программирования.

Очевидно, также, что это пространство должно разделяться логически, например с помощью использования различных пространств имен в системном языке программирования.

В-третьих, необходимо спроектировать специальные методы повышающие отказоустойчивость и предоставляющие возможность программным путем производить восстановление после сбоев. Среди подобных методов в общем можно выделить:

Использование дублирующих объектов, при возникновении сбоев в объекте обработки, этот объект мог бы, прозрачным для системы образом, быть заменен на безопасную версию.

Использование контрольных точек самовосстановления, возможность использования специальных контрольных точек в которые состояние объекты бы сохранялось. При возникновении сбоев, можно было бы восстановить прошлое состояние объекта.

Использование различных версий алгоритмов, возможность локального объявления однотипных алгоритмов, если при обработке одного алгоритма случается сбой, управление передавалось бы на другой алгоритм.

Использование специальных программных доменов отказоустойчивости, или другими словами, возможность объявления иерархических сущностей, которые бы адекватно могли отреагировать на ситуацию сбоя в системе, и предпринять соответствующие действия по восстановлению нормальной работы.

Очевидно, что все вышеперечисленные средства должны быть также реализованы на уровне системного языка операционной системы. Чтобы добиться описанной функциональности и программного контроля над выполнением такого уровня, для этого должна быть создана специальная среда выполнения языка, выполняющая программный код в специальных контекстах виртуальной машины.

Другими словами классическая операционная система должна рассматриваться как среда выполнения для языка программирования этой системы.

Все вышеизложенные предложения, помимо архитектурных особенностей в целом, способствуют тому, что необходимо исследовать методики программирования и новые языковые сущности для обеспечения отказоустойчивости, локализации и изоляции сбоев.

Еще очень существенной возможностью является контроль доступа в подобной среде, появляются возможность в принципе контролировать любые обращения и операции между объектами. Таким образом, создавая политики безопасности системы очень высокого уровня, поддерживающие мандатные режимы контроля.

Что же касательно производительности подобной системы, современные реализации динамической компиляции JIT, позволяют добиться хорошей практически идентичной производительности в сравнении со статической машинной компиляцией[8].


Заключение

В настоящее время нами был получен положительный опыт разработки операционной системы построенной на так называмой «безъядерной» архитектуре.

В основе этой архитектуры лежит многоуровневая модульность, позволяющая разбить все системные компоненты на независимые программные компоненты, динамически компонуемые при начальной инициализации. На основе полученного опыта, была развита концепция единой отказоустойчивой среды выполнения[9]. Создан специализированный язык программирования и среда выполнения[10]. В настоящее время происходит адаптация предлагаемых подходов к современным требованиям и их аппробация.


Литература:
  1. Swift M., Bershad B., Levy H. Improving the Reliability of Commodity Operating Systems. // University of Washington, 2003.

  2. Cordy J., Shukla M. Practical metaprogramming. // IBM Centre for Advanced Studies, 1992.

  3. Reekie H., Hylands C., Lee E. Tcl and Java Performance. // University of Californiat at Berkley, 1998.

  4. Ertl A, Maierhofer M. Implementation of Stack-Based Languages on Register Machines. // Dissertation, Technische Universtat Wien, Austria, 1996.

  5. Krall A. Efficient JavaVM Just-in-Time Compilation. // PACT, 1998, p. 205.

  6. Muller G., Moura B., Bellard F., Consel C. «JIT vs Offline Compilers: Limits and Benefits of Bytecode Compilation». // IRISA 1996 PI 1063

  7. Leroy X. Java Bytecode Verification: Algorithms and Formalizations. // JOAR, vol. 30, 2003.

  8. Hun G., Larus J., Abadi M. An Overview of the Singularity Project. // Microsoft Research Technical Report MSR-TR-2005-135.

  9. Симоненко Д. «Концепция единой отказоустойчивой среды выполнения» // Сборник научных трудов НТЦ РУП МЭСИ №6

  10. Симоненко Д.Н. «Архитектура и реализация единой отказоустойчивой среды выполнения.» // Сборник научных трудов НТЦ РУП МЭСИ №6

Обсуждение

Оставить комментарий:

войдите или зарегистрируйтесь, чтобы отправить комментарий