В какой области адресного пространства расположен bios

Обновлено: 06.07.2024

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

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

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

  • Организация памяти. Это количество различных типов памяти, их организация, способы подключения к процессору, особенности работы памяти.
  • Система команд. Обратите внимание, я не сказал набор команд, я сказал система команд. В наборе команд ЭВМ(точнее, процессора) некоторые команды могут отсутствовать, могут включаться дополнительные команды, но подход к формированию системы команд остается неизменным. Набор команд это подмножество системы команд. Сюда входит адресность команд (количество операндов), режимы адресации операндов, тонкости выполнения (количество циклов, возможно, переменное), типы выполняемых операций, возможность расширения.
  • Подсистема ввода-вывода. Способы подключения и адресации внешних (периферийных). Сюда же относится и наличие (и организация) канала прямого доступа к памяти ЭВМ.
  • Возможность построения многопроцессорных комплексов. Одновременная работа нескольких процессоров в составе ЭВМ далеко не так проста, как может показаться. Причем и с точки зрения программиста.
  • Механические параметры. Да, как ни странным это кажется. Наверное все знают платы расширения устанавливаемые в настольные ПК или в гнезда PCMCIA. Были и определенные требования к размерам ТЭЗ (типовой элемент замены), так назывались платы устанавливаемые в стойки больших ЭВМ (ЕС, СМ). Механические параметры далеко не всегда входят в понятие архитектуры, но иногда такое встречается.

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

"Чистые архитектуры" идеального мира

Наиболее известны две архитектуры подключения памяти к процессору. Первая, знакомая всем по IBM PC совместимым ЭВМ (точнее, микропроцессорам 80x86), архитектура фон Неймана . Вторая, менее известная, но более важная для нас, как станет видно в дальнейшем, Гарвардская архитектура .

Наиболее известные архитектуры ЭВМ с точки зрения организации памяти. Наиболее известные архитектуры ЭВМ с точки зрения организации памяти.

Как видно, основное различие здесь в использовании памяти.

Архитектура фон Неймана

Хранит и программу, и данные, в единой памяти. Это выглядит привлекательно, так как позволяет эффективно использовать память небольшого объема. У нас небольшая программа, которая обрабатывает большие объемы данных? Нет никаких проблем, главное, что бы суммарный размер программы и данных мог поместиться в память. Большая программа требующая мало данных? Тоже все просто.

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

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

Однако, и минусов хватает. Самый большой плюс, лежащий в основе архитектуры, является и самым большим минусом. Представьте, что по какой то причине программа записала часть данных в область команд. Это приведет к неработоспособности программы, если управление будет передано этой измененной области. Или к остановке ЭВМ (когда то давно это называлось АВОСТ - аварийный останов).

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

Гарвардская архитектура

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

Теперь мы не можем передать управление области данных или использовать команды как данные. То есть, мы не можем изменять саму программу. Но это иногда все таки требуется. Теперь мы должны по отдельности загружать в области памяти коды команд и данные перед началом выполнения программы. Для универсальных ЭВМ эта архитектура менее удобна, чем архитектура фон Неймана. Зато для микроконтроллеров она подходит хорошо. Ведь программа в микроконтроллер обычно загружается если не однократно, то надолго и изменять ее не требуется. А отдельная область данных позволяет повысить надежность работы и уменьшить объем ОЗУ (оперативная память зачастую дороже). Теперь стало немного понятнее, почему я сказал, что Гарвардская архитектура для более важна, мы же говорим как раз о микроконтроллерах.

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

Суровый реальный мир и компромиссы

Реальные ЭВМ не используют в чистом виде ни одну из описанных выше архитектур. Хотя существовали и ЭВМ полностью им соответствующие. Архитектура реальных ЭВМ это некий компромисс. Но прежде чем двигаться дальше нам нужно кратко остановиться на понятии адресного пространства.

Адресное пространство

Под адресным пространством мы будем понимать логически единую совокупность адресуемых ячеек памяти . Звучит туманно? Не волнуйтесь, сейчас все станет понятно. Давайте начнем разбираться с более знакомой всем универсальной ЭВМ (в виде ПК, например).

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

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

Пример упрощенного представления об адресном пространстве программы. Иллюстрация моя Пример упрощенного представления об адресном пространстве программы. Иллюстрация моя

Для процессоров 80х86 области памяти обычно называют сегментами. В рамках своего адресного пространства программа может как угодно распоряжаться памятью. Но выход за пределы адресного пространства запрещен (обычно, операционной системой). Видно, что адресное пространство задачи занимает часть, в общем случае, все имеющейся памяти ЭВМ.

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

Пример отображения адресного пространства задачи на физическую память ЭВМ. Серым цветом отмечены не используемые области памяти. Иллюстрация моя Пример отображения адресного пространства задачи на физическую память ЭВМ. Серым цветом отмечены не используемые области памяти. Иллюстрация моя

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

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

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

В рамках приведенного ранее примера с адресным пространством задачи можно показать пример частичного перекрытия

Пример перекрывающихся адресных пространств. Иллюстрация моя Пример перекрывающихся адресных пространств. Иллюстрация моя

