Программа для определения установленного компьютерного оборудования
Введение
В рамках достижения поставленной
цели автором были поставлены и решены следующие задачи:
. Изучить теоретические
аспекты.
. Изучить структуру и методы
написания программ на языке Assembler.
. Реализовать программу,
пользуясь пунктами 1 и 2.
Когда у программиста возникает
вопрос типа «Как определить, сколько в компьютере оперативной памяти?», в 90%
случаев он решается тривиально - используется определенный сервис операционной
системы, который и отвечает на все вопросы вроде этого.
А что делать, если пользоваться
сервисами нельзя, например, в случае разработки собственной ОС?
В данной работе будет проверено:
. Процессор (частота,
производитель, возможности).
. Оперативная память (объем).
. HDD (объем, тип).
. Устройства PCI
(производитель, модель).
Следовательно, необходимо понимать
устройство всей системы, под которую мы пишем программу (в нашем случае это
MS-DOS). Как видим, написание программы для определения установленного
оборудования требует обширных знаний в сфере организации ЭВМ.
1. Процессор
.1 Определение семейства
Процессоры поддерживают инструкцию
CPUID (как Intel, так и AMD), начиная с пятого поколения (Pentium) и
поздних моделей 486.
Если она не поддерживается -
определить производителя и другие параметры процессора, возможно, только
какими-либо недокументированными путями.
Посмотрим, чем отличаются процессоры
не поддерживающие CPUID. Все просто - если бит 18 в EFLAGS доступен,
значит процессор 486 или круче, если его невозможно изменить инструкцией
POPF - 386.
В том же EFLAGS нужно попробовать
изменить бит ID (21) если его можно программно изменить - процессор
поддерживает инструкцию CPUID. CPUID имеет параметр, который задается в
регистре ЕАХ.
Обычно в ответ на вызов CPUID с
ЕАХ=0 процессор возвращает в EBX:ECX:EDX некоторую строку-идентификатор
производителя.
У Intel это «Genuinelntel»,
у AMD - «AuthenticAMD», у Cyrix - «Cyrixlnstead».
Пример:
eax,0 cpuid
mov dword ptr [stm],EBX; Genu
mov dword ptr [stm+4],EDX ;inel
mov dword ptr [stm+8],ECX ;ntel
mov ah,9
lea dx,stm ; Genuinelntel
int 21h
1.2 Определение функций
При вызове CPUID с EAX=1, в регистре
EAX возвращается информация о типе, модели и степпинге (изменения в рамках
одной модели) процессора.
Эти значения расшифровываются по
специальным таблицам.
ЕАХ[00:03] - степпинг (stepping)
ЕАХ[07:04] - модель (model)
ЕАХ[11:08] - семейство (family)
ЕАХ[ 13:12] - тип (type)
ЕАХ[ 15:14]- резерв (reserved)
ЕАХ[19:16] - расширенная модель
(только Pentium 4)
ЕАХ[23:20] - расширенное семейство
(только Pentium 4)
ЕАХ[31:24] - резерв (reserved)
ЕВХ[07:00] - брэнд-индекс
(brand-index)
ЕВХ[ 15:08] - длина строки,
очищаемой инструкцией CLFLUSH (Pentium4)
ЕВХ[23:16] - резерв
ЕВХ[31:24] - идентификатор APIC
процессора.
ЕСХ-0
содержит информацию о различных
расширениях архитектуры (если определенный бит равен 1 - расширение
поддерживается). Ниже приведена таблица 1.
Таблица 1 - значения основных бит
регистра EDX
Бит
|
Описание
|
0
|
Наличие сопроцессора
|
1
|
Расширение для режима V86, наличие флагов VIP и VIF в EFLAGS
|
2
|
Расширения отладки (останов по обращению к портам)
|
3
|
Возможности расширения размера страниц до 4Мб
|
4
|
Наличие счетчика меток реального времени (и инструкции RDTSC)
|
5
|
Поддержка модельно-специфических регистров в стиле Pentium
|
6
|
Расширение физического адреса до 36 бит
|
7
|
Поддержка Machine Check Exception
|
8
|
Инструкция CMPXCHG8B
|
9
|
Наличие APIC
|
10
|
11
|
Поддержка инструкций SYSENTER и SYSEXIT
|
12
|
Регистры управления кэшированием (MTRR)
|
13
|
Поддержка бита глобальности в элементах каталога страниц
|
14
|
Поддержка архитектуры машинного контроля
|
15
|
Поддержка инструкций условной пересылки CMOVxx
|
16
|
Поддержка атрибутов страниц
|
17
|
Возможность использования режима PSE-36 для страничной адресации
|
18
|
Поддержка серийного номера процессора
|
19
|
Поддержка инструкции CLFLUSH
|
20
|
RESERVED
|
21
|
Поддержка отладочной записи истории переходов
|
22
|
Наличие управления частотой синхронизации(АСР1)
|
23
|
Поддержка ММХ
|
24
|
Поддержка инструкций сохранения\восстановления контекста FPU
|
25
|
SSE
|
26
|
SSE2
|
27
|
Самослежение (Self Snoop)
|
28
|
RESERVED
|
29
|
Автоматическое снижение производительности при перегреве
|
30
|
Начилие AMD 3DNow!
|
В данной курсовой работе проверка
значений бит, реализована с помощью макроса bit_test:
test MACRO
num.,messml,m2ah,9dx,ext&mess21hedi,nummldx,yes21hm2:dx,no int 21h m2:-
номер строковой переменной для вывода- номер проверяемого бита
При вызове CPUID с ЕАХ=2 (функция
появилась, начиная с Pentium II, в процессорах AMD она недоступна) в регистрах
ЕАХ, ЕВХ, ЕСХ, EDX возвращаются так называемые «дескрипторы», которые описывают
возможности КЭШей и TLB буферов. Причем AL содержит число, указывающее сколько
раз необходимо последовательно выполнить CPUID (с ЕАХ=2) для
получения полной информации. Дескрипторы построены по такому принципу: никаких
битов тестировать не нужно, если определенный байт просто присутствует в
регистре - значит его нужно интерпретировать. На практике обычно делают так, к
примеру EDX, сначала смотрят что в DL, интерпретируют его содержимое, потом
делают SHR EDX,8 и смотрят опять DL и т.д. Признаком достоверности информации в
регистре является бит 31, если он равен 1 - содержимое регистра достоверно.
Прежде чем выполнять команду CPUID с ЕАХ=2 сначала нужно удостовериться, что
текущий процессор ее поддерживает.
Обладатели процессоров Pentium III
(только их) могут определить серийный номер своего процессора (предварительно
разрешив в BIOS его сообщение процессором, которое по умолчанию отключено) при
помощи CPUID с ЕАХ=3.
В регистрах EDX:ECX возвращаются
младшие 64 бита номера, вместе с тем, что возвращается в ЕАХ при CPUID (ЕАХ=1),
они составляют уникальный 96-битный идентификатор процессора (о котором, в свое
время, было столько разговоров).
Кроме того, процессоры AMD имеют
возможности вызова функций EAX=80000005h и 80000006h, по ним сообщается такая
информация как ассоциативность TLB и элементов КЭШа, но этот вопрос не
затрагивается в данной работе.
В процессорах AMD (начиная с К5) и
Pentium4 имеются возможности сообщения некоторой 48-символьной строки (не той
что по CPUID(O)) эти возможности также задействуются с помощью номеров функций
более 80000000b.
Инструкция CPUID доступна в любом
режиме процессора и с любым уровнем привилегий.
В данной работе программа определяет
поддержку MMX, SSE, SSE2 и.т.д. Там используются только случаи с ЕАХ=0 и ЕАХ=1,
причина этого проста - начиная с ЕАХ=2, начинаются очень большие разночтения
между Intel и AMD. Предусмотреть оба случая - значит усложнить программу и
найти себе проблемы с тестированием на разных процессорах.
.3 Определение частоты
Частоту процессора можно определить
многими путями, в былые времена измеряли время выполнения циклов, но,надо
сказать, что этот метод весьма неточный и применим не ко всем процессорам.
Начиная с Pentium в архитектуру был
введен счетчик тактов (вообще говоря Intel его так не называет, и утверждает
что в будущем он может считать не такты, гарантируется лишь, что счетчик будет
монотонно возрастать) мы будем определять частоту процессора используя именно
этот счетчик. Счетчик тактов имеет разрядность 64 бита и увеличивается на 1 с
каждым тактом процессора, начиная с сигнала RESET#. Он продолжает счет при
выполнении инструкции HLT (собственно при выполнении этой инструкции процессор
вовсе не останавливается, а всего-навсего непрерывно выполняет инструкцию NOP,
которая, в свою очередь, является закамуфлированной инструкцией XCHG АХ, АХ
(опкод NOP - 10010000b, опкод XCHG AX,reg - 10010reg, что при использовании
регистра АХ (000) дает 10010000b, интересно, что фактически существует 32-
разрядный аналог NOP-a - XCHG ЕАХ, ЕАХ, на кодовую последовательность 66h,90h
процессор реагирует нормально). Считывание счетчика тактов можно запретить для
прикладных программ (CPL=3) установкой в 1 бита TSD в CR4 (в win считывание
запрещено). После выполнения инструкции RDTSC (если происходить ошибка
компиляции - db 0fh,031h) регистры EDX:EAX содержат текущее значение счетчика.
Измерение частоты при помощи RDTSC происходит следующим образом:
. Маскируются все прерывания,
кроме таймерного.
. Делается HLT.
. Считывается и сохраняется
значение счетчика.
. Снова делается HLT.
. Считывается значение
счетчика.
Разность значений, считанных в
пунктах 3 и 5, есть количество тактов за 1 тик таймера (частота прерываний
таймера примерно 18,2Гц).
Для большей наглядности представим
действия на временной диаграмме:
Рисунок 1 - Временная диаграмма
Момент запуска программы обозначен
как t0, штрихи на оси - моменты, когда происходит прерывание от таймера. Первый
HLT нужен для того чтобы преодолеть время t1, которое неизвестно заранее, так
как программа может быть запущена в произвольное время. Затем, в момент между
t1 и t2 считывается значение счетчика, оно сохраняется и снова делается HLT,
процессор будет простаивать до первого прерывания, то есть практически ровно
период t2, который и равен периоду прерываний от таймера. Таким образом, при
известном значении периода таймера 18,2 Гц, а также количества тактов за этот
период можно узнать точную тактовую частоту.
2. Оперативная память
.1 Методы определения объема
оперативной памяти
Перечислим функции BIOS для определения
оперативной памяти:
int 12h - получить размер базовой
памяти в кб (640 кб). Входных параметров не имеет.
На выходе:
- АХ - количество памяти.
Функция 88h прерывания INT 15h
сообщает объём имеющейся оперативной памяти свыше 1 Мбайта, т.е. начиная с
адреса 1 OOOOOh.
На входе:
На выходе:
- АХ - количество памяти в диапазоне
от 1 до 16 Мб в килобайтах.
Функция C7h прерывания INT 15h
возвращает карту распределения памяти.(является необязательной)
На входе:
- АН - C7h
- DS:SI - адрес карты памяти
На выходе:
- CF - сброшен, если успешно
Заполненная карта памяти
Формат карты памяти представлен в
таблице 2:
Таблица 2 - формат карты памяти
Смещение
|
Размер
|
Описание
|
0
|
WORD
|
Объём в килобайтах локальной памяти в пределах от 1 до 16 Мбайт
|
2
|
DWORD
|
Объём в килобайтах локальной памяти в пределах от 16 Мбайт до 4
Гбайт
|
6
|
DWORD
|
Объём в килобайтах системной памяти в пределах от 1 до 16 Мбайт
|
10
|
DWORD
|
Объём в килобайтах системной памяти в пределах от 1 до 16 Мбайт
|
14
|
DWORD
|
Объём в килобайтах системной памяти в пределах от 16 Мбайт до 4
Гбайт
|
18
|
DWORD
|
Объём в килобайтах кэшируемой памяти в пределах от 1 до 16 Мбайт
|
22
|
DWORD
|
Объём в килобайтах кэшируемой памяти в пределах от 16 Мбайт до 4
Гбайт
|
26
|
DWORD
|
Объём в килобайтах перед началом несистемной памяти в пределах
от 1 до 16 Мбайт
|
30
|
DWORD
|
Объём в килобайтах перед началом несистемной памяти в пределах
от 16 Мбайт до 4 Гбайт
|
34
|
WORD
|
Начальный сегмент крупнейшего свободного блока в диапазоне
адресов от COOOOh до DFFFFh
|
36
|
WORD
|
Размер крупнейшего свободного блока
|
38
|
DWORD
|
Зарезервировано
|
компьютерное оборудование
программная проверка
Функция E801h прерывания INT 15h
На входе:
AX -E801h
На выходе:
CF - сброшен, если успешно
AX - размер памяти в диапазоне от 1
до 16 Мбайт, выраженный в килобайтах.
BX - размер памяти свыше 16 Мбайт,
выраженный в блоках по 64 Кбайта.
CX - размер сконфигурированной
памяти в диапазоне от 1 до 16 Мбайт, выраженной в килобайтах.
DX - размер сконфигурированной
памяти свыше 16 Мбайт, выраженный в блоках по 64 Кбайта.
Данные методы не являются точными, и
не работают на процессорах, которые не поддерживают функцию APIC. Поэтому,
существует более точный метод рассмотренный далее.
2.2 Классический метод
Ставший уже классическим метод
определения объема заключается в следующем принципе:
Если что-то записать по
несуществующему физически адресу, а потом прочитать что-то с этого же адреса -
записанное и прочитанное значения естественно не совпадут (в 99,(9) процентах
случаев прочитаются нули). Сам алгоритм такой:
1. Инициализировать счетчик.
2. Сохранить в регистре значение из
памяти по адресу [счетчик].
3. Записать в память тестовое
значение (в нашем случае это будет AAh)
4. Прочитать из памяти.
5. Восстановить старое значение
по этому адресу.
6. Сравнить записанное и прочитанное
значение.
8. JMP пункт 2.
На первый взгляд все очень просто,
при практической же реализации приведенного алгоритма возникает множество
проблем: во-первых сама программа считающая память расположена в этой самой
памяти и рано или поздно она сама себя перезапишет тестовым значением. Этот
нюанс обычно решается так: программа выполняется в реальном режиме в пределах
первого мегабайта, счет же начинается с адресов выше мегабайта.
Этот метод порождает другую проблему
- в реальном режиме непосредственно доступен только этот самый один мегабайт.
Эта проблема решается путем применения «нереального» режима, он же Big
real-mode.
Как известно в процессоре каждый
сегментный регистр имеет скрытые или теневые (shadow parts) части, в которых в
защищенном режиме кэшируется дескриптор сегмента, для программиста они
невидимы. В защищенном режиме эти части обновляются всякий раз, когда в
сегментный регистр загружается новое значение, в реальном же режиме обновляются
только поля базового адреса сегмента. Если в защищенном режиме создать сегмент
с лимитом в 4Гб и загрузить в сегментный регистр такой селектор, после чего
переключиться в реальный режим, и, не следуя рекомендациям Intel, оставить
предел равным 4Гб - значение лимита сегмента сохранится, позволяя использовать
32-битные смещения. Алгоритм перехода в нереальный режим:
1. Создать дескриптор с базой
равной 0.
2. Установить предел сегмента в 4Гб.
3. Переключиться в защищенный режим.
4. Загрузить селектор сегмента в
какой-либо сегментный регистр.
5. Переключиться в реальный
режим.
После этих действий можно в реальном
режиме использовать конструкции типа:
ax, word ptr fs:[edx]
Где EDX может изменяться от нуля до
4Гб не вызывая никаких исключений защиты (в «настоящем» реальном режиме
превышение 64Кб вызывает исключение GP#) Фактически ЕDХ = целевой адрес,
поскольку база сегмента в FS=0
В защищенном режиме при включенной
страничной адресации считать память этим методом бесполезно, потому что кроме
основной память будет считаться еще и файл подкачки на винчестере, и в
перспективе можно всегда получать значение около 4Гб (зависит от ОС).
Нужно учесть один важный момент: в
некоторых книгах пишется, что в качестве «нереального» сегментного регистра
надо использовать FS или GS, так как другие регистры часто перезагружаются и
процессор якобы сбрасывает лимит в 64Кб после перезагрузки сегментного регистра
в реальном режиме. На практике оказывается совсем не так. Процессор не
использует поля лимитов в реальном режиме.
Для того чтобы избежать
дополнительных проблем, приведем пример с регистром FS:
. Установить «нереальный
режим».
. Открыть старшие адресные
линии (GateA20).
. Установить счетчик в
1048576 (1Mb).
. Цикл записи-чтения.
. Вывести значение счетчика.
. Закрыть вентиль А20.
. Выход.
После запуска программы следует
немного подождать, примерно в 2 раза больше времени, чем время, за которое
считает оперативную память BIOS при загрузке.
Существует способ многократного
увеличения скорости программы. Дело в том, что данный алгоритм считает память с
точностью до байта, такая точность вообще говоря не нужна, т.к. размер
современной планки памяти не может быть некратным мегабайту, поэтому можно
наращивать счетчик сразу прибавляя к нему значение 1048576, чего можно достичь
заменив в цикле записи-чтения команду inc есх на add есх, 1048576.
3. HDD
.1 Определение объема
Стандартный IDE контроллер,
применяемый в PC, поддерживает 2 канала, на каждом из которых, может быть 2
устройства АТА (то есть всего может быть 4 устройства). Каждый канал имеет свою
собственную часть пространства ввода-вывода. Для первого канала - lF0h-lF7h для
второго - 170h-177h. На данном этапе надо ввести понятие базового порта: в
общем, это лучше всего пояснить на примере:
Адреса портов формируются следующим
образом: базовый порт+смещение. Загрузив в базовый порт значение lFOh или 170h
можно больше не думать, о том с каким каналом нужно работать, потому что
функции портов, к примеру lF3h и 173h совпадают для разных каналов IDE.
То есть для первого канала базовым
портом является lFOh, для второго - 170h, загрузив один раз эти значения в
качестве базы остальные порты можно адресовать по смещениям относительно этих,
т.е. сделать так, что один и тот же код сможет работать с двумя каналами. В
нагрузку к этому контроллером используется еще пара портов 3F6h-3F7h для
первого канала и 376h-377h для его коллеги.
Теперь рассмотрим команды АТА.
Существует около 20 команд ATA, но на практике, обычно используются всего 10.
В данной работе рассматриваются
команды чтения/записи одного сектора, идентификации накопителя, а также команду
остановки винчестера (команда в стандарт не входит, но поддерживается
абсолютным большинством винчестеров), которая используются всеми ОС при
переводе компьютера в режим пониженного энергопотребления. Все примеры
относятся к Master устройству первого канала, но их можно легко адаптировать к
любой конфигурации.
Чтение секторов с винчестера
происходит следующим образом:
. Запретить прерывания с
записью 3F6h.
2. Дождаться готовности канала
читая бит BSY в порту lF7h.
. Выбрать устройство на
канале записью в lF6h.
. Дождаться DRDY=1 и BSY=0.
. Загрузить LBA адрес.
. Послать команду чтения
(20h).
. Дождаться BSY=0.
. Дождаться готовности обмена
данными (DRQ=1).
. Принять данные от
устройства через lFOh строковой операцией ввода из порта.
10. Разрешить прерывания от
устройства.
Хотя команда 20h является командой
чтения именно одного сектора, в абсолютном (возможно подавляющем) большинстве
винчестеров содержимое регистра lF2h (счетчик для групповых операций с секторами)
ни на что не влияет (в листинге он не фигурирует), однако могут встретиться
экземпляры, в которых обязательно нужно его устанавливать в 1.
4. PCI
.1 Нахождение PCI устройств
Сначала введем фундаментальное
понятие - конфигурационное пространство PCI (PCI configuration space).
Так называется массив регистров,
который имеется у каждого PCI- устройства, через них задаются различные
параметры (номера прерываний для устройства и т.д.). Общение с PCI-устройствами
происходит в основном через 2 32-битных порта 0CF8h и OCFCh. Через них можно
читать и писать в это самое конфигурационное пространство определенного
устройства.
Происходит этот процесс следующим
образом:
В регистре 0CF8h задается адрес
устройства на шине, после чего из OCFCh считываются (записываются) данные.
Координаты устройства на шине
(формат 0CF8h) выглядят так:
Рисунок 2 - Координаты устройства на
PCI шине
-й бит показывает достоверность
информации в регистре, там должен быть 1.Number - номер шины PCI. (их вполне
может быть несколько, например порт AGP использует не ту шину, к которой
подключены слоты PCI).Number - номер устройства на шине
Function Number - номер функции
устройства (здесь надо немного определится с терминологией, дело в том что под
функцией и подразумевается устройство, тогда так под устройством (device)
подразумевается абонент шины, то есть, если, например, есть карта в которой
совмещены 2 каких-либо устройства, то она будет восприниматься как одно
устройство с двумя функциями, причем даже такое «однофункциональное» как
видеокарта может иметь множество функций). Это деление на устройства и функции
в большинстве случаев просто логическое, «основное» устройство соответствует
функции 0.Number - номер регистра конфигурационного пространства который
следует прочитать (записать). (Вообще используется все поле до 0- го бита, но
поскольку обмен производится двойными словами (4 байта) то получается что
младшие 2 бита всегда нулевые).
Для того чтобы определить
производителя нужно посмотреть на карту конфигурационного пространства:
Рисунок 3 - Карта конфигурационного
пространства
Поля обозначенные желтым цветом
должны присутствовать у всех
устройств, именно там и хранится
информация о том, что это за устройство и кто его производитель. Нас будут
интересовать 2 поля:- код производителя.- код устройства.
Если же что-то прочитать из
пространства реально не существующего устройства, то прочитается специально
зарезервированное для этой цели значение OFFFFFFFFh.
Из этого всего можно сделать такой
вывод: чтобы найти все устройства нужно в цикле (изменяя Bus от нуля до 255,
dev от 0 до 31, func от нуля до 7) читать их конфигурационные пространства,
если прочиталось OFFFFFFFFh, значит устройства нет, если же прочиталось что-то
другое - устройство присутствует.
Вот пример процедуры используемой в
данной работе и читающей из конфигурационного пространства PCI.
Номер функции задается в BL, номер
устройства в ВЫ, функция в CL, и смещение (номер регистра) в СН.
PROC NEAR mov dx,0CF8h xor eax,eax
mov al,blah,80h;Бит достоверности в 1eax,16ah,bhah,3ah,clal,chal,0FCh;Сбросить
2 младших битаdx,eaxdx,0CFCheax,dxPCIENDP
К курсовой работе прилагается файл в
котором описаны коды VendorlD и DevicelD
Заключение
Данную работу можно считать
полноценной программой. Для ее написания необходимо понимание структуры ЭВМ и
ОС, в которой она должна работать, понимание принципа работы аппаратных
устройств.
Во время написания программы
возникали проблемы связанные с различным типом оборудования, и устройством ОС.
В итоге получилась простая программа
для определения подключенного к ЭВМ оборудования, которая, естественно,
нуждается в доработке и оптимизации.
Список литературы
2. В. Юров. «Assembler», 2-е изд. - СПб.:Питер, 2006. - 534
с.
. Эндрю Таненбаум «Архитектура компьютера» 5-е изд. - СПб.:
Питер, 2007. - 844 с.