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

Начнём с процедуры определения количества физической памяти, установленной в компьютере. К сожалению, мне не удалось найти документации по BIOS-у, в которой было бы описан доступ к переменным, хранящим размер памяти установленной в компьютере. Вообще говоря, вы можете получить dw-размер расширенной памяти по адресу 0040:0013h или прочитать его из CMOS, но это подразумевается память, размером не более 16Мб. Для i386 это, скорее всего, подходит, но для всех остальных компьютеров - нет.
Вашему вниманию предлагается процедура "get_phys_mem_size", которая сканирует всю физическую память и вычисляет её размер. Этот алгоритм имеет только одно ограничение - размер памяти не должен превышать 4Гб, т.к. мы пока рассматриваем 32-разрядную страничную организацию памяти.
Принцип работы данной процедуры простой - она записывает в память число, а затем считывает его. Если было прочитано записанное число - значит данная ячейка памяти присутствует в компьютере, если было прочитано другое число, то возможны 3 варианта:
  • Было обращение к ПЗУ (ROM).
  • Физическая память по данному адресу не присутствует.
  • Физическая память повреждена.
Третий вариант можно сразу отбросить, потому что наша программа будет работать после запуска компьютера, следовательно, POST уже будет выполнен и память признана пригодной для использования. Если же вы пишите программу для использования в составе BIOS, которая как раз и должна определить наличие и размер памяти, то наш пример будет недостаточен и вам обязательно понадобится получить от производителей техническую документацию по программированию чипсета и процессора, установленных на конкретной материнской плате.
Первый вариант, с обращением к ПЗУ также можно обойти, если начинать исследование памяти со второго мегабайта, т.к. ПЗУ размещено в конце первого мегабайта. При этом будет подразумеваться, что в компьютере установлен минимум - мегабайт памяти; это вполне разумное допущение, т.к. 32-разрядные процессоры обычно снабжают не меньшим количеством памяти. Если же вы пишете ОС, которая должна управлять контроллером или компьютером на базе 32-разрядного процессора с памятью, менее 1Мб, то приводимая в примере процедура вам не понадобится - используйте CMOS.
Итак, наша процедура определения размера физической памяти предназначена только для компьютеров и исправной памятью, размер которой - не менее 1Мб.
Для того, чтобы сканировать память не обязательно проверять каждый байт адресного пространства. Микросхемы памяти по объёму кратны, как правило, целому числу мегабайт, но даже если будут использоваться микросхемы меньшего размера, можно гарантировать, что они будут иметь объём, кратный 4Кб. Это значение взято не спроста - такой же размер имеет, как вы помните, физическая страница памяти и минимальная логическая страница (а мы пока только такие и рассматриваем), поэтому достаточно записать и затем прочитать одну ячейку памяти в каждой физической странице и проверить это значение.
При проверке считанного значения применяется "неразрушающий" контроль, который обеспечивается двумя командами XCHG:
1	xchg	[es:edi], eax	; Поменяли значениями регистр
2                            ; и ячейку памяти.
3
4	xchg	[es:edi], eax	; Вернули прежнее значение обратно
5							; в память. Теперь в регистре число,
6							; которое "побывало" в ячейке памяти.
7							; Если оно не изменилось - значит,
8							; запись была в ОЗУ.
Как правило, при считывании числа из неприсутствующей физической памяти, мы всегда получаем значение, одинаковое для каждого байта, например, FFh:
1	mov   edx, 12345678h
2	mov   eax, edx
3	xchg  [es:edi], eax		; EAX = FFFF FFFFh
4	xchg  [es:edi], eax		; EAX = FFFF FFFFh
5
6	; EAX <> EDX, значит, что ES:EDI указывают на не-ОЗУ
7    ; При обращении к ОЗУ мы получим то, что туда записали:
8
9	mov	  edx, 12345678h
10	mov	  eax, edx
11	xchg  [es:edi], eax		; EAX = FFFF FFFFh
12	xchg  [es:edi], eax		; EAX = 1234 5678h
13
14	; EAX = EDX
Далее приводится процедура "get_phys_mem_size" (tasm):
1get_phys_mem_size:
2
3; Вычисляет размер физической памяти, установленной в компьютере
4; Возврат: EAX = размер памяти в байтах
5
6	push	bx
7	push	ecx
8	push	edx
9	push	ebp
10	push	es
11	push	edi
12
13	mov	ax, All_mem_sel	;    Для проверки памяти мы будем
14	mov	es, ax			; использовать сегмент, описывающий
15					; всю память.
16
17	mov	bx, Page_Directory_adr	;    DS:BX указывает на начало каталога страниц.
18	add	bx, 1000h + 256 * 4	    ;    А теперь - на первую страницу
19					; второго мегабайта памяти в первой
20					; таблице страниц.
21
22	mov	edx,12345678h	; Это тестовое значение, которое
23					    ; мы будем записывать и затем
24					    ; считывать из памяти.
25
26	mov	ebp,1024 * 1024 * 2		;    В EBP будет находится адрес,
27						; с которого мы будем проверять
28						; память.
29gpms_1:
30
31;    Подготавливаем в первой определённой таблице страниц 256 элементов PTE,
32; начиная с 256. Они будут описывать второй мегабайт адресного пространства.
33; Эти PTE мы отобразим не один мегабайт физической памяти, начиная с адреса
34; в EBP.
35
36	mov	edi,1024 * 1024
37	mov	eax,ebp
38
39	mov	cx,256
40	mov	al,3	;    Записывая в AL 3, мы тем самым устанавливаем
41			; биты 0 и 1 этого регистра. Когда мы запишем EAX
42			; на место какого-либо элемента PTE в таблице
43			; страниц, то эти биты определят этот элемент как
44			; присутствующий (бит P=1) с разрешённым доступом
45			; по чтению записи (бит R/W=1).
46
47; Записываем 256 элементов PTE:
48gpms_2:
49	mov	[ bx ],eax
50	add	eax,1000h
51	add	bx,4
52	loop	gpms_2
53
54	mov	eax, cr3	;    Перегружаем значение CR3. Это вызовет
55	mov	cr3, eax	; сброс буферов TLB и заставит процессор
56				; использовать обновлённые значения из
57				; изменённой нами таблицы страниц.
58
59	jmp	$+2	;    Выполняя команду перехода, мы сбрасываем
60			; конвейер команд процессора, заставляя его заново
61			; считывать текущую часть кода и очистить результаты
62			; всех спекулятивно выполненных команд. Для нас это
63			; гарантия того, что процессор будет работать с
64			; обновлённой таблицей страниц.
65
66	sub	bx, 256 * 4	;    Возвращаем указатель на 256-й элемент
67				; PTE в таблице страниц.
68
69	mov	cx, 256
70
71; Тестируем память:
72gpms_3:
73
74	mov	 eax, edx			; EAX = 12345678h.
75	xchg eax, [ es:edi ]		;    Пара команд XCHG выполнит "не
76	xchg eax, [ es:edi ]		; разрушающий" контроль памяти.
77	add	 edi, 1000h
78
79	cmp	eax,edx	;    Если было прочитано то же значение, что
80				; и записано, значит текущая страница памяти
81				; отображена на ОЗУ.
82
83	jne	gpms_4		; Иначе - мы дошли до конца ОЗУ.
84
85	loop gpms_3
86
87	add	ebp, 1000h * 256	;    Здесь меняется адрес физической
88					; памяти, на который будет отображён
89					; следующий мегабайт.
90	jmp	gpms_1
91
92gpms_4:
93
94	mov	eax,ebp
95	add	eax,edi
96	sub	eax,1000h * 257	;    EAX содержит адрес последней
97					; реально присутствующей физической
98					; страницы памяти, это и есть размер
99					; физической памяти.
100
101;    Очистка элементов PTE, отображённых на второй мегабайт (т.е. возврат
102; таблицы страниц в первоначальное состояние.
103
104	mov	edx,eax
105
106	mov	ax,ds
107	mov	es,ax
108	mov	di,Page_Directory_adr
109	add	di,256 * 4
110	mov	cx,256
111	xor	eax,eax
112	cld
113	rep	stosd
114
115	mov	eax,edx		; EAX = размер физической памяти в байтах.
116
117	pop	edi
118	pop	es
119	pop	ebp
120	pop	edx
121	pop	ecx
122	pop	bx
123
124	ret