1. Определение конфигурации компьютера

Если ваша программа работает с устройствами компьютера на низком уровне или использует какие-либо аппаратные особенности периферии, она должна «уметь» определять конфигурацию аппаратных средств. В настоящее время выпускается много различных моделей персональных компьютеров и серверных платформ с процессором Intel, совместимых или не очень с оригинальным компьютером IBM PC/AT. В компьютере могут быть установлены процессоры различных моделей и различные версии BIOS. Что же касается номенклатуры периферийных устройств, таких как сетевые контроллеры, видеоадаптеры, сетевые и звуковые адаптеры, то она практически безгранична.

Для наиболее распространенных моделей персональных компьютеров конфигурация аппаратных средств задается установкой перемычек на системной плате (motherboard) и платах контроллеров периферийных устройств, а также записывается в область данных BIOS и в энергонезависимую память CMOS специальной программой BIOS Setup.

В следующем разделе мы рассмотрим средства BIOS, позволяющие определить конфигурацию компьютера. Об энергонезависимой памяти CMOS мы расскажем позже.

Определение конфигурации с помощью BIOS

Во время инициализации системы BIOS опрашивает порты, к которым подключены перемычки, и определяет содержимое ячеек памяти CMOS, содержащие информацию о конфигурации компьютера. Результат записывается в область данных BIOS, откуда программа может его извлечь с помощью прерываний BIOS.

Сведения о наличии основных устройств компьютера записывается в область данных BIOS с адресом 0000:0410 размером в двухбайтовое слово - слово конфигурации. С помощью прерывания INT 11h программа может получить в регистре AX слово конфигурации из указанной выше области данных BIOS.

Биты регистра AX Содержимое
0 В системе установлен накопитель на магнитном диске (НМД)
1 В системе установлен арифметический сопроцессор
2 – 3 (все модели, кроме IBM PS/2) Количество банков оперативной памяти на системной плате. Для компьютера IBM PC размер одного банка равен 16 Кбайт, для IBM PC/XT – 64 Кбайт. Для определения объема оперативной памяти в более современных компьютерах следует использовать другие средства, о которых мы расскажем позже
2 (IBM PS/2) Компьютер IBM PS/2 оборудован мышью
3 (IBM PS/2) Не используется в IBM PS/2
4 – 5 Начальный режим видеоадаптера:
00 – EGA или VGA
01 – цветной, 40x25
10 – цветной, 80x25
11 – монохромный, 80x25
6 – 7 Количество установленных накопителей на гибких магнитных дисках (НГМД)
8 Установлен контроллер прямого доступа к памяти DMA
9 – 11 Количество установленных асинхронных последовательных портов
12 Установлен игровой порт
13 Установлен последовательный порт (только для компьютера PCjr)
14 – 15 Количество установленных параллельных адаптеров

Наличие НМД

Сейчас уже трудно найти компьютер, в котором не было бы жесткого диска (разве лишь вам встретится бездисковая рабочая станция для локальной сети или сетевой компьютер для Internet). Тем не менее, анализируя нулевой бит слова конфигурации, полученного в регистре AX от прерывания INT 11h, вы можете определить, оборудован ли данный компьютер жестким диском. Если этот бит установлен в единицу, то оборудован, если нет – жесткий диск отсутствует.

Наличие арифметического сопроцессора

До появления процессора i486 арифметический сопроцессор, значительно ускоряющий вычисления с плавающей точкой, был необязательным устройством. На системной плате компьютера для него предусматривалось отдельное гнездо.

Процессор i486 выпускался в двух модификациях – с встроенным арифметическим сопроцессором и без сопроцессора. В современных компьютерах на базе процессора Pentium арифметический сопроцессор присутствует всегда и находится в корпусе центрального процессора.

Анализируя бит с номером 1 в значении, полученном в регистре AX от прерывания INT 11h, вы можете определить факт наличия в системе арифметического сопроцессора. Если сопроцессор установлен, программа может использовать его для выполнения вычислений. Каким именно образом - вы узнаете из главы нашей книги, посвященной этому устройству.

Начальный режим работы видеоадаптера

Биты с номерами 4 и 5 слова конфигурации содержат номер начального режима видеоадаптера. В современных компьютерах применяется, как правило, режим с номером 10b - цветной, 80 текстовых строк по 25 символов в каждой строке.

Количество установленных НГМД

Поле размером два бита с номерами 6 и 7 содержит количество накопителей НГМД, установленных в системе, минус единица. То есть, если в компьютере установлен один НГМД, в этом поле записано нулевое значение, если два – значение 1 и так далее.

Современные модели компьютеров обычно оборудуют одним накопителем НГМД, предназначенным для чтения дискет размером 3,5 дюйма. Что же касается дискет размером 5,25 дюйма, то они уже ушли в прошлое.

Наличие контроллера прямого доступа DMA

Контроллер прямого доступа DMA применяется для непосредственной передачи данных из периферийных устройств в оперативную память компьютера, минуя центральный процессор. Этот контроллер есть во всех современных компьютерах, поэтому бит с номером 8 обычно установлен в единицу.

Количество асинхронных последовательных адаптеров

В поле, образованное битами 9, 10 и 11, хранится количество асинхронных последовательных адаптеров, установленных в системе и обнаруженных BIOS в процессе инициализации.

Игровой адаптер

Если установлен бит с номером 12, к компьютеру подключен игровой адаптер, предназанченный для джойстика.

Последовательный порт компьютера PCjr

Маловероятно, что вам попадется в руки антикварный образец компьютера PCjr, но, тем не менее, установленный 13 бит слова конфигурации означает, что этот компьютер оборудован последовательным портом.

Количество параллельных адаптеров

В поле, образованное битами с номерами 14 и 15, записывается количество параллельных адаптеров, обнаруженных на этапе инициализации.

Размер оперативной памяти

В компьютере имеются различные виды оперативной памяти. В первых компьютерах применялся так называемый реальный режим работы процессора, не позволявший адресовать более 1 Мбайт памяти. Начальная область адресов от 0 до 640 Кбайт использовалась для оперативной памяти, а область от 640 Кбайт до 1 Мбайт – для памяти и регистров периферийных устройств, таких как видеоадаптеры и дисковые контроллеры.