Но какое это отношение имеет к микроконтроллерам? Самое прямое! И сейчас это станет видно.

Адресные пространства ЭВМ

Да, именно так. Микроконтроллер включает в себя управляющую ЭВМ, как мы уже видели ранее. Какие области памяти могут быть в ЭВМ? Вспомним наши "чистые архитектуры". Область команд программы и область данных . Но это не все, есть еще область стека , которая может оказаться не такой простой, и область ввода-вывода .

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

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

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

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

Если еще раз вспомнить "чистые архитектуры", то станет видно, что в архитектуре фон Неймана адресные пространства программ и данных полностью совпадают. А в Гарвардской архитектуре они полностью изолированы.

А вот со стеком все немного интереснее. Стек хранит адреса возвратов и временные данные. С архитектурой фон Неймана все понятно, там адресные пространства совпадают. А как быть с Гарвардской? Мы не можем поместить стек в память программ, так он может содержать и данные. А в микроконтроллерах память программ еще и обычно представлена ПЗУ. Мы не можем поместить стек в память данных, так это позволит изменять адреса возвратов, которые относятся к памяти программ.

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

Но вернемся к нашей теме. Даже в Гарвардской архитектуре иногда требуется доступ к памяти программ, как к данным. Это можно сделать несколькими способами, я кратко расскажу о двух.

Пример организации доступа к памяти программ как к внешнему устройству. Иллюстрация моя Пример организации доступа к памяти программ как к внешнему устройству. Иллюстрация моя

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

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

Пример объединения адресных пространств программ и данных в единое виртуальное адресное пространство. Иллюстрация моя Пример объединения адресных пространств программ и данных в единое виртуальное адресное пространство. Иллюстрация моя

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

Пример единого виртуального адресного пространства программ и данных. Иллюстрация моя Пример единого виртуального адресного пространства программ и данных. Иллюстрация моя

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

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

Таким образом, для Гарвардской архитектуры мы можем строить виртуальные адресные пространства по разному комбинируя отдельные адресные пространства не нарушая их изолированности. А можно и включать одно пространство в другое, без изоляции. Например, мы можем включить адресное пространство регистров процессора в адресное пространство данных. Так сделано, например, в микроконтроллерах PIC Microchip и AVR Atmel. При этом туда же входит и адресное пространство ввода-вывода (для Atmel это не совсем так, но разница нам сейчас не принципиальна). Подробнее обо всем этом поговорим в следующих статьях.

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

Архитектура и адресные пространства микроконтроллеров

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

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

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

Некоторые микроконтроллеры позволяют организовывать виртуальное адресное пространство. Как это выглядит для микроконтроллеров PIC Microchip можно посмотреть в статье, ссылку на которую я давал выше. А вот для STM8 я приведу упрощенный вид виртуального адресного пространства

Процессор x86 начинает выполнение по физическому адресу 0xFFFFFFF0. Там, в конце адресного пространства, находится BIOS ROM. Первая инструкция, которую CPU выполняет из ПЗУ, - это большой скачок, который вызывает перезагрузку сегмента CS, поэтому следующая инструкция выполняется из физической области 0x000F0000 - 0x000FFFFF.

Что заставляет ПЗУ реагировать на оба региона? Есть ли какая-то специальная логика декодирования адресов на ПК? Я нашел комментарий в исходном коде Bochs, в котором говорится, что последний 128K BIOS ROM сопоставляется с 0xE0000 - 0xFFFFF. Однако я не могу найти больше информации об этом. Очевидно, что это то, что характерно для ПК, поскольку у меня встроенная плата x86, и такого зеркалирования там не происходит. Я могу использовать только прыжок.

На ПК всегда присутствует какая-то логика декодирования адреса, потому что в физическом адресном пространстве есть несколько "отверстий/окон", через которые доступ к BIOS ROM и устройствам ввода/вывода (например, видеокарта) доступны вместо ОЗУ. Что по дизайну, по соображениям совместимости, старые программы все еще могут работать на более новых компьютерах.

Что касается начального адреса, по которому ЦП начинает выполнение после reset, если вы посмотрите на документацию, вы увидите, что процессоры класса Pentium начнут с этого:

Если вы следуете обычной схеме адресации в реальном режиме, физический адрес должен быть CS.Selector * 16 + IP или, при замене значений, 0xFFFF0. Однако ЦП фактически вычисляет адрес с использованием CS.Base + (E) IP (в реальном и 16/32-битном защищенном режиме, но не в виртуальном 8086 или 64-битном защищенном режиме), следовательно, первый адрес, который запрашивает процессор из памяти будет 0xFFFFFFF0. Ваша невозможность использовать большие переходы для кода внутри ПЗУ на этом высоком адресе может быть связана с тем, что при загрузке в CS будет reset CS.Base до 16 * новое значение CS.Selector. Итак, перепрыгивая, скажем, 0xF000: 0xFFF0 передаст управление в 0xFFFF0 вместо 0xFFFFFFF0, и если ПЗУ также не будет отображено на этом низком месте в памяти, а код в нем подходит для работы с CS (.Selector) = 0xF000, он не будет работать.

