Средние 10 бит линейного адреса предназначены для индексации таблицы страниц, которая содержит 1024 дескриптора страниц, которые, в свою очередь, определяют физический адрес страниц. Размер страницы составляет 4 Кб. Легко сосчитать, какое адресное пространство может быть охвачено одним каталогом таблиц страниц. Это составляет 1024*1024*1024*4 байт, т.е. порядка четырех гигабайт.
Младшие 12 бит определяют смещение внутри страницы. Как легко заметить, это
как раз составляет 4 Кб (4095 байта)51.
Конечно, читатель уже догадался, что для каждого процесса должен существовать
свой каталог таблиц страниц. Переключение же между процессами можно осуществлять
посредством изменения содержимого регистра CR3
. Однако это не
совсем рационально, так как требует большого объема памяти. В реальной ситуации
для переключения между процессами производится изменение каталога таблиц
страниц.
Обратимся теперь к структуре дескрипторов страниц (дескриптор таблицы страниц имеет ту же самую структуру).
- Биты 12-31 - адрес страницы, который в дальнейшем складывается со смещением, предварительно сдвигаясь на двенадцать бит.
- Биты 9-11 - для использования операционной системой.
- Биты 7-8 - зарезервированы и должны быть равны нулю.
- Бит 6 - устанавливается, если была осуществлена запись в каталог или страницу.
- Бит 5 - устанавливается перед чтением и записью на страницу.
- Бит 4 - запрещение кэширования.
- Бит 3 - бит сквозной записи.
- Бит 2 - если значение этого бита равно
0
, то страница относится к супервизору, если1
, то страница относится к рабочему процессу. Этим устанавливается два уровня доступа. - Бит 1 - если бит установлен, то запись на страницу разрешена.
- Бит 0. Если бит установлен, то страница присутствует в памяти. Страницы, содержащие данные сбрасываются на диск и считываются, когда происходит обращение к ним. Страницы, содержащие код, на диск не сбрасываются, но могут подкачиваться из соответствующих модулей на диске. Поэтому память, занятая этими страницами, также может рационально использоваться.
47 Я имею в виду и микропроцессоры совместимые с Intel, выпускаемые другими фирмами.
48 В узком смысле слова индексными регистрами называются DI и SI.
49 Когда-то казалось, что один мегабайт памяти это много.
50 В старых моделях регистр содержал всего 2 байта.
51 Размер страницы в операционной системе Windows NT может отличаться от 4 Кб, что, впрочем, почти никогда не сказывается на программировании.
II
Адресное пространство процесса. В предыдущем разделе мы говорили о
страничной и сегментной адресации. Как же эти две адресации уживаются в Windows?
Оказывается, все очень просто. В сегментные регистры загружаются селекторы,
базовые адреса которых равны нулю, а размер сегмента составляет 4 гигабайта.
После этого о существовании сегментов и селекторов можно забыть, хотя для
микропроцессора этот механизм по-прежнему работает. Основным же механизмом
формирования адреса становятся страничные преобразования. Такая модель памяти и
называется плоской (FLAT
). Логическая адресация в такой модели
определяется всего одним 32-битным смещением. До сих пор все наши программы
писались именно в плоской модели памяти. При этом мы представляли, что вся
область памяти, адресуемая 32-битным адресом, находится в нашем распоряжении.
Разумеется, мы были правы, только адрес этот является логическим адресом,
который, в свою очередь, подвергается страничному преобразованию, а вот в какую
физическую ячейку памяти он попадает, ответить уже весьма затруднительно.
На Рис. 3.6.3 представлено логическое адресное пространство процесса. Особо обратите внимание на разделенные области памяти. Что это значит? А значит это только одно: эти области памяти проецируются на одно и то же физическое пространство.
Самая нижняя область адресного пространства отводится под образ операционной системы MS DOS, которая как видите, еще вполне зримо присутствует в операционной системе Windows. Кроме того, эта область используется для выделения динамической памяти 16-битным процессам.
Следующая область адресного пространства, между 4 Мб и 2 Гб, является адресным пространством процесса. Процесс занимает эту область пространства под код, данные, а также специфичные для него динамические библиотеки. Это не разделяемая область. Есть, однако, исключения, с которым мы уже встречались. Можно определить отдельные разделяемые секции. Это значит, что некоторые страницы из этого логического пространства будут отображаться в одну физическую область у разных процессов. Следующая область содержит в себе файлы, отображаемые в память, системные динамические библиотеки, а также динамическую память для 16-битных приложений. Последняя часть адресного пространства отведена под системные компоненты. Удивительно, но в Windows 9х эта область не защищена от доступа обычных программ.
Рис. 3.6.3. Адресное пространство процесса. Области 1,3,4 являются разделяемыми.
III
Управление памятью. В этом разделе мы разберем несколько функций, позволяющих динамически выделять и удалять блоки памяти.
Начнем с функции GlobalAlloc
. Другая функция,
LocalAlloc
, полностью эквивалентна первой и сохранена только для
совместимости со старыми приложениями. Функция имеет два аргумента. Первым
аргументом является флаг, о значении которого будем говорить ниже. Вторым
аргументом является число необходимых байтов. Если функция выполнена успешно, то
она возвращает адрес начала блока, который можно использовать в дальнейших
операциях. Если же система не может выделить достаточно памяти, то функция
возвращает 0
.
Обычно значение флага принимают равным константе GMEM_FIXED
,
которая равна нулю. Это означает, что блок памяти неперемещаем. Неперемещаемость
следует понимать в том смысле, что не будет меняться виртуальный адрес блока,
тогда как физическая память, куда проецируется данный блок, может, разумеется,
меняться системой. Комбинация данного флага с флагом GMEM_ZEROINIT
приводит к автоматическому заполнению выделенного блока нулями, что часто бывает
весьма удобно. Изменить размер выделенного блока можно при помощи функции
GlobalReAlloc
. Первым аргументом данной функции является указатель
на изменяемый блок, второй аргумент - размер нового блока, третий аргумент -
флаг. Заметим, что данная функция может изменить свойства блока памяти, т.е.,
например, сделать его перемещаемым.
Обратимся теперь снова к флагам функции GlobalAlloc
. Дело в том,
что если ваша программа интенсивно работает с памятью, т.е. многократно выделяет
и освобождает память, память может оказаться фрагментированной. Действительно -
Вы же запрещаете перемещать блоки. В этом случае можно использовать флаг
GMEM_MOVEABLE
. Выделив блок, Вы можете в любой момент зафиксировать
его при помощи функции GlobalLock
, после этого спокойно работая с
ним. С помощью функции GlobalUnlock
можно в любой момент снять
фиксацию, т.е. разрешить системе упорядочивать блоки. Надо иметь в виду, что при
использовании флага GMEM_MOVEABLE
возвращается не адрес, а
дескриптор. Но как раз аргументом функции GlobalLock
и является
дескриптор. Сама же функция GlobalLock
возвращает адрес.
Возможен и еще более экзотический подход с использованием флага
GMEM_DISCARDABLE
. Этот флаг используется совместно с
GMEM_MOVEABLE
. В этом случае блок может быть удален из памяти
системой, если только вы его предварительно не зафиксировали. Если блок был
удален системой, то функция GlobalLock
возвратит 0
, и
Вам придется снова выделять блок и загружать, если необходимо, данные.
Для удаления блока памяти используется функция GlobalFree
.
Причем в случае выделения фиксированного блока памяти, аргументом функции
является адрес блока памяти, а в случае перемещаемого блока памяти - дескриптор.
Для освобождения удаляемого блока памяти используйте функцию
GlobalDiscard
.
Особо хочу отметить функцию GlobalMemoryStatus
, с помощью
которой можно определить количество свободной памяти. Единственным параметром
данной функции является указатель на структуру, содержащую информацию о памяти.
Вот эта структура.
MEM STRUC dwLength DW ? dwMemoryLoad DW ? dwTotalPhys DW ? dwAvailPhys DW ? dwTotalPageFile DW ? dwAvailPageFile DW ? dwTotalVirtual DW ? dwAvailVirtual DW ? MEM ENDS
dwLength
- размер структуры в
байтах.dwMemoryLoad
- процент использованной
памяти.dwTotalPhys
- полный объем физической памяти в
байтах.dwAvailPhys
- объем доступной физической памяти в
байтах.dwTotalPageFile
- количество сохраненных байт физической
памяти на диске.dwAvailPageFile
- количество доступных байт
памяти, сохраненных на диске.dwTotalVirtual
- объем виртуальной
памяти.dwAvailVirtual
- объем доступной виртуальной памяти.
Ниже на Рис. 3.6.4 показано простейшее применение функции
GlobalAlloc
.
; файл MEM.ASM .386P ; плоская модель .MODEL FLAT, stdcall ; константы ; для вывода в консоль STD_OUTPUT_HANDLE equ -11 GENERIC_READ equ 80000000h OPEN_EXISTING equ 3 IFDEF MASM ; MASM ; прототипы внешних процедур EXTERN GlobalFree@4:NEAR EXTERN GlobalAlloc@8:NEAR EXTERN GetFileSize@8:NEAR EXTERN CloseHandle@4:NEAR EXTERN CreateFileA@28:NEAR EXTERN ReadFile@20:NEAR EXTERN GetStdHandle@4:NEAR EXTERN WriteConsoleA@20:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR ; директивы компоновщику для подключения библиотек includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM LOCALS ; прототипы внешних процедур EXTERN GlobalFree:NEAR EXTERN GlobalAlloc:NEAR EXTERN GetFileSize:NEAR EXTERN CloseHandle:NEAR EXTERN CreateFileA:NEAR EXTERN ReadFile:NEAR EXTERN GetStdHandle:NEAR EXTERN WriteConsoleA:NEAR EXTERN ExitProcess:NEAR EXTERN GetCommandLineA:NEAR GlobalFree@4 = GlobalFree GlobalAlloc@8 = GlobalAlloc GetFileSize@8 = GetFileSize CloseHandle@4 = CloseHandle CreateFileA@28 = CreateFileA ReadFile@20 = ReadFile GetStdHandle@4 = GetStdHandle WriteConsoleA@20 = WriteConsoleA ExitProcess@4 = ExitProcess GetCommandLineA@0 = GetCommandLineA ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' LENS DWORD ? HANDL DWORD ? ; дескриптор консоли HF DWORD ? ; дескриптор файла SIZEH DWORD ? ; старшая часть длины файла SIZEL DWORD ? ; младшая часть длины файла GH DWORD ? ; указатель на блок памяти NUMB DWORD ? BUF DB 10 DUP (0) _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить HANDLE вывода PUSH STD_OUTPUT_HANDLE CALL GetStdHandle@4 MOV HANDL,EAX ; получить количество параметров CALL NUMPAR CMP EAX,2 JB _EXIT ;------------------------------------------------- ; получить параметр номером EDI MOV EDI,2 LEA EBX,BUF CALL GETPAR ; теперь работаем с файлом ; открыть только для чтения PUSH 0 PUSH 0 PUSH OPEN_EXISTING PUSH 0 PUSH 0 PUSH GENERIC_READ PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX,-1 JE _EXIT ; запомнить дескриптор файла MOV HF,EAX ; определить размер файла PUSH OFFSET SIZEH PUSH EAX CALL GetFileSize@8 ; запомнить размер, предполагаем, что размер не превосходит 4 Гб MOV SIZEL,EAX ; запросить память для считывания туда файла PUSH EAX PUSH 0 CALL GlobalAlloc@8 CMP EAX,0 JE _CLOSE ; запомнить адрес выделенного блока MOV GH,EAX ; читать файл в выделенную память PUSH 0 PUSH OFFSET NUMB PUSH SIZEL PUSH GH PUSH HF CALL ReadFile@20 CMP EAX,0 JE _FREE ; вывести прочитанное PUSH 0 PUSH OFFSET LENS PUSH SIZEL PUSH GH PUSH HANDL CALL WriteConsoleA@20 _FREE: ; освободить память PUSH GH CALL GlobalFree@4 ; закрыть файлы _CLOSE: PUSH HF CALL CloseHandle@4 _EXIT: ; конец работы программы PUSH 0 CALL ExitProcess@4 ;--------------------------------------------- ; область процедур ; процедура определения количества параметров в строке ; определить количество параметров (->EAX) NUMPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: INC ESI JMP @@L1 @@L4: MOV EAX,ECX RET NUMPAR ENDP ; получить параметр из командной строки ; EBX - указывает на буфер, куда будет помещен параметр ; в буфер помещается строка с нулем на конце ; EDI - номер параметра GETPAR PROC CALL GetCommandLineA@0 MOV ESI,EAX ; указатель на строку XOR ECX,ECX ; счетчик MOV EDX,1 ; признак @@L1: CMP BYTE PTR [ESI],0 JE @@L4 CMP BYTE PTR [ESI],32 JE @@L3 ADD ECX,EDX ; номер параметра MOV EDX,0 JMP @@L2 @@L3: OR EDX,1 @@L2: CMP ECX,EDI JNE @@L5 MOV AL,BYTE PTR [ESI] MOV BYTE PTR [EBX],AL INC EBX @@L5: INC ESI JMP @@L1 @@L4: MOV BYTE PTR [EBX],0 RET GETPAR ENDP _TEXT ENDS END START