Раньше оперативная память была очень дорогая, поэтому далеко не в каждом компьютере было установлен максимально возможный объем – 1 Мбайт. Можно было встретить компьютеры с объемом оперативной памяти, например, 512 Кбайт. Программы в те времена, конечно, тоже были не очень требовательны к объему памяти.

С появлением операционных систем Microsoft Windows и IBM OS/2 требования к объему памяти, установленной в компьютере, резко возросли. Компания Intel выпустила процессор i80286, способный адресовать в защищенном режиме до 16 Мбайт физической памяти.

Теперь память с адресами в пределах первого мегабайта стала называться стандартной, а выше этого предела - расширенной (extended). Размер расширенной памяти определяется во время инициализации компьютера и записывается в память CMOS, откуда ее нетрудно извлечь.

Стандартная оперативная память

Прерывание INT 12h возвращает в регистре AX размер стандартной оперативной памяти в килобайтах. Заметим, что это значение, хранящееся в области данных BIOS с адресом 0040h:0013h, не превышает 640 Кбайт, даже если в компьютере установлено более 64 Мбайт оперативной памяти.

Расширенная оперативная память

Функция 88h прерывания INT 15h позволяет определить размер доступной расширенной памяти в килобайтах, возвращая соответствующее значение в регистре AX.

Учтите, что если в системе установлен драйвер расширенной памяти, такой как, например, HIMEM.SYS, указанная выше функция может вернуть нулевое значение. Это происходит из-за того что драйвер берет на себя функции управления расширенной памятью. Заметим также, что с помощью параметра /int15 вы можете указать драйверу HIMEM.SYS размер зарезервированной расширенной памяти, доступной через интерфейс прерывания INT 15h:

device=c:\dos\himem.sys /int15=xxxx

Программа HDWCFG

Программа HDWCFG определяет конфигурацию аппаратных средств компьютера, пользуясь для этого описанными выше функциями BIOS. Полученная конфигурация отображается на консоли, как это показано ниже:

HDWCFG (C)A. Frolov, 1997

Configuration word: C823
HDD present
NPU present
RAM banks: 0
Video Mode: 2
Nubber of FDD: 1
Nubber of COM ports: 2
Number of LPT ports: 3
RAM istalled: 640 Kbytes
Extended RAM istalled: 0

Исходный текст программы HDWCFG представлен в листинге 1.1.

Листинг 1.1. Файл hdwcfg\hdwcfg.c

// =====================================================
// Получение информации о конфигурации компьютера
// при помощи BIOS
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <conio.h>
#include <memory.h>
#include <dos.h>

// Битовые поля слова конфигурации
typedef struct _HDWCFG
{
  unsigned  HddPresent:   1; // 0
  unsigned  NpuPresent:   1; // 1
  unsigned  AmountOfRAM:  2; // 2-3
  unsigned  VideoMode:    2; // 4-5
  unsigned  NumberOfFdd:  2; // 6-7
  unsigned  DmaPresent:   1; // 8
  unsigned  NumberOfCom:  3; // 9-11
  unsigned  GamePresent:  1; // 12
  unsigned  JrComPresent: 1; // 13
  unsigned  NumberOfLpt:  2; // 14-15
} HDWCFG;     

int main(void) 
{ 
  union REGS  rg;
  HDWCFG HdwCfg; 
  unsigned uword;

  printf("\nHDWCFG (C)A. Frolov, 1997");

  // Вызываем прерывание INT 11h для получения
  // слова конфигурации компьютера
  rg.h.ah = 0x0;
  int86(0x11, &rg, &rg);
  
  // Получаем слово конфигурации и сохраняем 
  // его в структуре HdwCfg
  uword = (unsigned int)rg.x.ax;
  memcpy(&HdwCfg, &uword, 2);
  
  // Выводим на экран конфигурацию компьютера
  printf("\n\nConfiguration word: %04.4X", HdwCfg);
  
  if(HdwCfg.HddPresent)
    printf("\nHDD present");

  if(HdwCfg.NpuPresent)
    printf("\nNPU present");

  printf("\nRAM banks: %d", HdwCfg.AmountOfRAM);
  printf("\nVideo Mode: %d", HdwCfg.VideoMode);
  printf("\nNubber of FDD: %d", HdwCfg.NumberOfFdd + 1);
    
  if(HdwCfg.DmaPresent)
    printf("\nDMA present");

  printf("\nNubber of COM ports: %d", HdwCfg.NumberOfCom);
  
  if(HdwCfg.GamePresent)
    printf("\nGame adapter present");

  if(HdwCfg.JrComPresent)
    printf("\nPCjr Com present");

  printf("\nNumber of LPT ports: %d", HdwCfg.NumberOfLpt);

  // Вызываем прерывание INT 12h для определения 
  // объема основной оперативной памяти компьютера
  rg.h.ah = 0x0;
  int86(0x12, &rg, &rg);

  // Выводим объем оперативной памяти
  printf("\nRAM istalled: %d Kbytes", 
    (unsigned int)rg.x.ax);

  // Получаем объем расширенной оперативной памяти,
  // доступной через прерывание INT 15h
  rg.h.ah = 0x88;
  int86(0x15, &rg, &rg);

  // Выводим объем расширенной оперативной памяти
  printf("\nExtended RAM istalled: %ld Kbytes", 
    (unsigned int)rg.x.ax);

  getch();
  return 0;
}

Код модели компьютера и версия BIOS

На этапе инициализации BIOS записывает в свою область данных по адресу FFFFh:FFFEh байт идентификатора модели компьютера. Ниже мы привели возможные значения этого байта:

Байт Модель компьютера
PRIVATEFF IBM PC
FE IBM XT, Portable PC
FD PCjr
FC IBM PC/AT
FB IBM XT с памятью 640 Кбайт на системной плате
FA IBM PS/2 модель 25 или 30
F9 Convertible PC
F8 IBM PS/2 модели 55SX, 70, 80
9A Compaq XT, Compaq Plus
30 Sperry PC
2D Compaq PC

Более подробную информацию можно получить, вызвав функцию C0h прерывания BIOS INT 15h:

Регистры на входе: AH = C0h
Регистры на выходе: ES:BX = адрес таблицы конфигурации, которая находится в ПЗУ BIOS;
CF = 0 при успешном вызове прерывания;
CF = 1 если в данной версии BIOS функция C0h не реализована