Кроме того, ни ЦП, ни схема вокруг него не должны поддерживать все 32 (или более) адресные строки, если на ПК ограничено максимум 16 МБ (как и на i80286 и i80386SX) или 4 ГБ (как это было на i80386DX/original i80386 и i80486) или 2 40-52 (на 64-разрядных процессорах класса Pentium), и если это так, если количество больших бит в физическом адресном пространстве игнорируется, выполнение можно сказать эффективно начать с адреса ниже теоретического максимума - 16, например 0x00FFFFF0 (i80286/i80386SX).

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

image


Управление памятью – центральный аспект в работе операционных систем. Он оказывает основополагающее влияние на сферу программирования и системного администрирования. В нескольких последующих постах я коснусь вопросов, связанных с работой памяти. Упор будет сделан на практические аспекты, однако и детали внутреннего устройства игнорировать не будем. Рассматриваемые концепции являются достаточно общими, но проиллюстрированы в основном на примере Linux и Windows, выполняющихся на x86-32 компьютере. Первый пост описывает организацию памяти пользовательских процессов.

Каждый процесс в многозадачной ОС выполняется в собственной “песочнице”. Эта песочница представляет собой виртуальное адресное пространство, которое в 32-битном защищенном режиме всегда имеет размер равный 4 гигабайтам. Соответствие между виртуальным пространством и физической памятью описывается с помощью таблицы страниц (page table). Ядро создает и заполняет таблицы, а процессор обращается к ним при необходимости осуществить трансляцию адреса. Каждый процесс работает со своим набором таблиц. Есть один важный момент — концепция виртуальной адресации распространяется на все выполняемое ПО, включая и само ядро. По этой причине для него резервируется часть виртуального адресного пространства (т.н. kernel space).

image

Синим цветом на рисунке отмечены области виртуального адресного пространства, которым в соответствие поставлены участки физической памяти; белым цветом — еще не использованные области. Как видно, Firefox использовал большую часть своего виртуального адресного пространства. Все мы знаем о легендарной прожорливости этой программы в отношении оперативной памяти. Синие полосы на рисунке — это сегменты памяти программы, такие как куча (heap), стек и так далее. Обратите внимание, что в данном случае под сегментами мы подразумеваем просто непрерывные адресные диапазоны. Это не те сегменты, о которых мы говорим при описании сегментации в Intel процессорах. Так или иначе, вот стандартная схема организации памяти процесса в Linux:

image

Давным давно, когда компьютерная техника находилась в совсем еще младенческом возрасте, начальные виртуальные адреса сегментов были совершенно одинаковыми почти для всех процессов, выполняемых машиной. Из-за этого значительно упрощалось удаленное эксплуатирование уязвимостей. Эксплойту часто необходимо обращаться к памяти по абсолютным адресам, например по некоторому адресу в стеке, по адресу библиотечной функции, и тому подобное. Хакер, рассчитывающий осуществить удаленную атаку, должен выбирать адреса для обращения в слепую в расчете на то, что размещение сегментов программы в памяти на разных машинах будет идентичным. И когда оно действительно идентичное, случается, что людей хакают. По этой причине, приобрел популярность механизм рандомизации расположения сегментов в адресном пространстве процесса. Linux рандомизирует расположение стека, сегмента для memory mapping, и кучи – их стартовый адрес вычисляется путем добавления смещения. К сожалению, 32-битное пространство не очень-то большое, и эффективность рандомизации в известной степени нивелируется.

В верхней части user mode space расположен стековый сегмент. Большинство языков программирования используют его для хранения локальных переменных и аргументов, переданных в функцию. Вызов функции или метода приводит к помещению в стек т.н. стекового фрейма. Когда функция возвращает управление, стековый фрейм уничтожается. Стек устроен достаточно просто — данные обрабатываются в соответствии с принципом «последним пришёл — первым обслужен» (LIFO). По этой причине, для отслеживания содержания стека не нужно сложных управляющих структур – достаточно всего лишь указателя на верхушку стека. Добавление данных в стек и их удаление – быстрая и четко определенная операция. Более того, многократное использование одних и тех же областей стекового сегмента приводит к тому, что они, как правило, находятся в кеше процессора, что еще более ускоряет доступ. Каждый тред в рамках процесса работает с собственным стеком.

Возможна ситуация, когда пространство, отведенное под стековый сегмент, не может вместить в себя добавляемые данные. В результате, будет сгенерирован page fault, который в Linux обрабатывается функцией expand_stack(). Она, в свою очередь, вызовет другую функцию — acct_stack_growth(), которая отвечает за проверку возможности увеличить стековый сегмент. Если размер стекового сегмента меньше значения константы RLIMIT_STACK (обычно 8 МБ), то он наращивается, и программа продолжает выполняться как ни в чем не бывало. Это стандартный механизм, посредством которого размер стекового сегмента увеличивается в соответствии с потребностями. Однако, если достигнут максимально разрещённый размер стекового сегмента, то происходит переполнение стека (stack overflow), и программе посылается сигнал Segmentation Fault. Стековый сегмент может увеличиваться при необходимости, но никогда не уменьшается, даже если сама стековая структура, содержащаяся в нем, становиться меньше. Подобно федеральному бюджету, стековый сегмент может только расти.

