\section{Защита оперативной памяти}

Легко понять, что разделение доступа к данным невозможно без контроля
над доступом к другим объектам системы, в первую очередь к оперативной
памяти, физическим внешним устройствам и драйверам этих устройств.
Без такого контроля все средства управления доступом к данным во внешней
памяти оказываются бессмысленными, как запертая дверь в сарае без одной
стены. Поэтому нельзя всерьез говорить о безопасности в системах, не
разделяющих доступ задач к физической памяти, таких как \sysname{DOS},
\sysname{MS Windows 3.x}, \sysname{Windows 95}, \sysname{MacOS} и др.

При этом пользовательские задачи должны иметь возможность обмениваться
данными с модулями ОС и друг с другом\footnote{На самом деле задачам
достаточно иметь возможность обмениваться данными с ядром. Межзадачное
взаимодействие в этом случае может быть организовано при посредстве ядра.
Примерами такого опосредованного взаимодействия являются трубы в системах
семейства \sysname{Unix} и аналогичные трубам средства в других ОС.

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

\subsection{Кольца защиты}

В современных системах чаще всего используется аппаратно реализованное
разделение системного и пользовательского уровней привилегий.
Процессор может работать в одном из двух режимов: \term{пользовательском}
и \term{системном}. Иногда системный режим называют режимом
\termint{супервизора}{supervisor}{надзиратель}.
Обычно такую архитектуру называют \term{кольцами защиты}:
структуру привилегий изображают в виде двух концентрических кругов,
где пользовательский круг является внешним и менее привилегированным,
а системный -- внутренним. В ряде современных систем количество колец
может быть увеличено, например процессоры семейства \machname{x86}
имеют четыре уровня привилегий.

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

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

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

{\small
Например, в \machname{PDP-11} системные вызовы реализуются через команду
\verb"EMT" -- Emulate Trap (программное прерывание). Эта команда имеет
6-битовое поле операнда, которое, однако, не интерпретируется процессором.
Пользовательская программа проталкивает параметры системного вызова в стек
и исполняет \verb"EMT" с операндом, равным коду исполняемого системного
вызова. При этом вызывается обработчик прерывания по вектору 8.

Прерывание 8 обрабатывается диспетчером системных вызовов.
Обработчики прерываний в \machname{PDP-11} всегда исполняются в системном
режиме, поэтому при вызове диспетчера автоматически происходит переключение
в режим ядра. Диспетчер извлекает из пользовательского стека
адрес команды \verb"EMT" и параметры. Затем он извлекает код команды
\verb"EMT", анализирует ее операнд и в зависимости от результатов анализа
вызывает соответствующую процедуру ядра. Для копирования параметров
используются специальные команды доступа к пользовательскому адресному
пространству: \verb"MFPI/MFPD" (Move From Previous Instruction/Data --
копировать из предыдущего [адресного пространства] команд/данных).

В \sysname{RSX-11}, использующей разделение памяти, fork-процесс драйвера
все время имеет доступ к пользовательскому адресному пространству
через команды \verb"MFPI/MFPD", поэтому не возникает необходимости
копировать данные в системные буфера, как в \sysname{Linux}.
}

Несколько иная схема обмена данными используется
в \sysname{Windows NT} и некоторых микроядерных системах:
вместо прямого отображения пользовательских адресов в системные
или специальных команд копирования между адресными пространствами
используются небольшие (4 кбайта в случае \sysname{NT}) разделяемые
буфера. Такое решение позволяет отчасти защитить пользовательские
программы от ошибок в модулях ОС и упрощает синхронизацию доступа к
разделяемым данным, но приводит к значительным накладным расходам.

{\small
В \sysname{Windows NT} для обмена данными между пользовательскими
программами и ядром используются буфера, однако многие модули ядра
обмениваются данными путем прямого разделения частей адресного
пространства для повышения производительности.
Несмотря на это, \sysname{NT} близка к статусу чемпиона среди
современных ОС по потребностям в памяти и накладным расходам,
хотя и трудно определить, с чем это связано: с буферизацией или
с чем-либо еще, например, с плохо сбалансированным дисковым кэшем.
}

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

В результате мы получаем двухуровневую систему прав, напоминающую
систему привилегий в ОС семейства \sysname{Unix}:
пользовательские программы имеют доступ только к своим внутренним объектам
и тем внешним объектам, доступ к которым им разрешен ядром,
ядро же обладает всеми мыслимыми правами и привилегиями.

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

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

Желание защитить ядро и пользовательские программы от ошибок в системных
модулях вынуждает разработчиков современных процессоров усложнять структуру
аппаратных привилегий. Некоторые процессоры, например семейство
\machname{x86}, предлагают несколько уровней привилегий в отличие от
двух в модели пользователь/супервизор, но и они сохраняют ``концентрическую''
структуру привилегий, когда более привилегированный уровень автоматически
получает доступ ко всем объектам предыдущего уровня. Таким образом,
код, исполняющийся в нулевом кольце защиты, мало чем отличается по
возможностям от кода супервизора в системах c двумя уровнями привилегий.

Альтернативный подход состоит в том, чтобы, не накладывая дополнительных
требований на аппаратуру, как можно сильнее упростить само ядро и
дополнительные модули, исполняющиеся в режиме ядра.
Этот подход, реализованный, например, в \sysname{Unix},
не решает проблемы в принципе, но по крайней мере позволяет уменьшить
вероятность опасных ошибок в ядре и системных модулях.

Описанная архитектура с различными вариациями используется в большинстве
систем семейства \sysname{Unix}, \sysname{OS/2}, \sysname{Windows NT},
\sysname{VAX/VMS-OpenVMS} и ОС для больших компьютеров фирмы \entname{IBM}:
\sysname{MVS}, \sysname{VM/ESA}, \sysname{OS/390}. В совокупности эти
системы образуют подавляющее большинство современных ОС общего назначения.

{\small
Отчасти похожую архитектуру памяти использует и \sysname{Windows 95},
но эта ОС не защищает значительную часть системных адресов от
пользовательских задач. Никак не защищен оказывается первый мегабайт
физической памяти, который зачем-то отображается в адресные пространства
всех 32-разрядных программ, значительная часть ядра и даже
глобальная таблица дескрипторов.

Поражают воображение
заявления фирмы \entname{Microsoft} по этому поводу. В одном из
пресс-релизов, опубликованных еще до выпуска системы, официальные
представители фирмы на полном серьезе утверждали, что на самом деле
защищать надо лишь первые 64 килобайта адресного пространства,
поскольку, по их измерениям (?!), 90 \% ошибочных доступов приходятся
именно на эти адреса. Комментарии излишни.
}

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

Важным недостатком раздельных адресных пространств является
относительная сложность разделения памяти между задачами, что
особенно заметно при разделении кода.
Прослеживание множественных ссылок на разделяемую
библиотеку подпрограмм приводит к снижению производительности
алгоритмов подкачки и поиска жертвы, а также к более сложному управлению
файлом подкачки. Из-за этого у систем с динамической сборкой
(\sysname{OS/2} и \sysname{Windows NT}) виртуальная память заметно
менее эффективна, чем у систем семейства \sysname{Unix}, использующих
статические загрузочные модули. Статические же загрузочные модули
обеспечивают гораздо меньшую гибкость, чем динамическая сборка.
Такой выбор между ``бедным, но здоровым'' и ``богатым, но больным''
трудно назвать привлекательным.

\subsection{Альтернативные подходы к защите памяти}\label{iAPX432}

Большинство альтернативных решений, в том числе и \machname{i432},
так или иначе восходят к системам \machname{Burroughs}, первые из
которых были разработаны в конце 60-х гг. В этих системах
использовалась сегментная виртуальная память, но все задачи
разделяли общую таблицу дескрипторов сегментов. Таким образом,
можно сказать, что все задачи имели общее адресное пространство.
Разделение же доступа к сегментам осуществлялось путем избирательной
выдачи селекторов.

В \machname{Burroughs} пользовательская программа не могла самостоятельно
формировать указатели, так как эти машины имели теговую архитектуру, когда
каждая ячейка данных снабжена описателем-\termin{тегом}{tag}, указывающим
на тип данных, хранящихся в данной ячейке.

\note{Благодаря тегам команды
модификации скалярных данных не могут оперировать с указателями.
Команды же создания и модификации указателей являются привилегированными
и доступны только специализированному модулю ядра.}

Простая передача указателя между задачами в \machname{Burroughs} приводила к
тому, что задача-получатель получала доступ к указуемому объекту,
что в системах с раздельными таблицами дескрипторов соответствовало
бы отображению объекта в два адресных пространства, но без связанных
с отображением сложностей и накладных расходов. Это делает
архитектуру типа \machname{Burroughs} очень привлекательной
альтернативой системам с разделением адресных пространств.

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

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

Успехи микропрограммирования привели во второй половине 70-х -- начале
80-х гг. к буйному расцвету CISC-архитектур. В микрокод переносили
интерпретаторы \progname{Lisp}, \progname{SmallTalk} и целые модули
операционных систем. На фоне этого расцвета возникла новая волна
интереса к архитектурам с контролем за передачей указателя. Например,
идею \machname{i432}, кратко обсуждаемого в п. \ref{i432} тоже можно
проследить вплоть до желания улучшить и усовершенствовать архитектуру
\machname{Burroughs}.

Среди возникшей волны CISC-архитектур такого типа нельзя не упомянуть
\machname{System/38} фирмы \entname{IBM}, кратко описанную в
\cite{Deitel2} и ряде других работ.

{\small
Значительная часть ОС \machname{System/38} реализована микропрограммно.
Точнее, в этой системе трудно сказать, где кончается ОС и начинается
система команд. Система использует одноуровневое адресное пространство
для оперативной и внешней памяти.
Все адресное пространство  разбито на объекты различных типов. Каждый
объект имеет набор атрибутов, включая имя объекта и идентификатор его
владельца. Структура большинства объектов скрыта от прикладных программ.
Доступ к объектам осуществляется специальными командами. Операции делятся
на типовые, приложимые к любым объектам, и уникальные, приложимые только
к объектам определенного типа.

Доступ к объектам может
осуществляться как по адресам, так и по символьным именам. Роль каталогов
при преобразовании имен в адреса выполняют специальные объекты, называемые
\termin{контекстами}{context}.
Адрес, называемый \term{системным указателем}, состоит из расположения
объекта (т.е. собственно указателя) и прав доступа к объекту. Изменение
прав доступа осуществляется специальными командами. Пользователь не может
изменить эти права прямыми битовыми манипуляциями. Таким образом,
\machname{System/38} вместо разделения адресных пространств использует
контроль передачи указателя, как и \machname{Burroughs}. Указатель с
ограниченными правами приблизительно соответствует мандату в \machname{i432}.

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

Часть типов объектов может быть сопоставлена с сегментами кода и данных
в традиционных архитектурах -- это объекты, содержащие скалярные
данные и код. Другие объекты, такие как описатели логических устройств
и сетевых портов или пользовательские профили, лишь с большой
натяжкой сопоставляются с обычными сегментами. Многие типы объектов
ориентированы на работу с реляционными базами данных. Это такие типы, как:
\begin{description}
\item[Data Space] -- область данных разбитая на записи фиксированного
формата, соответствующая таблице в обычных реляционных СУБД;
\item[Data Space Index] -- индекс поиска для таблицы;
\item[Cursor] -- средство для адресации в таблице.
\end{description}
}

Концепция \machname{System/38} получила несколько неожиданное развитие
в машинах серии \machname{AS/400} (Application Server -- сервер приложений).
\machname{System/38} использовала микропрограммное обеспечение
большого объема. Больш\'ая часть микрокода хранилась в основной
памяти, что в значительной мере размывало границу между микрокодом
и обычными программами. При разработке \machname{AS/400} разработчики
фирмы \entname{IBM} сделали смелый шаг, переместив эту границу из
процессора в загрузчик программ.

В традиционных CISC-системах центральный процессор извлекает из основной
памяти команды машинного языка и преобразует их в последовательности
микрокоманд. В традиционных RISC-системах микрокоманд как таковых нет,
и аппаратура процессора непосредственно интерпретирует  машинный язык.
В \machname{AS/400}, как и в CISC-системах, существует различие
между ``внешним'' машинным языком, команды которого хранятся в бинарных
загрузочных модулях, и командами аппаратуры ЦПУ. Однако преобразование
внешнего машинного кода в язык команд процессора происходит в момент
загрузки программы. Загрузчик считывает из загрузочного модуля
последовательность высокоуровневых объектно-ориентированных команд
и преобразует их в последовательность команд реального ЦПУ.
В современных моделях \machname{AS/400} используется RISC-процессор
с системой команд \machname{Power}, в основном аналогичный процессорам,
используемым в \machname{RS/6000}, \machname{PowerPC} и \machname{PowerMAC}.
Пользователь не может сформировать и запустить произвольную
последовательность команд этого ЦПУ и вообще не имеет доступа к
реальному процессору.

Можно сказать, что \machname{AS/400} представляет собой удачный компромисс
между упоминавшейся выше идеей о статическом контроле за передачей
указателей на этапе компиляции/ассемблирования и требованиями практики
эксплуатации вычислительных систем. Языковые процессоры в \machname{AS/400}
генерируют высокоуровневые команды, реальный же процессор исполняет код,
созданный системным загрузчиком на основе этих команд. Большая часть
проверок ``безопасности'' исполняемых операций выполняется загрузчиком
статически и не оказывает влияния на производительность системы.

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

\subsection{Взаимно недоверяющие подсистемы}\label{i432}

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

Согласно этой концепции,
пользовательская задача не должна предоставлять системе доступа ко всем
своим данным. Вместо этого задача должна выделять \term{мандат}
на доступ к буферу или нескольким буферам, предназначенным для обмена
данными. Все акты обмена данными, как между пользовательской
задачей и системой, так и между двумя пользовательскими задачами или
двумя модулями системы, также осуществляются при помощи передачи мандатов.

Например, при исполнении системного вызова
\verb"int read(int file, void * buf, size_t size)" программа должна
передать системе мандат на право записи в буфер \verb"buf" размером
\verb"size" байт. При этом буфер будет отображен в адресное пространство
подсистемы ввода/вывода, но эта подсистема не получит права записи
в остальное адресное пространство нашей задачи.
Впрочем, этот подход имеет две очевидные проблемы:
\begin{itemize}
\item При использовании страничных и двухслойных сегментно-страничных
диспетчерах памяти мы можем отображать в чужие адресные пространства только
объекты, выровненные на границу страницы и имеющие размер, кратный
размеру страницы.
\item Мы все равно не можем избавиться от супервизора. В данном случае
это должен быть модуль, ответственный за формирование мандата и
размещение отображенного модуля в адресном пространстве задачи,
получающей мандат.
\end{itemize}

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

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

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

\begin{itemize}
\item Убедиться в том, что передаваемый объект целиком входит
в один сегмент исходного адресного пространства.

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

\item Сформировать содержимое дескриптора сегмента для объекта и записать
в него соответствующие права. Эта операция требует формирования 4 - 5
битовых полей, и запись ее алгоритма на псевдокоде займет около десятка
операторов. По сложности алгоритма одна только эта операция сравнима с
наиболее сложными командами современных коммерческих CISC-процессоров,
таких как \machname{VAX} или транспьютер.

\item Отметить общесистемной базе данных, что на соответствующую
область физической памяти существует две ссылки. Это нужно для того,
чтобы процессы дефрагментации и управления виртуальной памятью правильно
обрабатывали перемещения сегмента по памяти и его перенос на диск,
отмечая изменения физического адреса или признака присутствия во
{\it всех} дескрипторах, ссылающихся на данный сегмент.
Ниже эта проблема будет обсуждаться подробнее.

\item Найти свободную запись в таблице дескрипторов сегментов
задачи-получателя. Эта операция аналогична строковым командам
современных CISC-процессоров, которые сами по себе считаются сложными
командами.

\item Разумным образом обработать ситуацию отсутствия такой записи.

\item Записать сформированный дескриптор в таблицу.
\end{itemize}

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

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

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

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

Структура этой базы данных представляется нам совершенно непонятной,
потому что она должна отвечать следующим требованиям:
\begin{itemize}
\item обеспечивать быстрый поиск всех селекторов сегментов, ссылающихся
на заданный байт или слово памяти;
\item обеспечивать быстрое и простое добавление новых селекторов,
ссылающихся на произвольные части существующих сегментов;
\item занимать не больше места, чем память, отведенная под сегменты.
\end{itemize}

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

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

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

В качестве дополнительного стимула для читателя, решившего взяться за эту
задачу, скажем еще, что разработчики фирмы \entname{Intel} не смогли
найти удовлетворительного решения.

{\small Авторам известен только один процессор, пригодный для полноценной
реализации взаимно недоверяющих подсистем: \machname{iAPX432} фирмы
\entname{Intel}. Вместо создания системной базы данных о множественных
ссылках на объекты, специалисты фирмы \entname{Intel} усложнили диспетчер
памяти и соответственно алгоритм разрешения виртуальных адресов.

Виртуальный адрес в \machname{i432} состоит из селектора объекта и
смещения в этом объекте. Селектор объекта ссылается на таблицу доступа
текущего \term{домена}\footnote{Домен представляет собой программный модуль
вместе со всеми его статическими и динамическими данными. По идее
разработчиков, домен соответствует замкнутому модулю языков высокого уровня,
например \termin{пакету}{package} языка \progname{Ada}.}.
Элемент таблицы состоит из прав доступа к объекту и
указателя на таблицу объектов процесса.

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

\small\verb"segment_table[seg].phys_address + offset"

\noparskip мы имеем

\small\verb"object_table[access_table[selector]].phys_address + offset."

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

Более сложным типом объектов являются \term{уточнения} -- объекты,
ссылающиеся на отдельные элементы других объектов. Вместо физического
адреса и длины дескриптор такого объекта содержит {\it селектор целевого
сегмента}, смещение уточнения в целевом объекте и его длину.
При ссылке на уточнение диспетчер памяти сначала проверяет допустимость
смещения, а затем повторяет полную процедуру разрешения и проверки
прав доступа для целевого объекта. Целевой объект также может
оказаться уточнением, и диспетчер памяти будет повторять процедуру
до тех пор, пока не дойдет до объекта, ссылающегося на физическую память.
Более подробно этот механизм описан в работе \cite{Organick}.

Уточнения позволяют решить проблему выдачи мандатов -- мандат реализуется
как уточнение исходного объекта (например, сегмента данных модуля, в котором
выделен буфер), а программе-получателю передается даже не само уточнение,
а лишь дескриптор доступа к нему. Для прекращения действия мандата
достаточно удалить дескриптор уточнения. После этого все переданные
другим модулям дескрипторы доступа будут указывать в пустоту,
т.е. станут недействительными. Задачи же перемещения и сброса объектов
на диск решаются путем изменения физического адреса или признака
присутствия целевого объекта; для этого не требуется прослеживать цепочку
уточнений.

Однако накладные расходы, связанные с реализацией этого сложного
механизма, оказались очень большими. Процессор \machname{i432}
остался экспериментальным и не получил практического применения.
Трудно сказать, какой из факторов оказался важнее: катастрофически низкая
производительность системы\footnote{Авторам не удалось найти официальных
заявлений фирмы \entname{Intel} по этому поводу, но фольклор утверждает,
что экспериментальные образцы \machname{i432} с тактовой частотой 20 MГц
исполняли около 20.000 операций в секунду.} или просто неспособность фирмы
\entname{Intel} довести чрезмерно сложный кристалл до массового
промышленного производства. Так или иначе едва ли не единственной
практической пользой, извлеченной из этого амбициозного проекта,
оказалось тестирование системы автоматизированного проектирования,
использованной впоследствии при разработке процессора \machname{80386} и
сопутствующих микросхем.
}

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

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

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