После выполнения прерывания регистры ES:BX будут указывать на таблицу в области ПЗУ BIOS. В этой таблице имеется более точная информация о типе компьютера, номер версии BIOS, сведения об аппаратных особенностях конкретной модели.

Приведем формат указанной таблицы:

Смещение Размер, байт Описание
0 2 Размер таблицы в байтах
2 1 Код модели
3 1 Дополнительный код модели
4 1 Модификация версии BIOS
5 1 Байт конфигурации аппаратных средств
6 2 Зарезервировано и равно 0
8 2 Зарезервировано и равно 0

Ниже мы привели описание отдельных бит байта конфигурации аппаратных средств.

Номер бита Описание
0 Зарезервирован
1 Если этот бит установлен в 1, то используется системная шина Micro Channel, в противном случае – шина ISA
2 Применяется расширенная область данных BIOS
3 В BIOS реализована функция ожидания внешнего события
4 Каждый раз после вызова прерывания от клавиатуры INT 9h вызывается функция 4Fh прерывания INT 15h
5 Установлены часы реального времени
6 Установлен второй контроллер прерываний 8259
7 Канал DMA номер 3 используется BIOS для работы с диском

В следующей таблице приведены коды моделей, дополнительные коды моделей и версии BIOS для некоторых известных типов компьютеров:

Код модели Доп. код модели Модификация версии BIOS Тип компьютера
FFh - - Оригинальная версия IBM PC
FEh - - IBM PC/XT
FDh - - PCjr
FCh 00h 01h IBM PC/AT, модель 239
FCh 01h 00h IBM PC/AT
FCh 02h 00h IBM PC/XT-286
FCh 04h 00h IBM PS/2 модель 50
FCh 05h 00h IBM PS/2 модель 60
FBh 00h 01h IBM PC/XT
FBh 00h 02h IBM PC/XT
FAh 00h 00h IBM PS/2 модель 30
FAh 00h 01h IBM PS/2 модель 30
FAh 01h 00h IBM PS/2 модель 25
F9h 00h 00h PC Convertible
F8h 00h 00h IBM PS/2 модель 80 с тактовой частотой 16 Мгц
F8h 01h 00h IBM PS/2 модель 80 с тактовой частотой 20 Мгц
F8h 04h 02h, 03h IBM PS/2 модель 70
F8h 09h 02h, 03h IBM PS/2 модель 70
F8h 0Ch 00h IBM PS/2 модель 55 SX
F8h 1Bhh 00h IBM PS/2 модель 70-486
9Ah - - Compaq XT или Compaq Plus
30h - - Sperry PC
2Dh - - Compaq PC или Compaq Deskpro

Символ "-" в приведенной выше таблице означает, что функция C0h прерывания INT 15h для данной версии BIOS не реализована. Все, что вы можете сделать в этом случае для идентификации BIOS, это получить байт кода модели по адресу F000h:FFFEh и дату изготовления BIOS, занимающую восемь байт начиная с адреса F000h:FFF5h. Дата хранится в формате ASCII.

Большинство современных так называемых IBM-совместимых персональных компьютеров имеют код модели FCh, а дополнительный код модели 01h. Такие значения, например, записаны в таблице конфигурации компьютера Compaq Deskpro 6000 с процессором Pentium Pro 200.

Программа BIOSINFO

Программа BIOSINFO получает и отображает на консоли дату изготовления версии BIOS, а также содержимое таблицы конфигурации, адрес которой определяется с помощью функции C0h прерывания BIOS INT 15h:

BIOSINFO (C)A. Frolov, 1997

BIOS data:        04/18/97
BIOSINFO address: 0212:0190
BIOSINFO Size:    8
Model:            FC
SubModel:         1
BIOS Revision:    0
Hardvare Cfg:     70
Reserved1:        00
Reserved2:        00

Hardware configuration
----------------------
Second IRQ Controller 8259
Real Time Clock
Used function 4Fh INT 15h
ISA Bus installed

Исходный текст программы представлен в листинге 1.2.

Листинг 1.2. Файл biosinfo\biosinfo.c

// =====================================================
// Получение информации о BIOS
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <conio.h>
#include <dos.h>

// Структура области данных с информацией о BIOS
typedef struct _BIOSINFO
{
  int nSize;                   // размер структуры
  unsigned char bModel;        // код модели компьютера
  unsigned char bSubModel;     // дополнительный код модели
  unsigned char bBIOSRevision; // номер изменений 
                               //  версии BIOS
  unsigned char bHardwareCfg;  // конфигурация аппаратуры
  int reserved1;               // зарезервировано
  int reserved2;               // зарезервировано
} BIOSINFO;

