§ Определение количества физической памяти

Начнём с процедуры определения количества физической памяти, установленной в компьютере. К сожалению, мне не удалось найти документации по BIOS-у, в которой было бы описан доступ к переменным, хранящим размер памяти установленной в компьютере. Вообще говоря, вы можете получить dw-размер расширенной памяти по адресу 0040:0013h или прочитать его из CMOS, но это подразумевается память, размером не более 16Мб. Для i386 это, скорее всего, подходит, но для всех остальных компьютеров - нет.
Вашему вниманию предлагается процедура "get_phys_mem_size", которая сканирует всю физическую память и вычисляет её размер. Этот алгоритм имеет только одно ограничение - размер памяти не должен превышать 4Гб, т.к. мы пока рассматриваем 32-разрядную страничную организацию памяти.
Принцип работы данной процедуры простой - она записывает в память число, а затем считывает его. Если было прочитано записанное число - значит данная ячейка памяти присутствует в компьютере, если было прочитано другое число, то возможны 3 варианта:
  • Было обращение к ПЗУ (ROM).
  • Физическая память по данному адресу не присутствует.
  • Физическая память повреждена.
Третий вариант можно сразу отбросить, потому что наша программа будет работать после запуска компьютера, следовательно, POST уже будет выполнен и память признана пригодной для использования. Если же вы пишите программу для использования в составе BIOS, которая как раз и должна определить наличие и размер памяти, то наш пример будет недостаточен и вам обязательно понадобится получить от производителей техническую документацию по программированию чипсета и процессора, установленных на конкретной материнской плате.
Первый вариант, с обращением к ПЗУ также можно обойти, если начинать исследование памяти со второго мегабайта, т.к. ПЗУ размещено в конце первого мегабайта. При этом будет подразумеваться, что в компьютере установлен минимум - мегабайт памяти; это вполне разумное допущение, т.к. 32-разрядные процессоры обычно снабжают не меньшим количеством памяти. Если же вы пишете ОС, которая должна управлять контроллером или компьютером на базе 32-разрядного процессора с памятью, менее 1Мб, то приводимая в примере процедура вам не понадобится - используйте CMOS.
Итак, наша процедура определения размера физической памяти предназначена только для компьютеров и исправной памятью, размер которой - не менее 1Мб.
Для того, чтобы сканировать память не обязательно проверять каждый байт адресного пространства. Микросхемы памяти по объёму кратны, как правило, целому числу мегабайт, но даже если будут использоваться микросхемы меньшего размера, можно гарантировать, что они будут иметь объём, кратный 4Кб. Это значение взято не спроста - такой же размер имеет, как вы помните, физическая страница памяти и минимальная логическая страница (а мы пока только такие и рассматриваем), поэтому достаточно записать и затем прочитать одну ячейку памяти в каждой физической странице и проверить это значение.
При проверке считанного значения применяется "неразрушающий" контроль, который обеспечивается двумя командами XCHG:
	xchg	[es:edi], eax	; Поменяли значениями регистр
                            ; и ячейку памяти.

	xchg	[es:edi], eax	; Вернули прежнее значение обратно
							; в память. Теперь в регистре число,
							; которое "побывало" в ячейке памяти.
							; Если оно не изменилось - значит,
							; запись была в ОЗУ.
Как правило, при считывании числа из неприсутствующей физической памяти, мы всегда получаем значение, одинаковое для каждого байта, например, FFh:
	mov   edx, 12345678h
	mov   eax, edx
	xchg  [es:edi], eax		; EAX = FFFF FFFFh
	xchg  [es:edi], eax		; EAX = FFFF FFFFh

	; EAX <> EDX, значит, что ES:EDI указывают на не-ОЗУ
    ; При обращении к ОЗУ мы получим то, что туда записали:

	mov	  edx, 12345678h
	mov	  eax, edx
	xchg  [es:edi], eax		; EAX = FFFF FFFFh
	xchg  [es:edi], eax		; EAX = 1234 5678h

	; EAX = EDX
