Доброго времени суток, уважаемые подписчики. В этом выпуске мы рассмотрим работу блочного менеджера памяти на основе менеджера памяти нашего проекта
Как уже упоминалось, наш менеджер памяти будет оперировать блоками памяти. Это имеет один большой минус - даже самый маленький блок памяти будет занимать 128 байт. Но имеет следующие плюсы:
- Меньшая фрагментированность памяти.
- Упрощённая работа с памятью (не нужды в куче переменных для работы менеджера)
Далее, менеджер памяти будет находиться в отдельной библиотеке, asmGLmem.dll. Вот её код:
include kernel32.inc
include def32.inc
include macros.inc
include objects.inc
.386
.model flat
option casemap:none
public _asmGLmem_AddObject@8
public _asmGLmem_GetChainEnd@4
_TEXT SEGMENT public USE32 'code'
_start@12:
push ebx ;сохраняем ebx
mov eax,[esp+0Ch];получаем в eax второй параметр функции, причину вызова
push esi ;сохраняем esi
null ebx ;ebx = 0
dec eax
js detach_process;если (eax+1) <= 0 то библиотека выгружается
jnz end_start ;если (eax+1) != 1, то библиотека и не загружается и не выгружается
attach_process: ;в оставшемся слечае (eax+1)=1, библиотека загружается
mov esi,128*1024 ;esi = размер логической страницы менеджера памяти (128 Кб)
run HeapCreate, ebx, esi, ebx
test eax,eax ;Если не получилось создать "кучу", то выход с ошибкой
jz error_start
mov hHeap,eax ;Сохраняем handle "кучи"
run HeapAlloc, eax, HEAP_ZERO_MEMORY, esi
test eax,eax ;Создаём первую страницу, если не получилось - выходим с ошибкой
jz error_start
mov MainBlock,eax;Заполняем переменные менеджера памяти
mov CurPage,eax
pusha
shr esi,7
dec esi
mov FreeItems,esi
inc PageCount
popa
lea eax,[eax+esi-128];Получаем в eax адрес предпоследнего блока в странице памяти
mov dword ptr [eax],GL_PAGEADDRESS;Резервируем его для нужд менеджера
jmp short end_start
detach_process: ;Если библиотека выгружается
run HeapDestroy, hHeap
end_start: ;Нормальное завершение функции
pop esi ;Восстанавливаем esi
null eax ;eax = 0
pop ebx ;Восстанавливаем ebx
inc eax ;eax = 0+1 = 1
ret 12 ;Возвращаем eax = 1
error_start: ;Завершение функции с ошибкой
pop esi ;Восстанавливаем esi
null eax ;eax = 0
pop ebx ;Восстанавливаем ebx
ret 12 ;Возвращаем eax = 0
_asmGLmem_AddObject@8 proc
push ebp ;создаём стековый кадр
mov ebp,esp
push esi ;сохраняем esi
mov esi,CurPage
mov eax,FreeItems
shl eax,10
add eax,-128
add esi,eax ;Генерируем адрес свободной ячейки памяти
run CopyDwords, esi, dword ptr [ebp+08h], 32
run _AsmGLmem_GetChainEnd@4, dword ptr [ebp+0Ch]
assume esi:PTR OBJECT_POINT
assume eax:PTR OBJECT_OBJECT
mov [esi]._last, eax
test eax,eax ;Если нет объекта-родителя то завершаем функцию
jz not_object__
cmp [eax]._type, GL_OBJECT
je object__ ;Если есть объект-родитель то заносим адрес дочерного объекта в поле _child
mov [eax]._next,esi;Если объект часть цепочки, то в поле _next
jmp short not_object__
object__:
mov [eax]._child,esi
not_object__:
assume esi:nothing
assume eax:nothing
dec FreeItems
jnz added_ok ;Если ещё осталить пустые ячейки, то закончить
push esi
mov esi,128*1024
run HeapAlloc, hHeap, HEAP_ZERO_MEMORY, esi
test eax,eax
jnz allocated_ok
... ;Если не хватает памяти, то надо будет что-то придумать
allocated_ok:
mov ecx,CurPage
lea ecx,[ecx+esi-128]
mov [ecx+18h],eax
mov CurPage,eax
shr esi,7
dec esi
mov FreeItems,esi
inc PageCount
pop esi
added_ok:
mov eax,esi ;eax = esi = адрес добавленого блока
pop esi ;Восстанавливаем esi
leave ;Уничтожаем стековый кадр
ret 8
_asmGLmem_AddObject@8 endp
_asmGLmem_GetChainEnd@4 proc
mov eax,[esp+04h]
assume eax:PTR OBJECT_OBJECT
test eax,eax
jz found__
cmp [eax]._type, GL_OBJECT
je object__
next__:
cmp [eax]._next, 0
jz found__
mov eax,[eax]._next
jmp short next__
object__:
cmp [eax]._child,0
jz found__
mov eax,[eax]._child
jmp short next__
assume eax:nothing
found__:
ret 4
_asmGLmem_GetChainEnd@4 endp
;Такие процедуры, как CopyDwords
...
end _start@12
_TEXT ENDS
Как вы могли заметить, пока объявлены лишь 3 процедуры, одна из которых запускается при загрузке/выгрузке библиотеки. При загрузке библиотеки процедура создаёт "кучу" памяти и резервирует первую страницу (здесь я имею ввиду логический кусок памяти, который наш менеджер выделяет за раз = 128 килобайт, а не логическую страницу страничного режима работы процессора = 4 килобайта). При выгрузке она просто уничтожает "кучу". Функция GetChainEnd возвращает адрес последнего объекта в цепочке. Это нужно, например, когда есть объект, к которому добавляется один полигон. Так как полигоны объекта образуют цепочку, адрес которой находится в поле "child" объекта, то новый полигон следует добавлять не к самому объекту, а в конец цепочки. Функция AddObject осуществляет следующие действия: Копирует логический блок памяти в память менеджера, согласует адреса объекта родителя, цепочки полигонов и самого блока памяти, и резервирует новою страницу памяти, если текущая заполнилась.
Возможно, у Вас возник вопрос, почему нет функций удаления блоков памяти. Ответ прост, фрагментация памяти происходит именно тогда, когда удаляются блоки памяти, поэтому вместе с функциями удаления блоков памяти нежно писать функции дефрагментации, именно этим мы и займёмся в следующем выпуске рассылки.