int main(void) 
{ 
  union REGS  rg;
  struct SREGS srg;
  int   i;
  BIOSINFO far *lpbi;
  void far* lp;
  unsigned char bHdwCfg;

  printf("\nBIOSINFO (C)A. Frolov, 1997");

  // Конструируем указатель на дату изготовления
  // BIOS. Эта дата записана в ПЗУ по адресу F000h:FFF5h
  _FP_SEG(lp) = 0xf000;
  _FP_OFF(lp) = 0xfff5;

  // Выводим дату на экран
  printf("\n\nBIOS data:        ");
  for(i=0; i<8; i++)
    putch(*((char far *)lp + i));

  // Вызываем функцию C0h для получения адреса
  // таблицы конфигурации компьютера.
  rg.h.ah = 0xc0;
  int86x(0x15, &rg, &rg, &srg);

  // Если в BIOS нет данной функции,
  // читаем код модели компьютера 
  // из ПЗУ по адресу F000h:FFFEh
  if(rg.x.cflag == 1)   
  {
    printf("\nFunction C0h INT 15h not supported\n");

    // Конструируем указатель на код модели    
    _FP_SEG(lp) = 0xf000;    
    _FP_OFF(lp) = 0xfffe;

    // Выводим код модели компьютера на экран
    printf("\nModel:             %02.2X",
      (unsigned char)(*(char far *)lp));
    return(-1);
  }
     
  // Конструируем укзатель на таблицу
  // информации о BIOS
  _FP_SEG(lpbi) = srg.es;
  _FP_OFF(lpbi) = rg.x.bx;

  // Выводим на экран содержимое таблицы
  printf("\nBIOSINFO address: %Fp"
         "\nBIOSINFO Size:    %d"
         "\nModel:            %02.2X"
         "\nSubModel:         %d"
         "\nBIOS Revision:    %d"
         "\nHardvare Cfg:     %02.2X"
         "\nReserved1:        %02.2X"
         "\nReserved2:        %02.2X",
   lpbi, lpbi->nSize, lpbi->bModel, lpbi->bSubModel,
   lpbi->bBIOSRevision, lpbi->bHardwareCfg,
   lpbi->reserved1, lpbi->reserved2);
   
  // Определяем конфигурацию компьютера
  printf("\n\nHardware configuration"
           "\n----------------------");

  // Запоминаем байт конфигурации
  bHdwCfg = lpbi->bHardwareCfg;

  // Расшифровываем байт конфигурации
  if(bHdwCfg & 0x80)
    printf("\nDMA Channel 3");
  
  if(bHdwCfg & 0x40)
    printf("\nSecond IRQ Controller 8259");

  if(bHdwCfg & 0x20)
    printf("\nReal Time Clock");

  if(bHdwCfg & 0x10)
    printf("\nUsed function 4Fh INT 15h");

  if(bHdwCfg & 0x8)
    printf("\nBIOS event wait supported");

  if(bHdwCfg & 0x4)
    printf("\nExtended BIOS data used");

  if(bHdwCfg & 0x2)
    printf("\nMicro Channel Bus");

  if(!(bHdwCfg & 0x2))
    printf("\nISA Bus installed\n");
  
  getch();
  return 0;
}

Конфигурация в памяти CMOS

Как мы уже говорили, энергонезависимая память CMOS используется в современных компьютерах для хранения текущей конфигурации аппаратных средств. Эта память состоит из набора ячеек, доступ к которым для чтения и записи выполняется через порты ввода и вывода с адресами 70h и 71h.

Процедура чтения ячейки памяти CMOS состоит из двух шагов. На первом шаге программа записывает в выходной порт с адресом 70h номер ячейки, из которой необходимо прочитать информацию. На втором шаге программа читает содержимое данной ячейки из входного порта с адресом 71h:

int nCellContent;
outp(0x70, nCell);
nCellContent = inp(0x71);

Запись данных в ячейку памяти CMOS выполняется аналогично, только после записи номера ячейки в порт 70h программа должна записать новое значение для этой ячейки в порт с адресом 71h:

outp(0x70, nCell);
outp(0x71, nNewValue);

В памяти CMOS хранится текущее время и дата, сведения о конфигурации системы, результат тестирования при включении питания и другая информация, приведенная ниже:

Адрес ячейки Содержимое
00h - 0Dh Используются часами реального времени
0Eh Байт состояния диагностики при включении питания
0Fh Байт состояния отключения
10h Тип НГМД
11h Зарезервировано
12h Тип НМД (если меньше 15)
13h Зарезервировано
14h Конфигурация оборудования
15h - 16h Объем основной памяти
17h - 18h Объем расширенной памяти
19h Тип первого НМД (если он больше 15)
1Ah Тип второго НМД (если он больше 15)
1Bh - 20h Зарезервировано
21h - 2Dh Зарезервировано
2Eh - 2Fh Контрольная сумма ячеек 10h - 20h
30h - 31h Объем расширенной памяти
32h Текущее столетие в двоично-десятичном коде (19h для 19-го столетия)
33h Различная информация
34h - 3Fh Зарезервировано

Рассмотрим подробно назначение отдельных ячеек CMOS-памяти.

00h - 0Dh - область часов реального времени

Ячейки с адресами 00h - 0Dh используются часами реального времени. Часам реального времени будет посвящена отдельная глава, поэтому сейчас мы не станем останавливаться на этих ячейках.

0Eh - байт диагностики

Байт диагностики (расположенный в памяти CMOS по адресу 0Eh) содержит результаты выполнения диагностики при включении питания компьютера. Выполнив анализ содержимого байта 0Eh, программа может выявить неисправность НМД, часов реального времени, разрядку аккумулятора и ошибки в конфигурации. Приведем формат этого байта:

Бит Описание
0-1 Не используется, равно 0
2 0 - неправильная установка часов реального времени;
1 - часы реального времени установлены правильно
3 0 - НМД исправен;
1 - неисправность НМД, невозможно загрузить операционную систему с жесткого диска
4 0 - размер оперативной памяти указан правильно;
1 - фактический размер оперативной памяти не соответствует указанному в памяти CMOS
5 0 - конфигурация указана правильно;
1 – ошибка в конфигурации системы, фактическая конфигурация не соответствует указанной в байте конфигурации оборудования (ячейка 14h)
6 0 - контрольная сумма памяти CMOS правильная;
1 - ошибка в контрольной сумме памяти CMOS
7 0 – аккумулятор, питающий память CMOS, исправен и заряжен;
1 - разрядка аккумулятора выше нормы

0Fh - байт отключения

Байт отключения 0Fh используется процессорами 80286, 80386, 80486 и Pentium для определения способа возврата из защищенного режима в реальный после аппаратного сброса.

Вы, вероятно, знаете, что эти процессоры могут работать либо в реальном режиме, который соответствует режиму работы процессора 8086, либо в защищенном. Защищенный режим работы используется такими операционными системами, как Microsoft Windows, IBM OS/2 и UNIX. В этом режиме процессор может непосредственно адресовать всю память, лежащую выше границы 1 Мбайт.

Подробное рассмотрение защищенного режима работы мы привели в 6 томе «Библиотеки системного программиста», который так и называется – «Защищенный режим процессоров Intel 80286/80386/80486». Здесь же мы только кратко расскажем о переходе процессора 80286 из реального режима в защищенный и обратно для иллюстрации использования ячейки памяти CMOS с адресом 0Fh.

Для перевода процессора 80286 из реального режима в защищенный можно использовать специальную команду LMSW:

mov   ax,1
lmsw  ax

Разумеется, двух строк, приведенных выше, недостаточно для правильной работы процессора в защищенном режиме.

Для того, чтобы вернуть процессор 80286 из защищенного режима в реальный, необходимо выполнить аппаратный сброс (отключение) процессора. Это можно сделать следующим образом:

mov   ax, 0FEh  ; команда отключения
out   64h, ax

Перед выдачей команды отключения программа должна записать в ячейку 0Fh памяти CMOS причину отключения:

Значение Причина отключения
0 Программный сброс при помощи комбинации клавиш <Ctrl-Alt-Delete> или неожиданный сброс. Выполняется обычный перезапуск системы, но процедура тестирования при включении питания не запускается
1 Сброс после определения объема памяти
2 Сброс после тестирования памяти
3 Сброс после обнаружения ошибки четности в оперативной памяти
4 Сброс с запросом перезагрузки
5 После сброса перезапускается контроллер прерываний, затем управление передается по адресу, который находится в области данных BIOS с адресом 0000h:0467h
6, 7, 8 Сброс после выполнения проверки работы процессора в защищенном режиме
9 Сброс после выполнения копирования блока памяти из основной памяти в расширенную
0Ah После сброса управление немедленно передается по адресу, взятому из области данных BIOS с адресом 0000h:0467h

Для перевода процессоров 80386, 80486 и Pentium из реального режима в защищенный и обратно можно использовать загрузку специального управляющего регистра CR0 обычной командой MOV. Однако будет работать и метод, основанный на применении команды LMSW и команды отключения.

Вы можете использовать сведения о команде отключения для организации программного перезапуска системы.

10h - тип накопителей НГМД

Младшая и старшая тетрады этого байта описывают, соответственно, второй и первый НГМД:

Значение Емкость НГМД
0000 НГМД не установлен
0001 360 Кбайт
0010 1,2 Мбайт
0011 720 Кбайт
0100 1.44 Мбайт

11h - зарезервировано для IBM PC/AT, тип НМД для IBM PS/2

В компьютерах IBM PS/2 ячейки памяти CMOS с адресами 11h и 12h используются для хранения типа, соответственно, первого и второго НМД.

12h - тип первого и второго НМД

Этот байт разделен на две тетрады аналогично байту, описывающему НГМД. Однако в тетраде можно закодировать только 16 различных значений, а типов НМД значительно больше. Поэтому тип 15 используется специальным образом - если тип НМД в младшей тетраде (первый физический НМД) равен 15, то правильное значение типа находится в памяти CMOS по адресу 19h. Аналогично для второго физического НМД этот тип можно взять из байта по адресу 1Ah (если старшая тетрада байта с адресом 12h равна 15).

Таблица стандартных типов НМД была приведена в третьей книге первого тома, в разделе, посвященном конфигурации дисковой подсистемы. Кроме того, сведения о типах дисков, задаваемых программой BIOS Setup, обычно есть в документации, поставляемой вместе с компьютером.

В современных компьютерах установлены НМД с интерфейсом IDE или SCSI.

В первом случае тип диска устанавливается равным 47. Это означает. Что фактические параметры диска хранятся в старших ячейках памяти CMOS, номера которых зависят от изготовителя и версии BIOS.

Тип НМД с интерфейсом SCSI задается как 1 или 0. Фактические параметры НМД определяются BIOS, расположенной в контроллере SCSI.

13h - зарезервировано

Эта ячейка памяти CMOS зарезервирована.

14h - конфигурация оборудования

В этом байте находится информация о количестве установленных НГМД, о наличии арифметического сопроцессора, а также о типе видеоадаптера, подключенного к системе. Приведем формат байта конфигурации:

Бит Описание
0 1 - в системе установлены НГМД;
0 - НГМД не используются
1 1 - установлен арифметический сопроцессор;
0 - арифметический сопроцессор не установлен
2-3 не используются, равны 0
4-5 Тип видеоадаптера и видеорежим:
00 - не используется или EGA;
01 - CGA, EGA, VGA в режиме 40x25;
10 - CGA, EGA, VGA в режиме 80x25;
11 – монохромный видеоадаптер
6-7 Количество установленных НГМД, уменьшенное на единицу;
00 – один НГМД;
01 – два НГМД;
10 – три НГМД;
11 – четыре НГМД

15h-16h - объем стандартной оперативной памяти

Ячейка 15h содержит младший байт, а ячейка 16h - старший байт объема основной памяти, например:

17h-18h - объем расширенной памяти

Ячейки 17h и 18h содержат, соответственно, младший и старший байты размера расширенной памяти (расположенной выше границы 1 Мбайт) в килобайтах.

19h-1Ah тип первого и второго НМД

Эти ячейки содержат типы, соответственно, первого и второго НМД, если соответствующий тип имеет значение, большее 15 (см. описание ячейки 12h).

1Bh-2Dh - зарезервировано

Эти ячейки памяти CMOS зарезервированы.

2Eh-2Fh - контрольная сумма ячеек 10h - 20h

Для ячеек памяти CMOS с адресами от 10h до 20h при инициализации системы BIOS выполняет проверку контрольной суммы. Эта контрольная сумма хранится также в памяти CMOS в ячейках 2Eh и 2Fh (соответственно, старший и младший байты).

30h-31h - объем расширенной памяти

Ячейки 30h и 31h содержат, соответственно, младший и старший байты размера расширенной памяти (расположенной выше границы 1 Мбайт) в килобайтах.

Эта значение дублирует значение, записанное в ячейках с адресами 17h-18h.

32h текущее столетие

В компьютерах IBM PC/AT этот байт содержит текущее столетие в двоично-десятичном коде, то есть 19 столетие записано как 19h.

Компьютеры модели IBM PS/2 используют эту ячейку вместе с ячейкой 33h для хранения контрольной суммы ячеек с адресами от 10h до 31h. При этом старший байт контрольной суммы хранится в ячейке 32h, а младший - 33h.

33h - различная информация

В компьютерах IBM PC/AT этот байт используется программой BIOS Setup для собственных нужд.

34h-3Fh - зарезервировано

Это поле зарезервировано, однако вы можете использовать по своему усмотрению, например, хранить здесь пароль.

Другие ячейки памяти CMOS

Назначение описанных выше 64 ячеек памяти CMOS документировано и одинаково для BIOS различных изготовителей и различных версий. Тем не менее, есть исключения.