Далее приводится процедура "get_phys_mem_size" (tasm):
get_phys_mem_size:

; Вычисляет размер физической памяти, установленной в компьютере
; Возврат: EAX = размер памяти в байтах

	push	bx
	push	ecx
	push	edx
	push	ebp
	push	es
	push	edi

	mov	ax, All_mem_sel	;    Для проверки памяти мы будем
	mov	es, ax			; использовать сегмент, описывающий
					; всю память.

	mov	bx, Page_Directory_adr	;    DS:BX указывает на начало каталога страниц.
	add	bx, 1000h + 256 * 4	    ;    А теперь - на первую страницу
					; второго мегабайта памяти в первой
					; таблице страниц.

	mov	edx,12345678h	; Это тестовое значение, которое
					    ; мы будем записывать и затем
					    ; считывать из памяти.

	mov	ebp,1024 * 1024 * 2		;    В EBP будет находится адрес,
						; с которого мы будем проверять
						; память.
gpms_1:

;    Подготавливаем в первой определённой таблице страниц 256 элементов PTE,
; начиная с 256. Они будут описывать второй мегабайт адресного пространства.
; Эти PTE мы отобразим не один мегабайт физической памяти, начиная с адреса
; в EBP.

	mov	edi,1024 * 1024
	mov	eax,ebp

	mov	cx,256
	mov	al,3	;    Записывая в AL 3, мы тем самым устанавливаем
			; биты 0 и 1 этого регистра. Когда мы запишем EAX
			; на место какого-либо элемента PTE в таблице
			; страниц, то эти биты определят этот элемент как
			; присутствующий (бит P=1) с разрешённым доступом
			; по чтению записи (бит R/W=1).

; Записываем 256 элементов PTE:
gpms_2:
	mov	[ bx ],eax
	add	eax,1000h
	add	bx,4
	loop	gpms_2

	mov	eax, cr3	;    Перегружаем значение CR3. Это вызовет
	mov	cr3, eax	; сброс буферов TLB и заставит процессор
				; использовать обновлённые значения из
				; изменённой нами таблицы страниц.

	jmp	$+2	;    Выполняя команду перехода, мы сбрасываем
			; конвейер команд процессора, заставляя его заново
			; считывать текущую часть кода и очистить результаты
			; всех спекулятивно выполненных команд. Для нас это
			; гарантия того, что процессор будет работать с
			; обновлённой таблицей страниц.

	sub	bx, 256 * 4	;    Возвращаем указатель на 256-й элемент
				; PTE в таблице страниц.

	mov	cx, 256

; Тестируем память:
gpms_3:

	mov	 eax, edx			; EAX = 12345678h.
	xchg eax, [ es:edi ]		;    Пара команд XCHG выполнит "не
	xchg eax, [ es:edi ]		; разрушающий" контроль памяти.
	add	 edi, 1000h

	cmp	eax,edx	;    Если было прочитано то же значение, что
				; и записано, значит текущая страница памяти
				; отображена на ОЗУ.

	jne	gpms_4		; Иначе - мы дошли до конца ОЗУ.

	loop gpms_3

	add	ebp, 1000h * 256	;    Здесь меняется адрес физической
					; памяти, на который будет отображён
					; следующий мегабайт.
	jmp	gpms_1

gpms_4:

	mov	eax,ebp
	add	eax,edi
	sub	eax,1000h * 257	;    EAX содержит адрес последней
					; реально присутствующей физической
					; страницы памяти, это и есть размер
					; физической памяти.

;    Очистка элементов PTE, отображённых на второй мегабайт (т.е. возврат
; таблицы страниц в первоначальное состояние.

	mov	edx,eax

	mov	ax,ds
	mov	es,ax
	mov	di,Page_Directory_adr
	add	di,256 * 4
	mov	cx,256
	xor	eax,eax
	cld
	rep	stosd

	mov	eax,edx		; EAX = размер физической памяти в байтах.

	pop	edi
	pop	es
	pop	ebp
	pop	edx
	pop	ecx
	pop	bx

	ret