введение_1 (1085732), страница 7
Текст из файла (страница 7)
Кроме описанных выше видов во многих компьютерах есть небольшое количество постоянной памяти с произвольным доступом — в отличие от оперативной памяти, она не теряет свое содержимое при выключении энергии машины. ПЗУ (постоянное запоминающее устройство, ROM, Read Only Memory — память только для чтения) программируется в процессе производства и после этого его содержимое нельзя изменить. Такая память достаточно быстра и дешева. На некоторых компьютерах программы начальной загрузки, используемые при запуске компьютера, находятся в ПЗУ. Кроме того, некоторые карты ввода-вывода содержат ПЗУ для управления низкоуровневыми устройствами.
Электрически стираемое ПЗУ (EEPROM, Electrically Erasable ROM) и флэш-ОЗУ (flash RAM) также энергонезависимы, но в отличие от ПЗУ их содержимое можно стереть и переписать. Однако запись данных на них требует намного больше времени, чем запись в оперативную память. Поэтому они используются точно так же, как и ПЗУ. Дополнительное преимущество электрически стираемого ПЗУ и флэшОЗУ состоит в том, что с их помощью теперь можно исправить ошибки, содержащиеся в программах.
Существует еще один вид памяти, называемый CMOS и являющийся энергозависимым. Во многих компьютерах CMOS-память используется для хранения текущих даты и времени. CMOS-память и часовая микросхема, отвечающая за отсчет времени, получают питание от маленького аккумулятора, поэтому компьютер всегда показывает правильное время, даже если он был выключен. CMOS также может содержать конфигурационные параметры, например указание, с какого жесткого диска производить загрузку. CMOS-память используется для этих целей, так как она потребляет настолько мало энергии, что установленный на фабрике
аккумулятор часто работает в течение нескольких лет. Однако если аккумулятор начинает выходить из строя, можно подумать, что компьютер стал страдать болезнью Альцгеймера и забывает то, о чем он помнил годами (например, с какого жесткого диска загружать систему).
Теперь более внимательно рассмотрим основную часть оперативной памяти. Зачастую крайне желательно держать сложные программы в памяти целиком. Если, например, одна программа находится в заблокированном состоянии, ожидая окончания операции чтения данных с диска, то другая программа может в это время использовать центральный процессор, что улучшает показатели эксплуатации процессора. Но при одновременном нахождении в памяти нескольких программ возникает необходимость решения двух следующих проблем:
-
Как защитить программы друг от друга, а ядро системы от всех них?
-
Как управлять перемещением программ в памяти?
Возможны различные решения этих вопросов. Но все они предполагают снабжение процессора специальным оборудованием.
Первая проблема достаточно очевидна, но второй вопрос требует пояснения. В процессе компилирования и компоновки программы компилятор и компоновщик не знают, в какую область физической памяти будет загружена программа после завершения процесса. По этой причине они обычно предполагают, что программа начнется с адреса 0, и помещают туда первую инструкцию. Предположим, что первая инструкция считывает из памяти слово, имеющее адрес 10 000, а вся программа и данные к ней были загружены, начиная с адреса 50 000. Тогда при выполнении первой команды появится сообщение об ошибке, поскольку она будет ссылаться на слово по адресу 10 000 вместо 60 000. Для решения этой проблемы нам нужно или «релоцировать» программу во время загрузки, то есть настроить ее, находя все адреса и изменяя их в соответствии с реальной адресацией (это выполнимо, но дорого), или оперативно изменять адресацию во время работы программы.
Простейшее решение показано на рис. 1.9, а. На рисунке видно, что компьютер оборудован двумя специальными регистрами: базовым и предельным. (Заметим, что в этой книге числа, начинающиеся с Ох, являются шестнадцатеричными в соответствии с правилами орфографии языка С, а числа, начинающиеся с нуля — восьмеричными.) Когда программа начинает работать, в базовый регистр загружается адрес начала исполняемого модуля программы, а предельный регистр говорит о том, сколько занимает исполняемый модуль программы вместе с данными. При выборке команды из памяти аппаратура проверяет счетчик команд, и если он меньше, чем предельный регистр, то добавляет к нему значение базового регистра, а сумму передает памяти. Когда программа хочет прочитать слово данных (например, из адреса 10 000), аппаратура автоматически добавляет к этому адресу содержимое базового регистра (например, 50 000) и передает сумму (60 000) памяти. Базовый регистр дает возможность программе ссылаться на любую часть памяти, следующую за хранящимся в нем адресом. Кроме того, предельный регистр запрещает программе обращение к любой части памяти после программы. Таким образом, с помощью этой схемы решаются обе задачи: защиты и перемещения программ. Стоимость решения равна двум новым регистрам и незначительному увеличению времени, затрачиваемого на операцию (уходящего на проверку предела и суммирование).
Программа пользователя и данные
Адреса
а
Рис. 1.9. Используется одна пара база—предел. Программа имеет доступ к части памяти,
находящейся между базой и пределом ; используются две пары база—предел. Код программы
находится между базой 1 и пределом 1, а данные к ней — между базой 2 и пределом 2 (б)
Программа пользователя
Операционная система
б
В результате проверки и преобразования данных адрес, сформированный программой и называемый виртуальным, переводится в адрес, используемый памятью и называемый физическим. Устройство, которое выполняет проверку и преобразование, называется устройством управления памятью или диспетчером памяти (MMU, Memory Management Unit). Диспетчер памяти располагается или в схеме процессора, или близко к ней, но логически находится между процессором и памятью.
Более сложный диспетчер памяти показан на рис. 1.9. Здесь диспетчер памяти состоит из двух пар базового и предельных регистров: одна пара для текста программы, другая — для данных. Командный регистр и все другие ссылки на текст программы работают с парой 1, а ссылки на данные используют пару 2. Появляется возможность делить одну и ту же программу между несколькими пользователями и при этом хранить в памяти только одну копию программы, что было невозможно в первой схеме. Когда работает программа 1, четыре регистра расположены так, как показано стрелками на рис. 1.9а слева. При работе программы 2 они располагаются так, как показано стрелками на рисунке справа. На самом деле существуют намного более сложные диспетчеры памяти, мы изучим их позже в этой книге. А сейчас нужно запомнить, что управление диспетчером памяти должно быть функцией операционной системы, так как нет уверенности, что пользователь сделает это корректно.
На характеристики памяти в основном влияют два аспекта. Во-первых, кэш скрывает относительно низкую скорость памяти. После того как программа про-
работала некоторое время, кэш заполняется строками программы, увеличивая скорость. Однако когда операционная система переключается от одной программы к другой, кэш остается заполненным данными первой программы, а необходимые строки новой программы должны загружаться уже из физической памяти. Такая операция может стать главной причиной снижения производительности, если она происходит слишком часто.
Во-вторых, при переключении от одной программы к другой регистры управления памятью должны меняться. На рис. 1.9б требуется перезагрузка только четырех регистров, что не является серьезной проблемой, но в реальных диспетчерах памяти должно перезагружаться, явно или динамически, намного большее количество регистров. В любом случае подобная операция занимает некоторое время. Мораль этой истории такова: переключение от одной программы к другой, называемое переключением контекста, очень дорого.
Устройства ввода-вывода
Память не является единственным ресурсом, которым должна управлять операционная система. Устройства ввода-вывода также тесно взаимодействуют с операционной системой. Устройства ввода-вывода обычно состоят из двух частей: контроллера и самого устройства. Контроллер — это микросхема или набор микросхем на вставляемой в разъем плате, физически управляющая устройством. Он принимает команды операционной системы, например указание прочитать данные с устройства, и выполняет их.
Во многих случаях фактическое управление устройством очень сложно и тре-
бует высокого уровня детализации, поэтому в работу контроллера входит пред-
ставление простого интерфейса для операционной системы. Контроллер диска
может принять команду прочесть сектор 11 206 с диска 2. При этом контроллер
должен преобразовать линейный номер сектора в номер цилиндра, сектора и го-
ловки. Операция преобразования усложняется тем фактом, что внешние цилинд-
ры могут иметь больше секторов, чем внутренние, и что номера испорченных сек-
торов отображаются на другие секторы. Затем контроллер должен определить, над
каким цилиндром находится в данный момент головка, и дать ей последовательность импульсов, чтобы переместить ее на необходимое количество цилиндров.
Дальше нужно ждать, пока повернется диск, поместив требуемый сектор под го-
ловку. Затем начинается чтение и сохранение битов по мере поступления их с дис-
ка, удаление заголовка и вычисление контрольной суммы. Наконец, контроллер
должен собрать полученные биты в слова и сохранить их в памяти. Для осуществ-
ления всей этой работы контроллеры часто содержат маленькие встроенные ком-
пьютеры, запрограммированные на выполнение подобных задач.
Следующей частью является само устройство. Устройства имеют достаточно простые интерфейсы, во-первых, потому что их возможности весьма невелики и, во-вторых, потому что нужно привести их к единому стандарту. Единый стандарт необходим, чтобы любой IDE-контроллер диска мог управлять любым IDE-диском. Аббревиатура IDE образована от Integrated Drive Electronics (встроенный интерфейс накопителей). IDE-интерфейс является стандартным для дисков на компьютерах с процессором Pentium, а также некоторых других компьютерах.
Поскольку настоящий интерфейс устройства скрыт с помощью контроллера, операционная система видит только интерфейс контроллера, который может сильно отличаться от интерфейса самого устройства.
Так как все типы контроллеров отличаются друг от друга, для управления ими требуется различное программное обеспечение. Программа, которая общается с контроллером, отдает ему команды и получает ответы, называется драйвером устройства. Каждый производитель контроллеров должен поставлять драйверы для поддерживаемых им операционных систем. Вы можете купить сканер с драйверами для Windows 98, Windows 2000 и UNIX. Если вы хотите получить возможность использовать драйвер, его нужно установить в операционную систему так, чтобы он мог работать в режиме ядра. Теоретически драйверы могут работать вне ядра, но такую возможность поддерживают всего несколько существующих систем, так как для этого требуется, чтобы драйвер в пространстве пользователя имел доступ к устройству неким контролируемым способом — очень редко поддерживаемое свойство. Есть три способа установки драйвера в ядро. Первый заключается в том, чтобы заново скомпоновать ядро вместе с новым драйвером и затем перезагрузить систему. Так работает множество систем UNIX. Второй: создать запись во входящем в операционную систему файле, говорящую о том, что требуется драйвер, и затем перезагрузить систему. Во время начальной загрузки операционная система сама находит нужные драйверы и загружает их. Так работает система Windows. . При третьем способе операционная система может принимать новые драйверы, не прерывая работы, и оперативно устанавливать их, не нуждаясь при этом в перезагрузке. Этот способ редко используется, но сейчас он становится все более и более распространенным. Такие съемные устройства, как шины USB и IEEE 1394 (мы поговорим о них ниже), всегда нуждаются в динамически загружаемых драйверах. Для связи с каждым контроллером существует небольшое количество регистров. Например, минимальный контроллер диска может иметь регистры для определения адреса на диске, адреса в памяти, номер сектора и направления операции (чтение или запись). Чтобы активизировать контроллер, драйвер получает команду от операционной системы, затем транслирует ее в величины, подходящие для записи в регистры устройства.
На некоторых компьютерах регистры устройств отображаются в адресное пространство операционной системы, поэтому их можно читать или записывать как обычные слова в памяти. На таких машинах не нужны специальные команды ввода-вывода, а программы пользователей можно оградить от аппаратуры, помещая эти адреса в памяти за пределами досягаемости программ (например, с помощью
базового и предельного регистров). На других компьютерах регистры устройств располагаются в специальных портах ввода-вывода, и каждый регистр имеет свой адрес порта. На этих машинах в режиме работы ядра доступны команды IN и OUT, они позволяют драйверам считывать и записывать регистры. Первая схема устраняет необходимость специальных команд ввода-вывода, но использует некоторое
количество адресного пространства. Вторая схема не затрагивает адресное пространство, но требует наличие специальных команд. Обе схемы широко используются.
Ввод и вывод данных можно осуществлять тремя различными способами. Простейший метод состоит в том, что пользовательская программа выдает системный запрос, который ядро транслирует в вызов процедуры соответствующего драйвеpa. Затем драйвер начинает процесс ввода-вывода. В это время драйвер выполняет очень короткий программный цикл, постоянно опрашивая готовность устройства, с которым он работает (обычно есть некий бит, который указывает на то, что устройство все еще занято). По завершении операции ввода-вывода драйвер помещает данные туда, куда требуется, и возвращается в исходное состояние. Затем операционная система возвращает управление программе, осуществлявшей вызов. Этот метод называется ожиданием готовности или активным ожиданием и имеет один недостаток: процессор должен опрашивать устройство до тех пор, пока оно не завершит свою работу.
При втором способе драйвер запускает устройство и просит его выдать прерывание по окончании ввода-вывода. После этого драйвер возвращает данные, операционная система блокирует программу вызова, если это нужно, и начинает выполнять другие задания. Когда контроллер обнаруживает окончание передачи данных, он генерирует прерывание, чтобы сигнализировать о завершении операции.
Текущая команда