BIOS компьютера IBM PS/2 использует ячейку с адресом 37h для хранения номера текущего столетия. Ячейки 38h-3Fh в модели 50 компьютера IBM PS/2 используются для хранения пароля. Обращение к этим ячейкам выполняется по адресам 78h-7Fh, которые аппаратно отображаются на адреса 38h-3Fh.

Кроме того, имеются ячейки памяти CMOS с номерами, большими чем 3Fh. Их назначение зависит от изготовителя BIOS, поэтому обращайтесь к ним только в том случае, если у вас есть соответствующая документация.

Программа CMOSSHOW

Программа CMOSSHOW читает в массив первые 64 ячейки памяти CMOS и отображает содержимое некоторых из них:

CMOS Show (C)A. Frolov, 1997

RTC:              22 00 30 00 17 00 03 19 08 97 a6 02 00
Diagnostics byte: 08
Shutdown byte:    00
Reserved:         00 00 00 00 00 00 00 00 00 00 00 00
Extended RAM:     16384 Kbyte

Отображаются ячейки часов реального времени RTC, о которых мы расскажем позже в отдельной главе, диагностический байт и байт отключения, зарезервированные байты. Кроме того, на основании информации, хранящейся в ячейках 17h и 18h программа вычисляет размер расширенной памяти, установленной в компьютере.

Исходный текст программы CMOSSHOW вы найдете в листинге 1.3.

Листинг 1.3. Файл cmosshow\cmosshow.c

// =====================================================
// Чтение и отображение ячеек памяти CMOS
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

int main() 
{
  unsigned char cmos[64];
  int i;
  unsigned long nExtRam;

  printf("\nCMOS Show (C)A. Frolov, 1997\n\n");

  // Читаем 64 ячейки CMOS-памяти в массив cmos
  for(i=0; i<64; i++) 
  {
    outp(0x70,i);
    cmos[i]=inp(0x71);
  }

  // Отображаем ячейки часов реального времени
  printf("\nRTC:              ");

  for(i=0; i<0xd; i++) 
  {
    printf("%02.2x ",(unsigned)cmos[i]);
  }

  // Отображаем состояние байта диагностики
  // после включения питания
  printf("\nDiagnostics byte: %02.2x",cmos[0xe]);

  // Отображаем содержимое байта отключения
  printf("\nShutdown byte:    %02.2x\n",cmos[0xf]);

  // Отображаем содержимое зарезервированных ячеек
  printf("Reserved:         ");
  for(i=0x34; i<0x40; i++) 
  {
    printf("%02.2x ",(unsigned)cmos[i]);
  }
  
  // Вычисляем объем расширенной памяти и отображаем
  // его на консоли
  nExtRam = ((unsigned long)cmos[0x18] << 8) + cmos[0x17];
  printf("\nExtended RAM:     %ld Kbyte\n", nExtRam);
    
  getch();
  return 0;
}

Определение типа центрального процессора

В некоторых случаях эффективность работы программы можно заметно повысить, если использовать команды новых моделей процессоров Pentium, такие как, например, команды MMX. На сервере Intel с адресом http://www.intel.com вы найдете исчерпывающую информацию о том, как распознать различные модели процессоров, созданных этой фирмой. В нашей книге мы рассмотрим упрощенную методику, которая, тем не менее, может быть использована в большинстве случаев.

Модели Intel 8086/8088

Способ распознавания процессоров Intel 8086/8088 основан на том факте, что биты 12-15 регистра FLAGS всегда установлены в единицу.

Прежде всего программа записывает текущее содержимое регистра FLAGS в регистр AX. Для этого используется стек:

pushf
pop ax

Первоначальное содержимое регистра FLAGS сохраняется в регистре CX:

mov cx, ax

Далее программа пытается записать нулевые значения в биты 12-15 регистра FLAGS:

and ax, 0fffh
push ax
popf

Теперь нужно проверить, изменилось ли содержимое указанных битов регистра FLAGS. Для этого новое содержимое регистра FLAGS записывается через стек в регистр AX, а затем, после наложения маски 0f000h, сравнивается со значением 0f000h:

pushf
pop ax
and ax, 0f000h
cmp ax, 0f000h
je is_8086

Если биты 12-15 остались установленными в единичное значение, программа работает на процессоре Intel 8086/8088, если нет – в компьютере установлена более старшая модель процессора.

Модель Intel 80286

В процессоре Intel 80286, когда он работает в реальном режиме адресации, биты 12-15 регистра FLAGS всегда сброшены в нуль, что можно использовать для обнаружения этой модели процессора.

Следующий фрагмент кода пытается записать в эти биты единичное значение:

mov ax, 0f000h
push ax
popf

Затем новое содержимое регистра FLAGS переписывается в регистр AX:

pushf
pop ax

Если содержимое битов 12-15 равно нулю, программа работает на процессоре Intel 80286:

and ax, 0f000h
jz is_80286

В противном случае необходимо продолжить проверку модели процессора.

Модель Intel 80386

Для того чтобы отличить процессор Intel 80386 от процессоров старших моделей, можно попробовать установить в регистре EFLAGS бит 18. Этот бит был впервые определен в процессоре Intel 80486 для сигнализации ошибки выравнивания. Его невозможно установить в процессоре Intel 80386.

В процессе проверки программа вначале получает исходное содержимое регистра EFLAGS, записывая его в регистры EAX и ECX:

pushfd
pop eax
mov ecx, eax

Далее программа инвертирует значение бита 18 и записывает полученный результат в регистр EFLAGS:

xor eax, 40000h
push eax
popfd

На последнем шаге идентификации новое содержимое регистра EFLAGS извлекается и сравнивается со старым:

pushfd
pop eax
xor eax, ecx
jz is_80386

Если бит 18 не изменил своего значения, мы имеем дело с процессором Intel 80386.

Модель Intel 80486

Отличительная особенность процессора Intel 80486 – невозможность изменения состояния бита 21 регистра EFLAGS. Этот бит используется процессорами Intel Pentium и более старшими моделями процессоров Intel для определения возможности вызова команды идентификации процессора CPUID, о которой мы скоро расскажем.

Фрагмент кода, который нужно использовать для обнаружения процессора Intel 80486, аналогичен фрагменту для процессора Intel 80386. Отличие заключается в том, что вместо бита 18 проверяется бит 21:

pushfd
pop eax
mov ecx, eax
xor eax, 200000h
push eax
popfd
pushfd
pop eax
xor eax, ecx
je is_80486