Динамическое наращивание стека – единственная ситуация, когда обращение к «немэппированной» области памяти, может быть расценено как валидная операция. Любое другое обращение приводит к генерации page fault, за которым следует Segmentation Fault. Некоторые используемые области помечены как read-only, и обращение к ним также приводит к Segmentation Fault.

Под стеком располагается сегмент для memory mapping. Ядро использует этот сегмент для мэппирования (отображания в память) содержимого файлов. Любое приложение может воспользоваться данным функционалом посредством системного вызовома mmap() (ссылка на описание реализации вызова mmap) или CreateFileMapping() / MapViewOfFile() в Windows. Отображение файлов в память – удобный и высокопроизводительный метод файлового ввода / вывода, и он используется, например, для загрузки динамических библиотек. Существует возможность осуществить анонимное отображение в память (anonymous memory mapping), в результате чего получим область, в которую не отображен никакой файл, и которая вместо этого используется для размещения разного рода данных, с которыми работает программа. Если в Linux запросить выделение большого блока памяти с помощью malloc(), то вместо того, чтобы выделить память в куче, стандартная библиотека C задействует механизм анонимного отображения. Слово «большой», в данном случае, означает величину в байтах большую, чем значение константы MMAP_THRESHOLD. По умолчанию, это величина равна 128 кБ, и может контролироваться через вызов mallopt().

Если текущий размер кучи позволяет выделить запрошенный объем памяти, то выделение может быть осуществлено средствами одной лишь среды выполнения, без привлечения ядра. В противном случае, функция malloc() задействует системный вызов brk() для необходимого увеличения кучи (ссылка на описание реализации вызова brk). Управление памятью в куче – нетривиальная задача, для решения которой используются сложные алгоритмы. Данные алгоритмы стремятся достичь высокой скорости и эффективности в условиях непредсказуемых и хаотичных пэттернов выделения памяти в наших программах. Время, затрачиваемое на каждый запрос по выделению памяти в куче, может разительно отличаться. Для решения данной проблемы, системы реального времени используют специализированные аллокаторы памяти. Куча также подвержена фрагментированию, что, к примеру, изображено на рисунке:

image

Наконец, мы добрались до сегментов, расположенных в нижней части адресного пространства процесса: BSS, сегмент данных (data segment) и сегмент кода (text segment). BSS и data сегмент хранят данные, соответствующий static переменным в исходном коде на C. Разница в том, что в BSS хранятся данные, соответствующие неинициализированным переменным, чьи значения явно не указаны в исходном коде (в действительности, там хранятся объекты, при создании которых в декларации переменной либо явно указано нулевое значение, либо значение изначально не указано, и в линкуемых файлах нет таких же common символов, с ненулевым значением. – прим. перевод.). Для сегмента BSS используется анонимное отображение в память, т.е. никакой файл в этот сегмент не мэппируется. Если в исходном файле на C использовать int cntActiveUsers, то место под соответствующий объект будет выделено в BSS.

В отличии от BSS, data cегмент хранит объекты, которым в исходном коде соответствуют декларации static переменных, инициализированных ненулевым значением. Этот сегмент памяти не является анонимным — в него мэппируется часть образа программы. Таким образом, если мы используем static int cntWorkerBees = 10, то место под соответствующий объект будет выделено в data сегменте, и оно будет хранить значение 10. Хотя в data сегмент отображается файл, это т.н. «приватный мэппинг» (private memory mapping). Это значит, что изменения данных в этом сегменте не повлияют на содержание соответствующего файла. Так и должно быть, иначе присвоения значений глобальным переменным привели бы к изменению содержания файла, хранящегося на диске. В данном случае это совсем не нужно!

С указателями все немножко посложнее. В примере из наших диаграмм, содержимое объекта, соответствующего переменной gonzo – это 4-байтовый адрес – размещается в data сегменте. А вот строка, на которую ссылается указатель, не попадет в data сегмент. Строка будет находиться в сегменте кода, который доступен только на чтение и хранит весь Ваш код и такие мелочи, как, например, строковые литералы (в действительности, строка хранится в секции .rodata, которая вместе с другими секциями, содержащими исполняемый код, рассматривается как сегмент, который загружается в память с правами на выполнение кода / чтения данных – прим. перевод.). В сегмент кода также мэппируется часть исполняемого файла. Если Ваша программа попытается осуществить запись в text сегмент, то заработает Segmentation Fault. Это позволяет бороться с «бажными» указателями, хотя самый лучший способ борьбы с ними – это вообще не использовать C. Ниже приведена диаграмма, изображающая сегменты и переменные из наших примеров:

image

