Asm выделение памяти dos
Сегодня мы попробуем закончить с азами управления массивами, а также познакомимся поближе с методикой выделения памяти в славном семействе Windows NT/2K/XP/Vista.
В предыдущих примерах наш массив располагался в секции данных исполняемого модуля. Такой подход вполне удобен при работе со статическим массивом. Но статические массивы используются достаточно редко. Чаще возникает необходимость использования динамического массива, количество элементов в котором не определено, а следовательно, размер памяти, занимаемой массивом, может изменяться за время работы программы.
Ранее, на рубеже перехода от семейства 9x к семейству NT, для выделения небольших объемов памяти рекомендовалось пользоваться функциями GlobalAlloc и LocalAlloc. Сейчас это по ряду причин считается нецелесообразным, а для выделения небольших объемов памяти (до нескольких мегабайт) рекомендуется использовать функции работы с кучами. Куча (от англ. Heap) — это область зарезервированного процессом адресного пространства, при помощи которой реализуется динамическое выделение памяти. При создании кучи под нее выделяется лишь виртуальная память, а физическая память выделяется специальным диспетчером куч (heap manager) уже по мере заполнения кучи данными. Физическая память выделяется определенными системой блоками — страницами, а по мере освобождения страниц возвращается системе. У каждого процесса есть стандартная куча, дескриптор которой можно получить вызовом функции GetProcessHeap. Параметры у этой функции отсутствуют, так как каждый процесс имеет лишь одну стандартную кучу. Также можно создавать дополнительные кучи при помощи функции HeapCreate. Ее параметры:
1. опции кучи: HEAP_CREATE_ENABLE_EXECUTE — разрешение на исполнение кода, содержащегося в куче, HEAP_GENERATE_EXCEPTIONS — системные уведомления при переполнении кучи и т.п., HEAP_NO_SERIALIZE — отказ от одновременного доступа к куче несколькими потоками немного
увеличивает производительность, но может вызвать сбой при попытке одновременного доступа.
2. Начальный размер кучи в байтах. Значение округляется в большую сторону до ближайшего значения, кратного размеру страницы, поэтому, если указать ноль, будет зарезервирована одна страница. Размер страницы можно узнать при помощи функции GetSystemInfo.
3. Максимальный размер кучи в байтах. Если при выполнении функций HeapAlloc и HeapReAlloc (выделение памяти в куче) запрашиваемый размер превышает начальный размер кучи, то система выделяет новые страницы физической памяти, но при этом суммарный размер кучи не может превысить значение данного параметра. Если максимальный размер кучи не равен нолю, то полученная куча не является "растущей", и существует ограничение на максимальный размер выделяемого блока, который не должен превышать 0x7FFF8 байт. Функция выделения памяти для такой кучи автоматически вернет ошибку при попытке выделить блок памяти большего размера. Если же в качестве максимального размера кучи указать ноль, то куча считается "растущей". Общий размер такой кучи ограничен лишь доступной системе памятью. Для такой кучи попытка выделения блока памяти размером более 0x7FFF8 байт уже не будет считаться ошибкой, — система сама произведет вызов функции VirtualAlloc для выделения памяти под большой блок данных. Приложения, которым требуется выделять большие блоки памяти, всегда должны устанавливать значение данного параметра в ноль.
При успешном выполнении функция возвращает в EAX-дескриптор созданной кучи. В случае ошибки возвращается ноль. Причем функция работает так, что точный максимальный размер блока в "нерастущей" куче никогда не известен. В официальной документации MSDN сказано лишь, что максимальный размер блока должен быть немного меньше 0x7FFF8 байт. Например, у меня не получилось выделить в "нерастущей" куче блок более 0x7EFF8. Так что старайтесь не использовать кучи фиксированного размера для выделения блоков памяти размером, близким к загадочной цифре 0x7FFF8. Для выделения блоков памяти в куче используется функция HeapAlloc, а для изменения размера выделенного блока — HeapReAlloc. Параметры первой из них:
1. Дескриптор кучи, в которой будет выделен блок (возвращается функциями GetProcessHeap и HeapCreate).
2. Опции блока: HEAP_GENERATE_EXCEPTIONS — системные уведомления при переполнении и т.п. (лучше указывать эту опцию сразу в HeapCreate для всех блоков кучи), HEAP_NO_SERIALIZE (тоже можно указать в HeapCreate для всех блоков кучи, а здесь не указывать), HEAP_ZERO_MEMORY — проинициализировать блок нулевыми значениями (занимает время, использовать по необходимости).
3. Размер выделяемого блока в байтах (если куча не является "растущей", то размер блока не должен превышать 0x7FFF8 байт).
В случае успешного выполнения функция возвращает указатель на выделенный блок памяти. При возникновении ошибки функция возвращает ноль, если не был указан параметр HEAP_GENERATE_EXCEPTIONS. Иначе функция может вернуть значения STATUS_NO_MEMORY (нехватка памяти) или STATUS_ACCESS_VIOLATION (неверные параметры) в зависимости от произошедшей ошибки. В отличие от большинства функций, функции HeapAlloc и HeapReAlloc не вызывают SetLastError в случае ошибки, поэтому обращение к GetLastError не даст более подробных сведений об ошибке. Параметры функции HeapReAlloc:
1. дескриптор кучи, в которой находится перераспределяемый блок (возвращается функциями GetProcessHeap и HeapCreate);
2. опции перераспределения: HEAP_GENERATE_EXCEPTIONS, HEAP_NO_SERIALIZE, HEAP_REALLOC_IN_PLACE_ONLY — запрещает перемещение блока: в случае недостатка свободной памяти для расширения блока по месту его расположения, функция не станет искать подходящее место в памяти, а вернет ошибку, оставив блок без изменений, HEAP_ZERO_MEMORY;
3. указатель на перераспределяемый блок памяти (возвращается функциями HeapAlloc или HeapReAlloc);
4. новый размер блока в байтах (если куча не является "растущей", то размер блока не должен превышать 0x7FFF8 байт).
Возвращаемые значения такие же, как и у HeapAlloc.
Обе вышеописанные функции могут выделить блок памяти как указанного размера, так и чуть больше требуемого. Точный размер выделенного блока можно узнать, обратившись к функции HeapSize. Ее параметры:
1. дескриптор кучи, в которой находится блок;
2. опции: HEAP_NO_SERIALIZE;
3. указатель на заданный блок.
В случае успешного выполнения возвращается размер заданного блока. В случае ошибки — минус единица. Данная функция также не указывает подробности о произошедшей ошибке через SetLastError.
Если выделенный в куче блок памяти вам больше не требуется, его следует удалить функцией HeapFree, чтобы освободить системную память. Параметры этой функции такие же, как и у HeapSize. В случае успешного освобождения памяти от заданного блока возвращается ненулевое значение. В случае ошибки возвращается ноль. Подробности можно узнать при помощи функции GetLastError. Для удаления кучи целиком используется функция HeapDestroy. Она имеет всего один параметр — дескриптор удаляемой кучи, возвращаемый функцией HeapCreate. Только не пытайтесь удалить кучу, дескриптор которой получен при помощи функции GetProcessHeap. Не вы ее создавали, не вам ее удалять. В случае успешного удаления кучи возвращается ненулевое значение. В случае ошибки возвращается ноль. Подробности можно узнать при помощи функции GetLastError.
Теперь подробнее о цикле удаления. Мы, как и прежде, командой repne scasb сканируем массив в поисках элемента, равного содержимому AL. Сканирование может быть прекращено либо если найдено равенство, либо если ECX сравнялся с нулем. Второй случай мы только что рассмотрели. Если же найден элемент, равный AL, то мы прыгаем на метку ".del_el", чтобы выполнить удаление элемента. В edi к этому моменту находится адрес следующего за удаляемым элементом. А нам для удаления при помощи команды rep movsb необходимо, чтобы в edi был адрес удаляемого элемента, а в esi — следующего за ним. Поэтому мы копируем содержимое edi в esi, а edi уменьшаем на единицу. Теперь мы сохраним регистры eax, ecx и edi, потому что текущие значения ecx и edi нам еще понадобятся для продолжения поиска других кандидатов на удаление с текущей позиции. На текущую позицию будет сдвинут следующий за удаляемым элементом, а он ведь тоже может оказаться подлежащим удалению, хотя пока мы об этом не знаем, а лишь готовимся к удалению текущего элемента, но предусматриваем все варианты. Содержимое EAX мы сохраняем по другой причине: после вызова функции удаления лишнего окошка в EAX вернется результат ее выполнения. А у нас в AL, который является частью EAX, если вы помните, хранится значение искомых элементов. Поэтому обязательно сохраняем.
Элемент удалили, уменьшили текущее количество элементов в переменной [n], удаляем последнее поле. Его дескриптор сейчас находится по адресу [hmas]+[n]*4. Но ввиду невозможности двойной адресации мы поместим значение [n] в edx, умножим на 4 (shl edx,2) и добавим к edx значение [hmas]. Теперь, когда edx хранит адрес дескриптора удаляемого окна, мы смело можем указать [edx] в качестве параметра функции SendMessage. После удаления окошка задвигаем в bl единицу в знак того, что хотя бы один элемент мы уже удалили. Восстанавливаем сохраненные регистры, соответственно, в обратном порядке. На всякий случай проверяем ecx на ноль — вдруг это был последний элемент массива. Если не ноль, то переходим на метку ".del_next", чтобы продолжить сканирование с того элемента, который мы еще не проверяли. Иначе обнуляем eax — успешное выполнение — и возвращаемся из процедуры. В процедуре вставки элемента так же, как и в остальных измененных процедурах, и по тем же причинам адрес массива копируется в регистр. Ну и для доступа к ячейке [hmas]+[n]*4 используется такой же прием, как и в процедуре удаления. Однако и сейчас, несмотря на то, что массив динамический, ему чего-то не хватает. Мы не можем вставить больше элементов, чем MAXMASSIZE. Для того, чтобы обойти это ограничение, заменим данную константу переменной. Например, икс:
…
x dd MAXMASSIZE
…
Теперь на метке ".in:" сравнивайте [n] с [x], естественно, через регистр. Если памяти не хватает для вставки очередного элемента — выделите ее функцией HeapReAlloc. Только постарайтесь сделать так, чтобы память выделялась сразу под несколько элементов, а не под один каждый раз — это уменьшит фрагментацию и общее время на выделение памяти. А еще лучше — чтобы память выделялась заранее: например, когда текущей выделенной памяти осталось меньше чем на 10 элементов, блоки увеличиваются на 20 элементов. Попробуйте сделать это сами — ваш уровень это позволяет. Вот, собственно, и все, что я вам хотел рассказать о динамических массивах. Понятно, что существуют массивы, в которых каждый элемент может быть строкой неопределенного размера, существуют двумерные и многомерные массивы, но общие принципы работы с ними остаются такими же. Просто усложняется алгоритм обработки. Однако, если вы разберетесь с простым типом массивов, то и более сложные типы без проблем поймете по мере надобности. Желаю вам успехов в этом, и до новых встреч!
Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт
Компьютерная газета. Статья была опубликована в номере 38 за 2008 год в рубрике программирование
My question is about the logic of dynamic memory allocation in assembly (particularly, MASM). There are lot of articles on this topic and all of them rely on the use of malloc or brk. However, according to my understanding, malloc as a part of C language must (or could) be certainly written on assembly. Idem for brk, because it's a part of the operating system, thus also written on C which can be replaced 1 to 1 by assembly. Very very long time ago I have seen an article in PCMag about dynamic memory allocation in MS-DOS using pure asm. Unfortunately, I have lost all the traces of this wonderful piece of writing. Now I'm working with FreeDOS (precisely bootable FreeDOS flash card) and wondering how to proceed if someone decides to write his own memory allocator? What is the starting point and the logic of memory allocation without relying on OS mechanisms?
Your "pure asm" examples likely relied on an interrupt or something. Either that, or they allocated their own large static blocks of memory and used that as their heap with which to allocate their own "dynamic" blocks from. Assembly has a very static view of memory and as such your custom malloc implementation would either rely on some sort of system call/interrupt or just a large static block of memory allocated as part of the object file (BSS/data segments)
What you're missing here is that if you don't use FreeDOS's allocator you won't know what memory FreeDOS (and other things) have already allocated, and FreeDOS won't know what memory you've allocated. What you can do is allocate a big chunk of memory using FreeDOS and then suballocate it with your own allocator. Note that you can't just write brk() yourself, since on Unix-type systems it's a system call that maps in memory into the process, something that needs to be done in the kernel. In other words, to perform memory allocation at the lowest level you need to write your own OS.
since interrupts are all provided by BIOS - No, the ABI for DOS system calls is int 21h with AH= call number. The BIOS uses a few different interrupt numbers, but it's not the only thing callable via a software-interrupt.
Of course programs in DOS are written knowing what memory they have been allocated. All programs can write anywhere, but in order to be functional they usually attempt to play nice with each other. That usually means not arbitrarily walking allover MS-DOS and other apps. You want memory you request it. You have too much memory, you give it back.
Здраствуйте.У меня возникла нетривиальная задача - создать собственный менеджер кучи под Turbo C без использования функций из стандартных библиотек.Подскажите пожалуйста как получить адрес или выделить память под heap.
[LEFT]А чем не устраивает стандартный? new, delete и всё, никаких проблем с выделением памяти.
[/LEFT]
во первых у меня программа на C.во вторых этим я подключу к программе весь rtl - а у меня в нем надо только пара функций.И потом я хотел бы разобраться как всё это работает.
[LEFT]Как Вам известно куча и нужна для того, что бы при выходе из функции, локальные переменные автоматически не удалялись, а оставалась память выделенная под них в области кучи. Указатель указывает на эту память. Я думаю можно обойтись и проще, не вижи причин для использования кучи в Си. Помоему это вобще излишняя бирюлька, так же как и объединения (хотя иногда бывают полезны, как раз в распределителях памяти).
[/LEFT]
CS: 586000 DS: 589632 SS: 589632 ES: 589632 END IMAGE: 655344
GETMEM(10) 1: 8 GETMEM(30) 2: 8 GETMEM (1000) 3: 8
FREEMEM 3 FREEMEM 2
GETMEM(1000) 2: 8
FREEMEM 3 FREEMEM 2 FREEMEM 1
MEMSIZE() : 752
GETMEM(MEMSIZE()) : 16
MEMSIZE() : 368
GETMEM(MEMSIZE) 2: 0
MEMSIZE() : 176
GETMEM(MEMSIZE) 2: 0
MEMSIZE() : 160
локальные переменные в C храняться в стеке.после выхода из функции их значение теряется,в стеке освобождается от них место.если только они не объявлены как static - тогда они хранятся в области данных.из кучи программа динамически выделяет память
спасибо всем кто читал этот топик.я уже нашел ошибку.
если кому-нибудь нужны вот функции для выделения памяти в dos.
работает под turbo c 3.0 в модели памяти small
DWORD MEMSIZE()
DWORD r;
_BX=0xffff;
_AH=0x48;
asm int 0x21
asm jnc reterr
r=_BX;
return r reterr:return 0;
>
void far*GETMEM(DWORD size)
DWORD r;
_BX=size+15>>4;
_AH=0x48;
asm int 0x21
asm jnb succ
_AX=0;succ:
r=_AX;
r return (void far*)r;
>
void FREEMEM(void far*mem)
asm push es
_ES=(DWORD)mem>>16;
_AH=0x49;
asm int 0x21
asm pop es
>
Остановившись коротко на этих вопросах, можем теперь перейти к рассмотрению того, что собственно является предметом данной статьи: использованию памяти в высших адресах DOS. Область памяти в адресах от A000h до FFFFh была зарезервирована IBM для системного аппаратного обеспечения. На многих машинах, однако, используется не вся эта область. Например, если на вашей машине стоит обычная системная BIOS, в случае неграфической монохромной системы может использоваться всего лишь 36 Кбайт из зарезервированных 384 Кбайт, тогда как в случае IBM OS/2, видео адаптера 8514/А и платы сети, возможно, из них будет использоваться свыше 300 Кбайт. Все, что не используется - это адресное пространство, которое "пропадает зря", и куда программы управления памятью компьютера 386 могут поместить действительно используемую память. Рассмотрим позднее особые случаи использования, теперь же остановимся на том, что ваша собственная система оставляет без внимания.
Вы, наверное, считаете, что хитроумная программа управления памятью - QEMM или 386max - способна автоматически обнаружить все выделенные и невыделенные участки памяти. Они действительно постараются это сделать, но с нестандартным аппаратным обеспечением связано слишком много параметров, а практически все компьютеры имеют какие-нибудь нестандартные аппаратные средства, так что вся работа не может быть проделана без вашей помощи.
Итак, возьмите лист бумаги и карандаш, и пометьте три колонки: "Используется", "Точно свободно" и "Возможно свободно". Затем выделите шесть строк для областей от A до F. Теперь мы можем постараться вычислить, какое адресное пространство можно передать программам. Если бумага и карандаш вас не устраивают, воспользуйтесь вашей излюбленной электронной таблицей.
Размещение памяти в высших адресах DOS
Области A и B зарезервированы для видеопамяти, но в большинстве случаев по крайней мере часть их не используется. Оригинальный монохромный адаптер берет из них 4 Кбайт, чтобы разместить 4000 байтов, необходимых для описания текстового экрана (25 строк на 80 столбцов, по 2 атрибута). Эта память начинается с адреса B000h и продолжается почти до адреса B100h. К сожалению, в зависимости от конкретной BIOS вашего компьютера, код простой команды, например, CLS, может занять большую, чем 4 Кбайт, область памяти из отрезка B000h - B100h. AMI BIOS (Northgate) задействует только эти 4 Кбайт, но встроенный AT эффективно использует память от B000h до B200h, а некоторые машины Compaq используют память от B000h до B400h. Следовательно, если у вас монохромный адаптер, вам следует проэкспериментировать.
Рис.4. Области А и В могут размещаться рядом с видео-памятью. Многие видео-платы не используют область оперативной памяти, отведенную для них, полностью, несмотря на это видео-память не может использоваться для других целей.
Другой стандартной областью является область F. В ней располагается системная оперативная память. Тем не менее, не все системы полностью занимают весь отведенный участок размером 64 Кбайт, и не все из этих 64 Кбайт нужны после загрузки. Например, в системах с последними версиями AMI BIOS память от F000h до F800h используется программой установки и диагностики, которая может быть вызвана во время загрузки. Поскольку этот участок не используется после загрузки, можно позволить программе управления памятью разместить там что-нибудь другое.
На оригинальном IBM AT память с адресами F600h - FC00h используется под ПЗУ для BASIC, значит, она может быть использована другим образом, если вы готовы пожертвовать BASICA или намерены вместо этого пользоваться GW BASIC. Если вы по натуре авантюрист, можете проэкспериментировать, используя на вашей машине часть этого адресного пространства. Для безопасности скопируйте жесткий диск, прежде чем начнете эксперименты и, разумеется, подготовьте загрузочную дискету. Если окажется, что память, которую вы попытались задействовать другим образом, принадлежит системному ПЗУ и используется, почти наверняка во время загрузки программы управления памятью произойдет крах системы.
На AT область E недоступна, поскольку она аппаратно привязана к пустому гнезду ПЗУ. Для версий QEMM и 386max, настроенных на 386, это не имеет значения, но важно для новых программ, настроенных на 286. Для PS/2 стандартом является 128 Кбайт системного ПЗУ, которое занимает адресные области E и F. На большинстве других машин область E совершенно свободна. Но, только для того, чтобы вы не знали наверняка, некоторые VGA располагают свое ПЗУ по адресу E000h. Более того, некоторые компьютеры Zenith размещают ОЗУ с C000h по C800h и с E000h по E800h. Это требует ключа в командной строке программ управления памятью, который сообщит им, что в этих областях находится ОЗУ.
Области C и D запутаны больше всего. В системах EGA и VGA видео ПЗУ обычно находится на дне области C; при наличии плат IBM для многих разновидностей EGA видео ПЗУ располагается с C000h по C400h, но в некоторых других VGA используется область с C000h по C800h. VGA Video7, Paradise и Compaq используюи ПЗУ в адресах C000h - C600h. Но платы Video7 и Paradise кроме того используют ПЗУ в области C600h C800h. Более ранние версии QEMM и 386max по умолчанию используют эти области, так что при переключении на определенные видео режимы произойдет крах системы. В результате возникновения этой проблемы технической поддержки последние версии программ управления памятью не станут использовать память с C600h по C800h в случае, если они обнаружат ПЗУ в адресах C000h - C600h. Если у вас Compaq VGA, проверьте, использует ли ваша программа управления памятью эту область и, если не использует, дайте команду
Платы сети или SCSI/ESDI ПЗУ обычно размещаются где-нибудь в другом месте в пределах областей C и D.
Проиллюстрируем, как все это работает, на примере конкретной системы. Система включает Video7 VRAM, которая целиком занимает область памяти А, область B800h - C000h для видео оперативной памяти и область C000h - C800h для видео ПЗУ и специального ОЗУ. Установлена также монохромная плата памяти, использующая память в адресах с B000h по B100h. По умолчанию обе программы управления памятью будут избегать всей области B000h - B800h, если обнаружат монохромную плату, значит, нам придется указать им явным образом, чтобы они использовали эту память.
В системе имеется также SCSI ПЗУ, которое может быть помещено в разные участки, манипулируя его ключами DIP. Установим его в область C800h - CC00h, чтобы не разбивать на фрагменты оперативную память в высших одресах. В высших адресах DOS больше не размещается ничего, кроме системного ПЗУ. Поскольку последнее имеет тип AMI, можно освободить область с F000h по F800h. С учетом вышесказанного, командная строка QEMM выглядит так:
Ключевые слова RAM (ОЗУ) и ROM (ПЗУ) указывают QEMM постараться наилучшим образом заполнить пустое адресное пространство логическим ОЗУ и перенести все ПЗУ в быструю память. Две команды I указывают память, которую следует использовать. Команда FRAME касается особенности Ventura, которая будет описана позднее. Для 386max команды выглядят так:
Ключевые слова RAM и ROM при обращении к 386max не нужны, посокльку они подразумеваются по умолчанию. Для старых версий QEMM необходимо добавить
а для старых версий 386max добавить
Расширенная память
Расширенная память, дополнительная память и HI-MEM (Microsoft) это все средства получить больше памяти, чем стандартный 1MB. Плата микропроцессора 80286 имеет на четыре адресных линии больше, чем 8086 /8088, что позволяет адресоваться к количеству адресов, большему в 16 раз (2 в степени 4). Адреса памяти выше предела 1MB называются расширенной памятью. Когда микропроцессор 286 работает как 8088 (т.е., в режиме реальной адресации), он не может получить доступ к этой памяти. Чтобы использовать расширенную память, он должен работать в режиме виртуальной (защищенной) адресации.
Поскольку микропроцессор 286 был спроектирован до того, как Intel стало известно, каким успехом оказался 8088, его разработчики не предусмотрели простого способа переключаться обратно в режим реальной адресации из режима витруальной адресации (защиты). Чтобы осуществить это обратное переключение, используются разные хитрости, и в масштабе процессора оказывается, что они требуют значительного времени на выполнение, таким образом понижая производительность и угрожая потерей прерываний. В силу этих причин до последнего времени не было возможности осуществлять выполнение программы из расширенной памяти. В основном эта память использовалась под буферы ввода-вывода и печати.
Программа улучшения возможностей DOS, такая, как Release 3 в составе Lotus 1-2-3, работает, запуская на выполнение программу в расширенной памяти и переключаясь обратно в режим реальной адресации только тогда, когда вызывается сервисная программа DOS. Это возможно для прикладных программ, которые получают в свое распоряжение все ресурсы машины, но плохо работает для резидентных программ.
На компьютере с микропроцессором 286 можно получить до 15MB расширенной памяти (весь объем памяти - 16MB). Микропроцессор 386 физически способен адресовать 4 гигабайта памяти, т.к. он имеет 32 адресные линии (2 в степени 32 байтов). Большинство из машин семейства 386, тем не менее, устанавливают искусственное ограничение на объем памяти в 15MB, из-за используемых соединительных шин и BIOS.
OS/2 использует расширенную память. Однако, поскольку OS/2 работает в режиме виртуальной адресации (защиты), термин "расширенная память" становится излишним. Под управлением OS/2 все эти определения памяти не используются, есть только одна память - любой ее участок может быть использован.
Традиция запутывания терминологии
Традиционная терминология, используемая для ссылок на память персонального компьютера, весьма запутанна. Расширенная память - это нечто совершенно другое, чем дополнительная память. Ни один из этих двух типов памяти не имеет никакого отношения к памяти в высших адресах, если не считать того, что дополнительная память использует область памяти в высших адресах для собственной индексации. Хотя эта терминология краткая и вполне отвечает действительности, прозрачной ее назвать нельзя.
Более того, память в высших адресах между 640 Кбайт и 1 Mбайтом не единственная; существует другая память в высших адресах. Это область памяти объемом 64 Кбайт (точнее, 64 Кбайт минус 16 байтов), к которой DOS может получить доступ на большинстве компьютеров с микропроцессорами 386/286, когда они работают в режиме реальной адресации. Вторая область памяти в высших адресах расположена непосредственно над границей 1MB, и осуществить доступ и управление этой областью памяти можно, используя драйвер HIMEM.SYS (Microsoft). Чтобы объяснить, как разобраться со всеми этими участками памяти, начнем с пояснения терминологии.
Известные проблемы
Существуют некоторые особенности, о которых следует знать. Aristocad производит несколько программ, которые обеспечивают большую виртуальную страницу в Ventura Publisher, Microsoft Windows или Excel (SoftKicker, SoftKicker Plus и ExcelMore). Все они используют EGA и VGA в особом режиме, при котором осуществляется доступ ко всей области A000h - C000h, включая участок B000h - B800h, который обычно свободен для систем, использующих только EGA/VGA. Таким образом, вы теряете 32 Кбайт места для резидентных программ и должны удостовериться, что ваша программа управления памятью не пытается их задействовать.
Наконец, существует общая проблема (не связанная с этими двумя программами управления памятью), которая касается области памяти в высших адресах DOS и 16-битовых плат VGA. Спецификация шины ISA заставляет шину управлять каждым участком ОЗУ размером 128 Кбайт либо целиком в режиме 8 бит, либо целиком в режиме 16 бит. Следовательно, все области A и B должны быть одного типа, C и D по возможности разного типа, E и F одинакового типа.
Замедление ПЗУ не играет роли, поскольку программа управления памятью 386 помещает его в 32-битовое ОЗУ. Это возможно, поскольку перемещение происходит на логическом уровне, а спецификация шины заботится только о физическом уровне.
Как только вы разберетесь, какие области памяти могут быть использованы для резидентных программ, следующей задачей будет их размещение. Вы должны будете побеспокоиться о том, сколько памяти нужно каждой резидентной программе, когда она уже загружена, а также о том, что она, возможно, потребует дополнительной памяти во время загрузки. Например, SideKick Plus занимает всего лишь 70 Кбайт, когда загружен, но требует более 200 Кбайт во время загрузки. Это делает данную программу непригодной для загрузки в память в высших адресах. Возможно, перспектива расчета всех комбинаций вас напугает - к счастью, как QEMM, так и 386max могут вам помочь. Недавно к их возможностям прибавились режимы, автоматизирующие такие операции.
Установка этих программ оптимальным образом может потребовать значительных усилий, но помните, что вам понадобится сделать это только один раз, зато в результате вы можете получить десятки килобайтов драгоценного места в оперативной памяти. Через несколько лет, когда большинство из нас будет работать в OS/2, мы будем оглядываться назад и смеяться над тем, что десятки килобайт ОЗУ быль столь ценны, но пока мы ограничены 640 Кбайт, это так.
Следующим шагом для вас будет решиться воспользоваться программами, которые позволят получить доступ к памяти DOS в высших адресах. Помните, что кроме управления памятью в высших адресах программы, предлагаемые Quarterdeck и Qualitas, также управляют расширенной памятью и имитируют дополнительную память, позволяя вам конфигурировать вашу систему таким образом, чтобы она работала с максимальной производительностью. Мы надеемся, что объяснение нескольких частей головоломки памяти, которое мы дали, и подробное рассмотрение высших адресов памяти DOS поможет вам в этом оптимизационном процессе. Вам остается только заняться этим.
Стандартная память
В оригинальной архитектуре персонального компьютера 640 Кбайт из этого 1 Mбайт было зарезервировано под DOS и прикладные программы, работающие под ее управлением, а область с 640 Кбайт по 1 Mбайт была зарезервирована для системного пользования. Большая часть 640 Кбайт обычной памяти действительно используется почти постоянно, но верно и то, что существуют участки зарезервированной системной памяти, которые система не использует и которые могут быть сделаны доступными для других целей. QEMM и 386max управляют именно этой доступной частью участка памяти, расположенного между 640 Кбайт и 1Mбайт, и именно об этой части памяти следует рассказать подробнее.
Удобно и общепринято делить 1 Mбайт памяти на 16 последовательных участков по 64 Кбайт каждый. Эти участки, иногда называемые страницами, помечаются шестнадцатеричными целыми числами от 0 до F, т.е., 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. (Есть старая программистская шутка, что считать в шестнадцатеричной системе легко, если отрезать большие пальцы). Итак, DOS получает области 0 - 9 (10 участков по 64 Кбайт, всего 640 Кбайт), а система получает остальное: участки A, B, C, D, E, F.
Иногда участки по 64 Кбайт разбиваются далее каждый на 16 областей по 4 Кбайт. Области, разумеется, также помечаются шестнадцатеричными числами. Например, область А разбивается на дополнительные области от 0 до F: A0, A1, . , AF. Как QEMM, так и 386max используют эти шестнадцатеричные адреса. Для завершения картины обе программы добавляют еще два числа, так что 386max ссылается на объединенную область А6 - А7 следующим образом: A600h - A800h. Последний вариант QEMM (версия 3.0) также принял этот синтаксис, тогда как предыдущие версии обозначили бы ту же самую область как A600h - A7FFh. Мы будем в дальнейшем придерживаться синтаксиса 386max, поскольку он применим теперь и к текущей версии QEMM.
Как извлечь максимум из системной памяти DOS в высших адресах
Чтобы добиться максимальной производительности работы компьютера, часто приходится совершенствовать свои знания в некоторых спецефических и сложных областях, однако немногие из них требуют знакомства со столь запутанными проблемами, как те, что связаны с дополнительной (expanded) и расширенной (extended) памятью. Если ваш компьютер основан на микропроцессоре 386, или это одна из машин с микропроцессором 286, то эти проблемы еще более усложняются, поскольку для компьютеров с микропроцессорами 286/386 существует возможность доступа к еще одной области памяти, называемой памятью в высших адресах (high memory).
Рис.1. Из 1 Мбайта оперативной памяти, имеющегося обычно в большинстве вычислительных систем, 640 Кбайт зарезервированы для DOS и работающих под ее управлением прикладных программ, а 384 Кбайт остаются недоступными для операционной системы.
Память в высших адресах - это область объемом 384 Кбайт, расположенная между границами 640 Кбайт и 1 Mбайт, зарезервированная для системного пользования - для видеопамяти, BIOS и проч. Такие программы, как QEMM (производства Quarterdeck) и 386max (производства Qualitas) управляют этой областью зарезервированной памяти таким образом, что вы можете поместить туда резидентные программы, тем самым освобождая больше обычной памяти под прикладные программы (см. рис. 1). Последние версии этих программ, QRAM (Quarterdeck) и Move'em (Qualitas), также дают возможность некоторым компьютерам с микропроцессорами 286 получить доступ к этой зарезервированной памяти. Эта статья посвящена подробному рассмотрению того, что представляет собой область памяти DOS в высших адресах и как можно ей воспользоваться, чтобы сделать систему максимально эффективной.
HIMEM
Microsoft не был первооткрывателем HIMEM, как обычно считается. HIMEM использовалась ранее DESQview (Quarterdeck), хотя стала широко доступна только с версией Microsoft Windows 2.10. HIMEM использует всего лишь 64 Кбайт из доступной расширенной памяти - точнее говоря, первые 64 Кбайт (минус 16 байтов) расширенной памяти, памяти непосредственно над 1 Mбайт. Важной для HIMEM особенностью является то, что эти 64 Кбайт доступны из режима реальной адресации и могут быть использованы без средств расширения DOS. Одно из возможных применений HIMEM - позволить DOS разместить там часть себя, таким образом освобождая дополнительно часть памяти ниже 640 Кбайт для пользовательских программ.
HIMEM в действительности основана на ошибке, на разнице между микропроцессорами 80286 и 8088 в случае, когда 286 работает, как 8088. Кроме того, что процессор разбивает память на страницы - 16 взаимно не пересекающихся участков по 64 Кбайт,- процессор также представляет память в виде пересекающихся участков по 64 Кбайт, называемых сегментами, которые начинаются с каждых 16 байтов. Предположим, что процессор пытается получить доступ к одному из сегментов, который начинается очень близко к верхней границе 1MB. 8086 при этом совершит циклический переход к памяти в низших адресах, как показано на рис. 2.
Рис.2. Реально существует две области памяти в высших адресах. Вторая из этих областей имеет объем 64 Кбайт и расположена непосредственно за 1-Мбайтной границей. DOS может получать доступ к этой области при работе в реальном режиме на большинстве из систем, оснащенных процессорами 286 и 386.
В случае процессора 80286, однако, адрес, который будет получен в результате операции, находится в действительности непосредственно над границей 1 Мбайт. Это дает возможность в режиме реальной адресации адресовать первые 64 Кбайт (минус 16 байтов) расширенной памяти. Этот участок памяти меньше 64 Кбайт на 16 байтов потому, что последний сегмент начинается на 16 байтов ниже границы 1 Mбайт. Чтобы ликвидировать разногласия в случае, когда микропроцессор 286 работает как микропрочессор 8086, IBM встроила в шину специальные средства, которые заставляют память в режиме реального вроемени переходить циклически на низшие адреса, как это происходит в 8086/8088. Драйвер HIMEM занимается как раз тем, что выборочно отменяет действие этой поправки, чтобы программы, написанные особым образом с целью использования этой области, могли поместить там информацию. Это то, что делают Microsoft Windows, DESQview, Ventura Publisher и Carousel.
Дополнительная память
Дополнительная память (expanded memory), часто называемая EMS (Expanded Memory Specification) или LIM (согласно разработчикам, Lotus, Intel и Microsoft), представляет собой схему коммутации банков, которая позволяет процессору получить доступ к большому объему памяти посредством окна размером 64 Кбайт. (Коммутация банков - способ управления памятью, когда физическая память разбита на несколько сегментов (банков) длиной, равной размеру адресного пространства процессора. В каждый момент процессор работает с одним банком). Платы EMS занимают по одной области размером 64 Кбайт адресного пространства выше границы 640 Кбайт. При рассмотрении будем ссылаться на область, расположенную в адресах D000h - E000h, поскольку она принята по умолчанию для большинства плат. К этой области обращаются, как к рамке страницы. Память EMS логически подразделяется на участки размером по 16 Кбайт, называемые страницами.
Эти страницы отличаются от страниц размером 64 Кбайт в адресном пространстве процессора 8086, хотя, так же как и страницы по 64 Кбайт, они не пересекаются. Платы дополнительной памяти имеют специальное аппаратное обеспечение, которое может переключаться на те четыре из этих страниц, к которым осуществляется доступ, когда процессор посылает команду чтения или записи, обращенную к памяти с адресами в рамке страницы. Передвигая это окно посредством программных команд, процессор может получить доступ к такому количеству памяти, которое имеется физически на плате, хотя в каждый момент доступен лишь небольшой участок. Спецификация LIM 4.0 и ранние EEMS расширяли понятие, делая возможным передвигать не только рамку страницы, но (при соответствующем аппаратном обеспечении) также и другие области адресного пространства 8086. При соответствующем аппаратном обеспечении этот способ подкачки может быть использован для многозадачного режима DESQview и Windows.
Дополнительная память особенно полезна потому, что, в отличие от расширенной памяти и HIMEM, она использует только адреса ниже границы 1MB; следовательно, она может использоваться на машинах с процессорами 8086/8088.
Теперь к этой путанице карт и разной памяти присоединился микропроцессор 386, который обладает свими собственными средствами работы с памятью. Микропроцессор 386 поддерживает справочную таблицу, которая позволяет различать физическую и логическую память; он может определить, какая физическая память соответствует данной логической памяти. Примером может служить дублирование ПЗУ. Видео-ПЗУ на многих адаптерах работает значительно медленнее, чем ОЗУ компьютера с процессором 386. Можно программно скопировать ПЗУ в ОЗУ, затем привести в соответствие справочные таблицы таким образом, чтобы обращения к адресам, которые логически принадлежат ПЗУ, в действительности передавались копии, расположенной в значительно более быстродействующем ОЗУ. Информация, которая, как будут считать программы, размещается в нормальных адресах ПЗУ (логических адресах), будет в действительности размещена в различных физических адресах ОЗУ.
Спецификация LIM 4.0 является стандартной спецификацией организации дополнительной памяти. Для доступа к защищенной памяти существуют две спецификации: XMS (Extended Memory Specification), спецификация Microsoft, и VCPI (Virtual Control Program Interface). VCPI дает возможность программе запрашивать защищенные ресурсы у программы управления памятью в режиме виртуальной адресации (защиты), такой, как 386max или QEMM. Программы, работающие в режиме виртуальной адресации (защиты), например, Mathematica и Interleaf, могут работать в среде этих программ управления памятью только потому, что они поддерживают VCPI.
Читайте также: