Системы с базовой виртуальной адресацией

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

Вы можете заметить, что термин базовая адресация уже занят — мы называ­ли таким образом адресацию по схеме reg[offset]. Метод, о котором сейчас идет речь, состоит в формировании адреса по той же схеме. Отличие состоит в том, что регистр, относительно которого происходит адресация, не доступен прикладной программе. Кроме того, его значение прибавляется ко всем адресам, в том числе к "абсолютным" адресным ссылкам или переменным типа указатель. По существу, такая адресация является способом организации виртуального адресного пространства.

Как правило, машины, использующие базовую адресацию, имеют два регистра. Один из регистров задает базу для адресов, второй устанавливает верхний предел. В системе IСL1900/Одренок эти регистры называются соответственно base и datum. Если адрес выходит за границу, установленную значением datum, возникает исключительная ситуация (exception) ошибочной адресации. Как правило, это приводит к тому, что система принудительно завершает работу программы.

При помощи этих двух регистров мы сразу решаем две важные проблемы.

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

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

Часто ОС, работающие на таких архитектурах, умеют сбрасывать на диск образы тех процессов, которые долго не получают управления. Это самая простая из форм своппинга (swapping— обмен) (русскоязычный термин «страничный обмен» довольно широко распространен, но в данном случае его использование было бы неверным, потому что обмену подвергаются не страницы, а целиком задачи).

решив перечисленные выше проблемы, мы создаем другие, довольно не­ожиданные. Мы оговорили, что базовый регистр недоступен прикладным задачам. Но какой-то задаче он должен быть доступен! Каким же образом процессор узнает, исполняет ли он системную или прикладную задачу, и не сможет ли злонамеренная прикладная программа его убедить в том, что яв­ляется системной?

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

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

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

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

Протокол общения прикладной программы с системой состоит в следую­щем: программа помещает параметры вызова в оговоренное место — обычно в регистры общего назначения или в стек — и исполняет syscall. Одним из параметров передается и код системного вызова. Диспетчер вызовов анали­зирует допустимость параметров и передает управление соответствующей процедуре ядра, которая и выполняет требуемую операцию (или не выпол­няет, если у пользователя не хватает полномочий). Затем процедура поме­щает в оговоренное место (чаще всего опять-таки в регистры или в пользо­вательский стек) возвращаемые значения и передает управление диспетчеру, или вызывает sysret самостоятельно.

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

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

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

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