Если же выяснилось, что содержимое бита 21 регистра EFLAGS можно изменять, дальнейшую идентификацию модели процессора следует выполнять с использованием команды CPUID, к описанию которой мы и переходим.

Команда CPUID

В новых процессорах фирмы Intel появилась команда CPUID, специально предназначенная для определения модели процессора. В своей программе, составленной на языке ассемблера, вы можете определить эту команду следующим образом:

CPU_ID MACRO
  db 0fh
  db 0a2h
ENDM

Как работает команда CPUID?

Перед вызовом этой команды необходимо загрузить в регистр EAX значение, которое определяет выполняемые действия. В первый раз вы должны вызвать команду CPUID, загрузив предварительно в регистр EAX нулевое значение:

mov eax, 00h
CPU_ID

Команда вернет в регистре EAX максимальное значение, которое можно передавать команде CPUID для определения выполняемых действий. Кроме того, в регистрах EBX, ECX и EDX будут находиться байты текстовой строки описания фирмы-изготовителя процессора. Для процессоров Intel это будет строка GenuineIntel.

Следующая последовательность команд перепишет эти байты в буфер _vendor_id_msg в формате, пригодном для вывода на консоль средствами BIOS:

_vendor_id_msg db "............", 0dh, 0ah, "$"
. . .
mov dword ptr _vendor_id_msg,     ebx
mov dword ptr _vendor_id_msg[+4], edx
mov dword ptr _vendor_id_msg[+8], ecx

Для определения модели процессора следует вызвать команду CPUID, загрузив предварительно в регистр EAX значение 1:

mov eax, 1
CPU_ID

При этом в регистр EAX будет загружено слово сигнатуры, по которому можно определить модель процессора, а в регистр EDX – слово, состоящее из отдельных флагов, характеризующих возможности процессора (feature flags). Регистры EBX и ECX зарезервированы для моделей процессоров, которые будут разработаны в будущем.

Ниже приведен фрагмент кода, в котором слово сигнатуры и слово возможностей записываются в переменные _cpu_signature и _features_edx для дальнейшего анализа:

_cpu_signature dd 0
_features_edx  dd 0
. . .
mov _cpu_signature, eax
mov _features_edx, edx

Рассмотрим формат слова сигнатуры, возвращаемое командой CPUID в регистре EAX:

Биты Описание
0-3 Код модификации модели (stepping)
4-7 Код модели
8-11 Код семейства моделей
12-13 Тип процессора
14-31 Зарезервировано

Слово сигнатуры состоит из отдельных полей, назначение которых будет описано ниже.

Биты 12 и 13 определяют тип процессора:

Значение битов 12 и 13 Тип процессора
00 Процессор, изготовленный производителем OEM
01 Процессор OverDrive
10 Процессор типа Dual, который можно использовать в двухпроцессорных системах
11 Зарезервировано

Заметим, что многопроцессорные системы могут быть построены не только на базе процессоров типа Dual, но и на обычных процессорах.

Ниже мы привели таблицу, с помощью которой по содержимому трех полей слова сигнатуры (тип процессора, код семейства и код модели) можно распознать процессор:

Тип процессора Код семейства Код модели Описание процессора
00 0100 0100 Intel 486 SL
00 0100 0111 Intel DX2
00 0100 1000 Intel DX4
00, 01 0100 1000 Intel DX4 OverDrive
00 0101 0001 Pentium 60, 66;
Pentium OverDrive для процессоров Pentium 60, 66
00 0101 0010 Pentium 75, 90, 100, 120, 133, 150, 166, 200
01 0101 0010 Pentium OverDrive для процессоров Pentium 75, 90, 100, 120, 133
01 0101 0011 Pentium OverDrive для систем на базе процессора Intel 486
00 0101 0100 Pentium 166, 200 с командами MMX
01 0101 0100 Зарезервировано. Будет использоваться процессорами Pentium OverDrive для процессоров Pentium 75, 90, 100, 120, 133
00 0110 0001 Pentium Pro
00 0110 0011 Pentium II
00 0110 0101 Зарезервировано для новых процессоров P6
01 0110 0011 Зарезервировано для процессоров Pentium OverDrive для процессоров Pentium Pro

Что же касается кода модификации модели (stepping), то он определяет изменения в процессоре. Вы найдете описания этих кодов для процессора Pentium в руководстве Pentium Processor Specification Update, который можно приобрести в фирме Intel.

Приведем описание битов, возвращаемых командой CPUID в регистре EDX. Напомним, что это слово состоит из отдельных флагов, характеризующих возможности процессора (feature flags).

Бит Описание
0 На кристалле процессора имеется арифметический сопроцессор, совместимый по командам с сопроцессором Intel 387
1 Процессор может работать в режиме виртуального процессора 8086
2 Процессор может работать с прерываниями ввода/вывода, а также с битом DE регистра CR4
3 Возможно использование страниц памяти размером 4 Мбайт
4 В процессоре есть команда RDTSC, которая может работать с битом TSD регистра CR4
5 Набор регистров процессора, специфический для модели, доступен с помощью команд RDMSR, WRMSR
6 Возможна физическая адресация памяти с использованием шины с шириной, большей чем 32 разряда
7 В процессоре реализовано исключение Machine Check (исключение с номером 18). Возможно использование бита MCE регистра CR4
8 В процессоре реализована команда сравнения и обмена 8 байт данных CMPXCHG8
9 В процессоре есть локальный APIC
10 Зарезервировано
11 В процессоре реализованы команды быстрого вызова системы SYSENTER и SYSEXIT
12 В процессоре есть регистры Memory Type Range
13 Доступен глобальный бит в PDE и PTE, а также бит PGE в регистре CR4
14 Применена архитектура Machine Check Architecture
15 В процессоре реализованы команды условного перемещения данных CMOVCC и (при установленном бите 0) FCMOVCC и FCOMI
16-22 Зарезервировано
23 Применена технология MMX
24-31 Зарезервировано

Еще одно применение для команды CPUID – определение характеристик кэша, установленного в процессоре. Для этого перед вызовом команды CPUID в регистр EAX необходимо загрузить значение 2. Подробное изучение этой возможности выходит за рамки нашей книги. При необходимости вы узнать все подробности о команде CPUID на сервере Intel в сети Internet по указанному нами ранее адресу.

