Сведения из теории
Определение типа ПЭВМ
В конце ПЗУ BIOS по адресу FF00:0FFE записан байт типа ПЭВМ. Специфицированы следующие 4 значения этого байта для машин фирмы IBM: 0xFF - IBM PC; 0xFE - XT; 0xFD - PCjr; 0xFC - AT. Для ПЭВМ других производителей стопроцентной гарантии дать нельзя.
адрес FF00:0FFE
-
Байт
Тип ПЭВМ
FF
PC
FE
PC XT
FD
PCjr
FC
PC AT
FA
PS/2 модель 30
F8
PS/2 модель 80
00
ЕС 1840
Фрагмент программы, определяющей тип ПЭВМ
unsigned char pc;
char *PT[]= { "AT", "PCjr", "XT", "IBM PC", "???" };
printf("Тип ПЭВМ = %Xh - ",pc=peekb(0xf000,0xfffe));
if ((pc-=0xfc)>4) pc=4;
printf("%s\n",PT[pc]);
Определение даты издания BIOS
8 байт ПЗУ начиная с адреса 0xFF00:0x0FF5, содержат дату выпуска данной версии BIOS в символьном виде в формате мм/дд/гг, например: 01/30/03.
Фрагмент программы определения даты BIOS:
unsigned int t;
printf("Дата издания BIOS = ");
for (t=0xfff5;t<0xfffd;t++)
printf("%c",peekb(0xf000,t));
printf("\n");
или
char *BIOSData;
printf("Дата издания BIOS: ");
BIOSData = (char *) MK_FP(0xFF00,0x0FF5);
for (i=0;i<=7;i++) {
printf("%c",BIOSData[i]);
}
printf("\n");
Определение размера ОЗУ (основной памяти)
Для хранения различных сведений о конфигурации ПЭВМ и режимах её работы используется область ОЗУ с адресами от 0040:0000h до 0050:0000h, называемая BIOS Data Area.
Объем ОЗУ (в Кбайтах) находится в области памяти BIOS по адресу 0040:0013 (2-байтное слово) и может быть получен или непосредственным чтением по этому адресу или при помощи прерывания 0x12, как это показано в следующем примере. Для определения объема ОЗУ в С существует специальная функция biosmemory(), объявленная в модуле bios.h.
printf("Объем ОЗУ %d Кб\n",peek(0x40,0x13));
union REGS rr;
int86(0x12,&rr,&rr);
printf("Объем ОЗУ: %d Кбайт\n",rr.x.ax);
printf("Объем ОЗУ: %d Кбайт\n",biosmemory());
Для ПЭВМ типа АТ (которые имеют энергонезависимую CMOS-память) объем ОЗУ может быть также прочитан из регистров 0x15 (младший байт) и 0x16 (старший байт) CMOS-памяти.
CMOS-память с точки зрения системного программиста представляет собой 64 однобайтовых ячеек памяти с мультиплексированной шиной адреса/данных и доступных для чтения/записи по адресам 70h (адрес ячейки) и 71h (данные ячейки). Для чтения их необходимо выдать в порт 0x70 байт номера регистра, а затем из порта 0x71 прочитать байт содержимого этого регистра.
printf(" Объем основной памяти (по данным CMOS) - ");
outportb(0x70,0x15);
for (i=1;i<=4000;i++);
f=inportb(0x71);
outportb(0x70,0x16);
for (i=1;i<=4000;i++);
f1=inportb(0x71);
printf("%d Кбайт\n",(f1<<8)|f);
printf("\n");
Кроме того, в AT может быть еще и расширенная (extended) память сверх 1 Мбайта (в AT она используется только для виртуальных дисков). Ее объем можно получить из регистров 0x17 (младший байт) и 0x18 (старший байт), или из регистров 0x30 (младший байт) и 0x31 (старший байт) или по функции 0x88 прерывания 0x15. К сожалению, содержимое этих регистров специфицировано для объемов дополнительной памяти до 15 Мб и на ПЭВМ, которые имеют больший объем ОЗУ выдают неверные результаты.
printf(" Объем extended памяти - ");
outportb(0x70,0x17);
for (i=1;i<=4000;i++);
f=inportb(0x71);
outportb(0x70,0x18);
for (i=1;i<=4000;i++);
f1=inportb(0x71);
printf("%u Кбайт\n",(f1<<8)|f);
Для выполнения этой же операции используется функции 0x88 прерывания 0 x15:
printf("Объем extended памяти: ");
rr.h.ah=0x88;
int86(0x15,&rr,&rr);
printf("%d Кбайт\n",rr.x.ax);
Определение состава оборудования
Определять состав оборудования следует только после того, как мы определили тип ПЭВМ. Это обусловлено тем, что способы получения информации о составе оборудования различны для различных типов ПЭВМ (например, XT и AT).
Наиболее важные элементы конфигурации ПЭВМ определяются процедурами POST при включении питания ПЭВМ. Результаты тестирования заносятся в область памяти BIOS. На основании этой информации BIOS формирует так называемый список оборудования - 2-байтное слово по адресу 0040:0010. Прочитать это слово можно либо обратившись по указанному адресу, либо обратившись к BIOS через прерывание 0x11. Назначения разрядов списка оборудования следующие:
-
Бит
Значение
0
- установлен в 1, если есть НГМД (см. разряды 6, 7);
1
- установлен в 1, если есть сопроцессор;
2,3
- число 16-Кбайтных блоков ОЗУ на системной плате, для PS/2 бит 2=1, если при POST обнаружен манипулятор «мышь»;
4,5
- код видеоадаптера: 11 - монохромный, 10 – цветной, 80 колонок, 01 - цветной, 40 колонок, 00 - другой;
6,7
- число НГМД (если бит 0 равен 1);
8
- 0, если установлен канал ПДП (для ХТ и АТ – всегда 0);
9,10,11
- число последовательных портов RS-232;
12
- 1, если установлен джойстик;
13
- 1, если установлен последовательный принтер только для PCjr (для ХТ и АТ – всегда 0);
14,15
- число параллельных принтеров.
Фрагмент программы, реализующей чтение слова состава оборудования:
// Чтение слова состава обоpудования из памяти BIOS
d=peek(0x40,0x10);
printf("Список оборудования");
for (i=15; i>=0; printf("%d",(d>>i--)&0x01));
printf(" (%04Xh)\n",d);
Далее необходимо расшифровать полученный код:
Определения наличия, количества и типа НГМД
Для получения дополнительной информации программа может обратиться к CMOS-памяти. С точки зрения определения конфигурации НГМД представляют интерес регистр с номером 10h CMOS-памяти. Младший полубайт этого регистра содержит информацию о НГМД А, старший – о НГМД В
-
Код
Значение
Младший полубайт (НГМД А)
0000
нет НГМД
0001
360 К
0010
1,2 Мб
0011
резерв
0100
1,44 Мб
0101 – 1111
зарезервировано
Старший полубайт (НГМД В)
0000
нет НГМД
0001
360 К
0010
1,2 Мб
0011
резерв
0100
1,44 Мб
0101 – 1111
зарезервировано
// "Раскодирование" списка обоpудования
printf(" Дисководов ГМД - ");
if (d&0x0001) {
printf("%d\n",FDDNum=((d&0x00c0)>>6)+1);
// определение типа дисковода */
outportb(0x70,0x10);
for (i=1;i<=4000;i++);
FDDType = inportb(0x71);
for (i=1;i<=FDDNum;i++) {
printf(" ГМД дисковод %d: тип ",i);
switch ((FDDType>>(i*4))&0x0F) {
case 0: printf("нет\n"); break;
case 1: printf("360 Кб"); break;
case 2: printf("1,2 Мб\n"); break;
case 3: printf("резерв"); break;
case 4: printf("1,44 Мб\n"); break;
default: printf("неизвестный тип (резерв)\n"); break;
}
}
}
Следует отметить, что CMOS-память содержит также данные о НЖМД (регистр 12h) - типы жестких дисков младший полубайт - для первого диска, старший - для второго; 0 в соответствующем полубайте означает, что этого жесткого диска нет; числа 1-14 - коды типов, число 15 означает, что тип жесткого диска записан в регистре 0x19 (для первого диска) или 0x1A (для второго). К сожалению, содержимое этих регистров специфицировано для НЖМД объемом до 512 Мб и не может быть использовано для современных ПЭВМ.
Определение наличия математического сопроцессора
Для этой операции необходимо проанализировать бит 1 слова состава оборудования. Дополнительно в ЯВУ С можно определить тип математического сопроцессора с использованием переменной _8087.
printf(" Сопроцессор x87 - ");
if (d&0x0002) {
printf("есть (");
// Определение типа сопроцессора
switch (_8087) {
case 1: printf("8087)\n"); break;
case 2: printf("80287)\n"); break;
case 3: printf("80387 или более современный)\n"); break;
}
}
else printf("нет\n");
Определения количества 16К модулей ОЗУ на системной плате (актуально для ХТ)
printf(" Количество 16К модулей ОЗУ на системной плате - ");
switch (d&0x000c) {
case 0: printf("0 \n"); break;
case 4: printf("1 \n"); break;
case 8: printf("2 \n"); break;
case 12: printf("3 или больше\n"); break;
}
Текущий режим адаптера дисплея
printf(" Текущий режим дисплейного адаптера - ");
switch (d&0x0030) {
case 0: printf("EGA/VGA"); break;
case 0x10: printf("40x25 - цветной"); break;
case 0x20: printf("80х25 - цветной"); break;
case 0x30: printf("MDA - монохромный"); break;
}
Количество установленных последовательных портов
Определяется аналогичным образом:
printf("\n Портов RS232 - %d\n",(d&0x0e00)>>9);
Базовые адреса установленных последовательных портов заносятся POST в область данных BIOS по адресам:
-
Адрес
Данные
0040:0000
Базовый адрес СОМ1
0040:0002
Базовый адрес СОМ2
0040:0004
Базовый адрес СОМ3
0040:0006
Базовый адрес СОМ4
Следует отметить, что хотя слово состояния может показывать до 7 установленных последовательных портов реально с использованием функций BIOS и MS-DOS можно работать с 4 СОМ портами.
Определение наличия манипулятора «джойстик»
Наличие манипулятора «джойстик» определяется аналогичным образом:
printf(" Джойстик - ");
if (d&0x1000) printf("есть\n");
else printf("нет\n");
При наличии манипулятора «джойстик» за ним закреплены адреса в диапазоне 200-20Fh (как правило, 201h)
Определение количества адаптеров параллельного порта
Аналогично:
printf(" Принтеров - %d\n",(d&0xe000)>>14);
Базовые адреса установленных адаптеров параллельного порта заносятся POST в область данных BIOS по адресам:
-
Адрес
Данные
0040:0008
Базовый адрес LPT1
0040:000A
Базовый адрес LPT2
0040:000C
Базовый адрес LPT3
0040:000E
Базовый адрес LPT4
Следует отметить, что POST определяет не более 3 адаптеров параллельного порта. Если с использованием функций BIOS и MS-DOS требуется использовать установленный LPT4, необходимо по адресу 0040:000E записать его базовый адрес.
Если необходимо получать информацию по всему списку оборудования, удобнее использовать такие структуры данных ЯВУ С как структуры с битовыми полями и объединения:
union EqListUnion {
unsigned EqWord;
struct EqListStruct {
unsigned FDD : 1; // наличие FDD
unsigned FPU : 1; // наличие FPU
unsigned MEM : 2; // и т.д.
unsigned Video : 2; //
unsigned FDDNum : 2; //
unsigned DMA : 1; //
unsigned RS232Num : 3; //
unsigned Joystick : 1; //
unsigned SerialPrn : 1; //
unsigned ParNum : 2; //
} EqList;
} Eq;
Тогда заполнить поля такой структуры можно одним оператором присваивания элемента EqWord:
// Чтение состава обоpудования через прерывание 0x11
int86(0x11,&rr,&rr);
Eq.EqWord = rr.x.ax;
А получать доступ к отдельным битам и их группам можно с использованием элемента EqList:
printf("НГМД (1-есть, 0-нет) %d\n",Eq.EqList.FDD);
printf("Сопроцессор (1-есть, 0-нет) %d\n",Eq.EqList.FPU);
printf("Количество 16К ОЗУ на плате %d\n",Eq.EqList.MEM);
Пример:
/*============= Получение списка оборудования ============*/
#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <bios.h>
main()
{
union REGS rr;
unsigned int d; /* список оборудования */
int i;
char NumOfBits = 16;
char mBits = 0;
int j;
union EqListUnion {
unsigned EqWord;
struct EqListStruct {
unsigned FDD : 1; // наличие FDD
unsigned FPU : 1; // наличие FPU
unsigned MEM : 2; //
unsigned Video : 2; //
unsigned FDDNum : 2; //
unsigned DMA : 1; //
unsigned RS232Num : 3; //
unsigned Joystick : 1; //
unsigned SerialPrn : 1; //
unsigned ParNum : 2; //
} EqList;
} Eq;
unsigned char pc; // Код типа PC
unsigned int t; // Текущее смещение для определения даты BIOS
int FDDNum;
unsigned char FDDType;
char *BIOSData;
clrscr();
char *PT[]= { "AT", "PCjr", "XT", "IBM PC", "???" };
printf("Тип ПЭВМ = %Xh - ",pc=peekb(0xf000,0xfffe));
if ((pc-=0xfc)>4) pc=4;
printf("%s\n",PT[pc]);
printf("Дата издания BIOS = ");
for (t=0xfff5;t<0xfffd;t++) printf("%c",peekb(0xf000,t));
printf("\n");
// Чтение байта обоpудования из памяти BIOS
// Будет получено то же самое
d=peek(0x40,0x10);
printf("Список активного оборудования из памяти BIOS - ");
for (i=15; i>=0; printf("%d",(d>>i--)&0x01));
printf(" (%04Xh)\n",d);
// "Раскодирование" списка обоpудования
printf(" Дисководов ГМД - ");
if (d&0x0001) {
printf("%d\n",FDDNum=((d&0x00c0)>>6)+1);
// определение типа дисковода */
outportb(0x70,0x10);
for (i=1;i<=4000;i++);
FDDType = inportb(0x71);
for (i=1;i<=FDDNum;i++) {
printf(" ГМД дисковод %d: тип ",i);
switch ((FDDType>>(i*4))&0x0F) {
case 0: printf("нет\n"); break;
case 1: printf("360 Кб"); break;
case 2: printf("1,2 Мб\n"); break;
case 3: printf("резерв"); break;
case 4: printf("1,44 Мб\n"); break;
default: printf("неизвестный тип (резерв)\n"); break;
}
}
}
else printf("нет\n");
printf(" Сопроцессор x87 - ");
if (d&0x0002) {
printf("есть (");
// Определение типа сопроцессора
switch (_8087) {
case 1: printf("8087)\n"); break;
case 2: printf("80287)\n"); break;
case 3: printf("80387 или более современный)\n"); break;
}
}
else printf("нет\n");
printf(" Количество 16К модулей ОЗУ на системной плате - ");
switch (d&0x000c) {
case 0: printf("0 \n"); break;
case 4: printf("1 \n"); break;
case 8: printf("2 \n"); break;
case 12: printf("3 илибольше\n"); break;
}
printf(" Объем ОЗУ (по данным BIOS) - %d Кбайт\n",peek(0x40,0x13));
unsigned char f, f1;
printf(" Объем основной памяти (по данным CMOS) - ");
outportb(0x70,0x15);
for (i=1;i<=4000;i++);
f=inportb(0x71);
outportb(0x70,0x16);
for (i=1;i<=4000;i++);
f1=inportb(0x71);
printf("%d Кбайт\n",(f1<<8)|f);
printf("\n");
printf(" Объем extended памяти - ");
outportb(0x70,0x17);
for (i=1;i<=4000;i++);
f=inportb(0x71);
outportb(0x70,0x18);
for (i=1;i<=4000;i++);
f1=inportb(0x71);
printf("%u Кбайт\n",(f1<<8)|f);
printf(" Текущий режим дисплейного адаптера - ");
switch (d&0x0030) {
case 0: printf("EGA/VGA"); break;
case 0x10: printf("40x25 - цветной"); break;
case 0x20: printf("80х25 - цветной"); break;
case 0x30: printf("MDA - монохромный"); break;
}
printf("\n Портов RS232 - %d\n",(d&0x0e00)>>9);
printf(" Джойстик - ");
if (d&0x1000) printf("есть\n");
else printf("нет\n");
printf(" Принтеров - %d\n",(d&0xe000)>>14);
printf("Для продолжения нажмите любую клавишу...");
getch();
clrscr();
printf("Дата издания BIOS: ");
BIOSData = (char *) MK_FP(0xFF00,0x0FF5);
for (i=0;i<=7;i++) {
printf("%c",BIOSData[i]);
}
printf("\n");
// Чтение состава обоpудования через прерывание 0x11
int86(0x11,&rr,&rr);
Eq.EqWord = rr.x.ax;
// Побитная распечатка слова оборудования
printf("Слово списка оборудования, полученного из прерывания 11h:\n");
for (i=NumOfBits-1; i>=0; i--) {
printf("%d",(rr.x.ax>>i)&0x01);
}
printf("b (%04Xh)",Eq.EqWord);
printf("\n|||||||||||||||+-- НГМД (1-есть, 0-нет) %d",Eq.EqList.FDD);
printf("\n||||||||||||||+--- Сопроцессор (1-есть, 0-нет) %d",Eq.EqList.FPU);
printf("\n||||||||||||\\/");
printf("\n|||||||||||| +---- Количество 16К ОЗУ на плате %d",Eq.EqList.MEM);
printf("\n||||||||||\\/");
printf("\n|||||||||| +------ Код видеоадаптера %d",Eq.EqList.Video);
printf("\n||||||||\\/");
printf("\n|||||||| +-------- Количество НГМД (00=1, 01=2, 10=3, 11=4) %d",Eq.EqList.FDDNum);
printf("\n|||||||+---------- Наличие оборудования КПДП (0 - есть) %d",Eq.EqList.DMA);
printf("\n||||\\ /");
printf("\n|||| +------------ Количество последовательных портов %d",Eq.EqList.RS232Num);
printf("\n|||+-------------- Наличие джойстика %d",Eq.EqList.Joystick);
printf("\n||+--------------- Наличие последовательного принтера (PCjr) %d",Eq.EqList.SerialPrn);
printf("\n\\/");
printf("\n +---------------- Количество адаптеров параллельного порта %d",Eq.EqList.ParNum);
int86(0x12,&rr,&rr);
printf("\nОбъем памяти по данным прерывания 12h: %d Кбайт\n",rr.x.ax);
printf("Объем памяти по данным biosmemory: %d Кбайт\n",biosmemory());
printf("Объем extended памяти: ");
rr.h.ah=0x88;
int86(0x15,&rr,&rr);
printf("%d Кбайт\n",rr.x.ax);
rr.h.ah=0x30;
intdos(&rr,&rr);
printf("Версия MS-DOS %d.%d\n",rr.h.al,rr.h.ah);
getch();
}
Содержание отчета
-
Титульный лист.
-
Текст программ(ы).
-
Дампы областей памяти, содержащих информацию о составе оборудования.
-
Выводы (преимущества и недостатки различных способов определения состава оборудования, способов доступа к ячейкам памяти и портам ввода-вывода, области эффективного применения способов и т.п.).
Контрольные вопросы
-
Для чего необходимо определять состав оборудования ПЭВМ?
-
Какие параметры входят в слово состава оборудования ПЭВМ и почему?
-
Чем отличается порт ввода-вывода от ячейки ОЗУ?
-
В чем отличие логических и поразрядных логических операций?
-
Что такое маска?
-
Для чего могут быть использованы операции сдвига >> и <<?
-
Для чего используются структуры?
-
В чем отличие структур и объединений?
-
Для чего используются объединения?
-
Чем отличается структура от структуры с битовыми полями? В чем их преимущество?
-
Какой размер могут иметь структуры с битовыми полями?
-
Что определяет класс памяти переменной?
-
В чем отличие использования псевдорегистров (AX_,BX_ и др.) и объединения REGS?
-
Почему средствами языка С невозможно модифицировать содержимое регистра ВР?
-
Почему в языке С реализовано несколько функций генерации программных прерываний?
-
Можно ли использовать функции доступа к портам ввода-вывода для доступа к оперативной памяти? (объяснить почему).
-
Какими способами можно осуществить доступ к ячейке оперативной памяти в языке С?
-
Чем отличаются указатели near, far и huge?
-
В чем состоят особенности логических и арифметических операций с указателями в языке С?
-
Почему возникает необходимость использования динамических переменных?
-
Где выделяется память под динамические переменные?
-
Как программно определить наличие установленных микросхем ОЗУ по заданным адресам?
-
Какой диапазон адресов в адресном пространстве ПЭВМ занимает КМОП память и каким образом осуществляется доступ к её ячейкам?
-
В каких случаях имеет смысл использовать ассемблерные вставки в программу на языке С?
-
В каких случаях имеет смысл применять раздельную компиляцию модулей на языке С и Ассемблера?
-
Каким требованиям в случае использования раздельной компиляции должны удовлетворять программы на языке Ассемблера?
-
Что такое макроопределение в языке С? В чем его отличие от функции?
-
Что означает понятие «базовый адрес»?