Воскресенье, 20.07.2025, 01:17 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 6. Некоторые вопросы системного программирования в Windows(2)

Средние 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



Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0