Мы можем посмотреть, как используются области памяти процесса, прочитав содержимое файла /proc/pid_of_process/maps. Обратите внимание, что содержимое самого сегмента может состоять из различных областей. Например, каждой мэппируемой в memory mapping сегмент динамической библиотеке отводится своя область, и в ней можно выделить области для BSS и data сегментов библиотеки. В следующем посте поясним, что конкретно подразумевается под словом “область”. Учтите, что иногда люди говорят “data сегмент”, подразумевая под этим data + BSS + heap.

Можно использовать утилиты nm и objdump для просмотра содержимого бинарных исполняемых образов: символов, их адресов, сегментов и т.д. Наконец, то, что описано в этом посте – это так называемая “гибкая” организация памяти процесса (flexible memory layout), которая вот уже несколько лет используется в Linux по умолчанию. Данная схема предполагает, что у нас определено значение константы RLIMIT_STACK. Когда это не так, Linux использует т.н. классическую организации, которая изображена на рисунке:

image

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

Одной из характерных черт архитектуры ПК является размещение в физическом адресном пространстве памяти не только собственно памяти компьютера (ОЗУ и ПЗУ), но и видеопамяти, а также регистров различных контроллеров. Ощутимым недостатком ПК, отчасти обусловленным историческими причинами, является фрагментация адресного пространства, когда ОЗУ, видеопамять, ПЗУ и регистры устройств расположены «лоскутами», довольно хаотично чередуясь друг с другом. Чтобы определить диапазоны физических адресов, в которых действительно находится оперативная память — а именно это обычно требуется знать операционной системе в первую очередь — в BIOS предусмотрено несколько функций, о практическом использовании которых можно прочитать в статье Определение объёма памяти. Здесь же мы рассмотрим общую картину распределения физического адресного пространства памяти между различными устройствами, как её видит процессор, а значит, и программист. Это описание основывается на документации фирмы Intel на чипсет G45, однако аналогичным образом адресное пространство памяти распределяется и для других чипсетов.

Заметим, что программист может поменять карту распределения памяти, перепрограммировав чипсет. Однако это, скорее всего, приведёт к потере компьютером работоспособности, поскольку код режима управления системой (SMM), находящийся в BIOS и отвечающий за такие вещи, как управление электропитанием, регулировка частоты вращения вентиляторов, эмуляция клавиатуры и мыши PS/2 и т.д., рассчитан на строго определённое распределение адресного пространства памяти, а именно на то, которое устанавливается BIOS.

Содержание

Особенности чипсета G45

Чипсет G45, как и другие чипсеты Intel этой же серии (G43, P43 и P45), включает две микросхемы, однако за распределение физического адресного пространства памяти отвечает главным образом одна из них, обычно называемая «северным мостом» и обозначаемая GMCH (Graphic and Memory Controller Hub, концентратор контроллеров графики и памяти). GMCH является связующим звеном между:

  1. процессором, в документации именуемым «хостом» и подключаемым к GMCH с помощью шины FSB (Front Side Bus);
  2. модулями оперативной памяти стандарта DDR2 или DDR3, включёнными в одно- или двухканальном режиме и именуемыми системной памятью (System Memory);
  3. шиной PCI Express , обычно используемой для подключения дискретного графического контроллера, хотя годящейся и для любого другого подходящего устройства;
  4. второй микросхемой чипсета — концентратором контроллеров ввода-вывода ICH10/ICH10R, подключаемым к GMCH посредством специализированной шины DMI (Direct Media Interface, является разновидностью PCI Express ).

Кроме того, в состав GMCH входит встроенный графический контроллер, обозначаемый в документации IGD (Internal Graphics Device). Обращения к памяти могут инициироваться хостом (процессором), встроенным графическим контрллером или устройствами, подключенными к шинам PCI Express и DMI. В зависимости от адреса и настроек GMCH эти запросы могут относиться к системной памяти, устройствам на шинах PCI Express и DMI либо иметь специальное назначение.

Распределение памяти


Типичная карта распределения физического адресного памяти для GMCH G45

Типичная карта распределения физического адресного пространства памяти для чипсета Intel G45 показана на рисунке. Она в целом применима и к любым другим чипсетам, в том числе иных производителей: все они схожи в главном, различаясь лишь деталями. В центральной части показано распределение физического адресного пространства памяти с точки зрения процессора, а значит, и программиста (Host/System View), справа — с точки зрения контроллера памяти (Physical Memory).

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

Всё адресное пространство памяти можно разделить на следующие крупные участки:

  • унаследованный диапазон адресов (Legacy Address Range);
  • нижний диапазон адресов главной памяти (Main Memory Address Range; на рисунке в него входит также область TSEG);
  • нижний диапазон адресов памяти PCI (PCI Memory Address Range);
  • верхний диапазон адресов главной памяти (Main Memory Address Range);
  • диапазон адресов перемещённой главной памяти (Main Memory Reclaim Address Range);
  • верхний диапазон адресов памяти PCI (PCI Memory Address Range).

Унаследованный диапазон адресов


Распределение памяти в унаследованном диапазоне адресов

Младший мегабайт адресного пространства (физические адреса от 0000_0000 до 000F_FFFF включительно) называется унаследованным диапазоном адресов (Legacy Address Range), поскольку его структура определяется требованиями совместимости с ранними моделями ПК. Он состоит из нескольких частей.