Программа CPUINFO

Программа CPUINFO определяет модель и характеристики процессора, пользуясь только что описанной нами методикой. Полученная информация выводится на консоль в следующем виде (для процессора Pentium Pro):

*CPU Information*, (C) A. Frolov, 1997

CPU model: 5
Vendor ID: GenuineIntel

CPU Signature    00000619
CPU Feature EDX  0000F9FF

CPU type:     0
CPU family:   6
CPU model:    1
CPU stepping: 9

FPU detected

В листинге 1.4 вы найдете исходный текст модуля, составленного на языке ассемблера. В этом модуле определены все функции, необходимые для распознавания процессора и получения его характеристик.

Листинг 1.4. Файл cpuinfo\askcpu.asm

; =====================================================
; Get CPU information
;
; (C) A. Frolov, 1997
;
; E-mail: frolov@glas.apc.org
; WWW:    http://www.glasnet.ru/~frolov
;            or
;         http://www.dials.ccas.ru/frolov
; =====================================================
  .model small

CPU_ID MACRO
  db 0fh
  db 0a2h
ENDM

  .stack 100h
  .data
  
  public _vendor_id_msg
  public _cpu_model
  public _cpu_signature
  public _features_ecx
  public _features_edx
  public _features_ebx
  public _get_cpu_model

_vendor_id_msg db "............", 0dh, 0ah, "$"
_cpu_model     db 0
_cpu_signature dd 0
_features_ecx  dd 0
_features_edx  dd 0
_features_ebx  dd 0

  .code
; ============================================
; _get_cpu_model
; ============================================
  .8086

_get_cpu_model proc

  call cpu_8086
  cmp ax, 0
  jz try_80286

  mov _cpu_model, 0
  jmp end_of_detect

try_80286:

  call cpu_80286
  cmp ax, 0
  jz try_80386

  mov _cpu_model, 2
  jmp end_of_detect

try_80386:

  call cpu_80386
  cmp ax, 0
  jz try_80486

  mov _cpu_model, 3
  jmp end_of_detect

try_80486:

  call cpu_80486
  cmp ax, 0
  jz Pent_CPU

  mov _cpu_model, 4
  jmp end_of_detect

Pent_CPU:

  mov _cpu_model, 5

  .386
  pusha

  mov eax, 00h
  CPU_ID

  mov dword ptr _vendor_id_msg, ebx
  mov dword ptr _vendor_id_msg[+4], edx
  mov dword ptr _vendor_id_msg[+8], ecx

  cmp eax, 1
  jl end_of_detect
  
  mov eax, 1
  CPU_ID

  mov _cpu_signature, eax
  mov _features_ebx, ebx
  mov _features_edx, edx
  mov _features_ecx, ecx

  popa

end_of_detect:
  
  .8086
  ret

_get_cpu_model endp

; ============================================
; cpu_8086
; ============================================
cpu_8086 proc
  pushf
  pop ax
  mov cx, ax
  and ax, 0fffh
  push ax
  popf
  pushf
  pop ax
  and ax, 0f000h

  cmp ax, 0f000h
  je is_8086

  mov ax, 0
  ret

is_8086:
  mov ax, 1
  ret

cpu_8086 endp

; ============================================
; cpu_80286
; ============================================
  .286
cpu_80286 proc

  mov ax, 0f000h
  push ax
  popf
  pushf
  pop ax
  and ax, 0f000h
  jz is_80286

  mov ax, 0
  ret

is_80286:
  mov ax, 1
  ret

cpu_80286 endp

; ============================================
; cpu_80386
; ============================================
  .386
cpu_80386 proc
  pushfd
  pop eax
  mov ecx, eax
  xor eax, 40000h
  push eax
  popfd
  pushfd
  pop eax
  xor eax, ecx
  jz is_80386

  mov ax, 0
  ret

is_80386:
  push ecx
  popfd

  mov ax, 1
  ret

cpu_80386 endp

; ============================================
; cpu_80486
; ============================================
cpu_80486 proc
  pushfd
  pop eax
  mov ecx, eax

  xor eax, 200000h
  push eax
  popfd
  pushfd
  pop eax
  xor eax, ecx
  je is_80486

  mov ax, 0
  ret

is_80486:

  mov ax, 1
  ret

cpu_80486 endp
  end

Данный файл был оттранслирован при помощи пакетного файла, представленного в листинге 1.5.

Листинг 1.5. Файл cpuinfo\mk.bat

masm /Zi askcpu.asm,,,,

Главный файл программы приведен в листинге 1.6.

Листинг 1.6. Файл cpuinfo\cpuinfo.c

// =====================================================
// Определение типа процессора
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <memory.h>
               
extern void GET_CPU_MODEL(void);
extern char VENDOR_ID_MSG[12];
extern char CPU_MODEL;
extern long CPU_SIGNATURE;
extern long FEATURES_ECX;
extern long FEATURES_EDX;
extern long FEATURES_EBX;

int main(void)
{         
  char buf[128];
  printf("*CPU Information*, (C) A. Frolov, 1997\n\n");

  GET_CPU_MODEL();
  
  printf("CPU model: %d\n", (unsigned)CPU_MODEL);
  
  if(CPU_MODEL >= 5)
  {
    memcpy(buf, VENDOR_ID_MSG, 12);
    buf[12] = 0;
    printf("Vendor ID: %s\n\n", buf);
  
    printf("CPU Signature    %08.8X\n", CPU_SIGNATURE);
    printf("CPU Feature EDX  %08.8X\n\n", FEATURES_EDX);
    
    printf("CPU type:     %d\n", 
      (CPU_SIGNATURE & 0x3000) >> 12);

    printf("CPU family:   %d\n", 
      (CPU_SIGNATURE & 0xF00) >> 8);

    printf("CPU model:    %d\n", 
      (CPU_SIGNATURE & 0xF0) >> 4);

    printf("CPU stepping: %d\n\n", CPU_SIGNATURE & 0xF);
    
    if(FEATURES_EDX & 0x1)
      printf("FPU detected\n");

    if(FEATURES_EDX & 0x800000)
      printf("MMX supported\n");
  }

  getch();
  return 0;
}