Нижние 640 Кбайтов (адреса 0000_0000 — 0009_FFFF) всегда отображаются на оперативную память: это так называемая стандартная память.

Далее следуют 128 Кбайтов (адреса 000A_0000 — 000B_FFFF), которые на ранних ПК использовались для доступа к видеопамяти. Конкретная их структура зависела от типа используемого видеоадаптера и его режима работы; например, цветные графические адаптеры в графических режимах использовали видеопамять, начинающуюся с адреса A0000, а информация в текстовых режимах размещалась с адреса B8000.

В современных компьютерах эта область выполняет две функции. Во-первых, она используется для совместимости со старым программным обеспечением и позволяет обращаться к видеопамяти. Применительно к чипсету G45 обычные доступы к этим адресам могут в зависимости от его настроек передаваться либо интегрированному графическому устройству (IGD, если используется встроенная графика), либо на шину PCI Express (если используется отдельный графический контроллер, установленный в гнездо этой шины), либо на шину DMI и далее через ICH на шину PCI (если используется графический контроллер, установленный в гнездо PCI).

Во-вторых, эта область может использоваться кодом режима управления системой (SMM). Когда процессор обращается к этим адресам в режиме SMM, GMCH передаёт его системной памяти, а не графическому контроллеру.

Следующие 128 Кбайтов (адреса 000C_0000 — 000D_FFFF) — это область расширения (Expansion Area). По этим адресам могут располагаться ПЗУ BIOS внешних устройств. GMCH делит эту область на 8 сегментов по 16 Кбайтов, каждый из которых может быть индивидуально настроен на доступ процессора к соответствующим адресам ОЗУ либо переадресован на DMI, причём чтение и запись настраиваются индивидуально (т. е., например, чтение может направляться на DMI, а запись — к ОЗУ). Любое обращение по этим адресам со стороны устройств, находящихся на PCI Express или DMI, всегда передаётся системной памяти. BIOS в процессе инициализации определяет наличие периферийных устройств с ПЗУ. По очереди отображая их на этот диапазон, она вызывает код из этих ПЗУ, который инициализирует соответствующие периферийные устройства. По окончании инициализации несколько первых сегментов отводятся под ОЗУ, где располагается копия ПЗУ видеоконтроллера, что сделано с целью совместимости.

Область расширенной системной BIOS (Extended System BIOS Area) занимает следующие 64 Кбайта (адреса 000E_0000 — 000E_FFFF). Она разделена на четыре сегмента по 16 Кбайтов; обращения процессора к каждому сегменту могут направляться либо в оперативную память, либо на шину DMI и далее через ICH к ПЗУ BIOS, причём доступы на чтение и запись настраиваются отдельно.

Область системной BIOS (System BIOS Area) занимает старшие 64 Кбайта унаследованного диапазона (адреса 000F_0000 — 000F_FFFF). Она не делится на сегменты; доступы к этой области направляются либо в оперативную память, либо на шину DMI и далее через ICH к ПЗУ BIOS, причём чтение и запись опять-таки настраиваются индивидуально.

Нижний диапазон адресов главной памяти


Карта нижнего диапазона адресов главной памяти

Нижняя часть диапазона адресов главной памяти (Main Memory Address Range) располагается от границы 1 Мбайта (начиная с адреса 0010_0000) до границы нижней доступной физической памяти (Top of Low Usable Physical Memory; эта величина находится в регистре GMCH TOLUD, её устанавливает BIOS). Весь этот диапазон может быть прямо отображён на физическую память, однако здесь возможны исключения, рассмотренные ниже.

Дыра ISA (ISA Hole) располагается между 15 и 16 Мбайтами адресного пространства памяти (00F0_0000 — 00FF_FFFF). Исторически её появление связано с микропроцессором 80286, способным в защищённом режиме адресовать до 16 Мбайт памяти, старший мегабайт которой был отведён под ПЗУ BIOS и некоторые другие нужды (в первую очередь под видеопамять). Современные компьютеры в ней не нуждаются, поэтому обычно эта дыра отключена, а её адреса отображаются на физическое ОЗУ. Однако теоретически некоторым устройствам может потребоваться память именно из этого диапазона, и тогда средствами настройки BIOS дыру надо включить. Когда она включена, все обращения к памяти в этом диапазоне передаются на шину DMI, а ОЗУ становится недоступным.

Необязательная область TSEG предназначена для использования кодом режима управления системой. Её размер может изменяться (для G45 он может составлять 1, 2 или 8 Мбайт), а располагается она в верхней части нижнего диапазона адресов главной памяти: её концом служит либо адрес, находящийся в TOLUD, либо адрес, с которого начинается память, отведённая для нужд встроенного графического контроллера (о ней — чуть ниже). Доступ к памяти в области TSEG возможен только со стороны процессора и только при нахождении его в режиме SMM.

Область памяти для нужд IGD выделяется только в том случае, если используется встроенный графический контроллер; на рисунке она не показана. В случае чипсета G45 эта область состоит из двух смежных участков. Первый участок находится в верхней части нижнего диапазона главной памяти, заканчиваясь адресом TOLUD. Он предназначен для хранения таблицы переадресации графического контроллера (Graphics Translation Table, GTT) и носит название «Pre-allocated Graphics GTT Stolen Memory», а его объём составляет 1 или 2 Мбайта. Второй участок, «Pre-allocated Graphics VGA Memory», имеет размер от 1 до 256 Мбайт, находится ниже первого участка и выше области TSEG и используется для хранения информации, необходимой для работы встроенного графического контроллера в режиме совместимости с VGA.

Нижний диапазон адресов памяти PCI


Карта нижнего диапазона адресов памяти PCI

Нижний диапазон адресов памяти PCI (PCI Memory Address Range) находится между TOLUD и границей 4 Гбайт (заканчивается адресом FFFF_FFFF). Большая его часть отображается на шину DMI и служит для доступа к контроллерам, входящим в состав ICH или подключенным к шинам, обслуживаемым ICH, а также к ПЗУ BIOS. Однако ряд областей этого диапазона на DMI не отображается. Ниже различные участки адресов этого диапазона обсуждаются подробнее.

В самой верхней части этого диапазона, вплотную к границе 4 Гбайт, находится область верхней BIOS (High BIOS), под которую отведено 2 Мбайта (адреса FFE0_0000 — FFFF_FFFF). Эта область отображается на шину DMI и через ICH обеспечивает доступ к ПЗУ системной BIOS и BIOS контроллеров, если таковые имеются.

Ещё один мегабайт (адреса FEC0_0000 — FECF_FFFF) зарезервирован для регистров конфигурационного пространства APIC (APIC Configuration Space). Это пространство разделено на две равные части. Нижняя половина (FEC0_0000 — FEC7_FFFF) служит для взаимодействия с IOAPIC , который физически обычно является частью ICH, хотя может быть выполнен и в виде отдельного контроллера. В любом случае обращения процессора к памяти по этим адресам передаются на шину DMI.

Заметим, что на приведённом рисунке, взятом из документации на чипсет, для области FEC8_0000 — FECF_FFFF ошибочно указан локальный APIC процессора (LAPIC). На самом деле LAPIC занимает область адресов FEE00000 — FEE00FFF (всего 4 Кбайта), при этом у каждого логического процессора в системе имеется свой LAPIC в одних и тех же адресах: когда процессор производит обращение к этой области, запрос сразу поступает в LAPIC, не выходя за пределы кристалла процессора; следовательно, GMCH просто не узнает о самом факте такого обращения. Обращения устройств по этим адресам, как уже писалось выше, рассматриваются как запросы прерываний.

Необязательная область HSEG, назваемая иногда верхним пространством памяти SMM (High SMM Memory Space), может располагаться в диапазоне адресов FEDA_0000 — FEDB_FFFF. Если она имеется, то доступы к ней со стороны процессора, находящегося в режиме управления системой, отображаются на область ОЗУ с адресами 000A_0000 — 000B_FFFF (т.е. на нижнюю память режима управления системой, занимающую те же адреса, что унаследованная видеопамять, но доступные только в режиме SMM). Доступы к HSEG в других режимах являются недопустимыми. Если же область HSEG отсутствует, все обращения к этому диапазону адресов передаются, как и большинство других обращений в нижнем диапазоне адресов PCI, на шину DMI.

В оставшихся адресах может находиться конфигурационное пространство PCI Express (PCI Express Configuration Space). Его точный размер и местоположение зависят от настроек GMCH; на рисунке оно занимает адреса от E000_0000 до EFFF_FFFF. Подробнее о назначении и функциях этого конфигурационного пространства говорится в описании шины PCI Express .

Всё остальное пространство нижнего диапазона адресов памяти PCI отведено под память и регистры внешних устройств. Большинство из них либо являются частью ICH, либо находятся на шинах PCI и PCI Express , подключаемых к ICH, поэтому GMCH передаёт обращения по этим адресам на шину DMI. Однако часть устройств может находиться на шине PCI Express , подключенной к самому GMCH (обычно это дискретный видеоконтроллер). Устройством, использующим адресное пространство памяти из данного диапазона, является встроенный в GMCH графический контроллер (IGD). Наконец, в GMCH входит ряд других вспомогательных контроллеров, которые также нуждаются в некотором пространстве адресов памяти из этого диапазона. Таким образом, оставшиеся адреса распределяются между внутренними устройствами GMCH и шинами PCI Express и DMI. На рисунке все они обозначены как DMI Interface.

Верхний диапазон адресов главной памяти

Верхний диапазон адресов главной памяти (Main Memory Address Range) начинается с границы 4 Гбайт (адрес 1_0000_0000) и простирается вверх почти до конца физически установленной памяти (её адрес в процессе инициализации заносится BIOS в регистр TOM — Top Of Memory). До самого конца физической памяти этот диапазон не доходит по той причине, что какое-то количество самых верхних адресов используется для нужд одного из компонентов GMCH — так называемого Measurement Engine (ME, «измерительный движок»). Объём этого изымаемого участка может доходить до 64 Мбайт, однако верхняя граница диапазона адресов главной памяти всегда выравнивается на границу 64 Мбайта, поэтому, если объём изымаемого участка меньше этой величины, неиспользованная память просто пропадает.

Диапазон адресов перемещённой главной памяти

Нижний диапазон адресов памяти PCI «съедает» значительное количество адресного пространства памяти, лежащего ниже границы 4 Гбайт (на практике потеря может достигать 1,5 и даже 2 Гбайт в зависимости от типов и количества установленных контроллеров). Чтобы эта оперативная память не пропадала впустую, GMCH может обеспечить к ней доступ, используя адреса, лежащие сразу за верхним диапазоном адресов главной памяти: доступы к ним переотображаются на адреса физической памяти, лежащие ниже границы 4 Гбайта. Эта «спасённая» область ОЗУ называется диапазоном адресов перемещённой главной памяти (Main Memory Reclaim Address Range). С точки зрения операционной системы разницы между этой областью и верхним диапазоном адресов главной памяти нет, поэтому можно считать, что это единая область, начинающаяся с адреса 1_0000_0000 и заканчивающаяся адресом, находящимся в регистре TOLUD (Top of Upper Usable Physical Memory, вершина верхней пригодной для использования физической памяти).

Верхний диапазон адресов памяти PCI

Верхний диапазон адресов памяти PCI начинается сразу за концом доступной физической памяти (TOUUD) и продолжается до конца адресуемого пространства памяти, т.е. до границы 64 Гбайт (адрес F_FFFF_FFFF). Эти адреса могут отображаться на шины PCI Express и DMI и использоваться для доступа к регистрам и памяти различных контроллеров, поддерживающих адресацию свыше 4 Гбайт.

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


Рис. 1.5. Типичное распределение адресного пространства.

Первые 640 Кб адресного пространства с адресами от 00000h до 9FFFFh (и, соответственно, с сегментными адресами от 0000h до 9FFFh) отводятся под основную оперативную память, которую еще называют стандартной (conventional). Начальный килобайт оперативной памяти занят векторами прерываний, которые обеспечивают работу системы прерываний компьютера, и включает 256 векторов по 4 байта каждый. Вслед за векторами прерываний располагается так называемая область данных BIOS, которая занимает всего 256 байт, начиная с сегментного адреса 40h. Сама BIOS (от Basic In-Out System, базовая система ввода-вывода) является частью операционной системы, хранящейся в постоянном запоминающем устройстве. Это запоминающее устройство (ПЗУ BIOS) располагается на системной плате компьютера и является, таким образом, примером встроенного, или "зашитого" программного обеспечения. В функции BIOS входит тестирование компьютера при его включении, загрузка в оперативную память собственно операционной системы MS-DOS, хранящейся на магнитных дисках, а также управление штатной аппаратурой компьютера - клавиатурой, экраном, дисками и прочим. В области данных BIOS хранятся разнообразные данные, используемые программами BIOS в своей работе. Так, здесь размещаются:

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

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

В области памяти, начиная с адреса 500h, располагается собственно операционная система MS-DOS, которая обычно занимает несколько десятков Кбайт. Программы MS-DOS, как и другие системные составляющие (векторы прерываний, область данных BIOS) записываются в память автоматически в процессе начальной загрузки компьютера.

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

Оставшиеся 384 Кб адресного пространства между границами 640 Кб и 1 Мб, называемые старшей, или верхней (upper) памятью, первоначально были предназначены для размещения постоянных запоминающих устройств (ПЗУ). Практически под ПЗУ занята только небольшая часть адресов, а остальные используются в других целях.

Часть адресного пространства старшей памяти отводится для адресации к графическому и текстовому видеобуферам графического адаптера. Графический адаптер представляет собой отдельную микросхему или даже отдельную плату, в состав которой входит собственное запоминающее устройство (видеопамять). Это запоминающее устройство не имеет никакого отношения к оперативной памяти компьютера, однако, его схемы управления настроены на диапазоны адресов A0000h. AFFFFh и B8000h. BFFFFh, входящих в общее с памятью адресное пространство процессора. Поэтому любая программа может обратиться по этим адресам и, например, записать данные в видеобуфер, что приведет к появлению на экране некоторого изображения. Если видеосистема находится в текстовом режиме, а запись осуществляется по адресам текстового видеобуфера, на экране появятся изображения тех или иных символов (букв, цифр, различных знаков). Если же перевести видеосистему в графический режим, и записывать данные в графический видеобуфер, то на экране появятся отдельные точки или линии. Можно также прочитать текущее содержимое ячеек видеобуфера.




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

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

В состав компьютера, наряду со стандартной памятью (640 Кб), входит еще расширенная (extended) память, максимальный объем которой может доходить до 4 Гбайт. Эта память располагается за пределами первого мегабайта адресного пространства и начинается с адреса 100000h. Реально на машине может быть установлен не полный объем расширенной памяти, а лишь несколько десятков Мбайт или даже меньше.

Читайте также: