Алексей Фоминов
Автор: Алексей Фоминов
Большинство современных жестких дисков поддерживают технологию S.M.A.R.T. - Self-Monitoring, Analysis and Reporting Technology (Технология самодиагностики, анализа и отчёта), благодаря которой возможно предсказать появление сбоев в работе жесткого диска, и позволить пользователю своевременно сделать резервную копию диска или же полностью его заменить.
Существует множество программ, дающих возможность следить за состоянием винчестера посредством технологии S.M.A.R.T., однако большинство из них - платные. Например, Hard Drive Inspector 1.6 стоит $29,95; Active SMART 2.4 - $24,95; SiGuardian 1.6 - $14.
В своей статье я постараюсь рассказать о том, как встроенными средствами операционной системы Windows и с помощью языка Object Pascal вы сможете написать программу подобного рода и потом использовать её абсолютно бесплатно.
Я в основном буду опираться на документ "Small Form Factor Committee. Specification for Self-Monitoring, Analysis and Reporting Technology", изданный в апреле 1996 года и утверждённый такими компаниями, как Compaq Computer Corporation, Hitachi Ltd., IBM Storage Products Company, Maxtor Corporation, Quantum Corporation, Seagate Technology, Toshiba Corporation и Western Digital Corporation. Большинство положений этого документа актуально и по сей день.
Следует также отметить, что на сегодняшний день стандарт на технологию S.M.A.R.T. не утверждён. Однако в стандарте ATA, начиная с версии 3, описан обязательный минимум для технологии S.M.A.R.T., и если ваш жёсткий диск соответствует ATA (3-8), то он будет поддерживать данную технологию в соответствии с этим стандартом.
Анализ состояния диска мы будем проводить посредством изучения атрибутов S.M.A.R.T. Максимальное количество атрибутов на одном диске зависит от производителя и не превышает 30. Атрибуты могут принимать значения в диапазоне от 1 до 253. Для каждого атрибута существует пороговое значение, основываясь на котором можно судить о близости момента выхода привода из строя.
Перейдём непосредственно к коду.
Для начала необходимо создать дескриптор для работы с функциями S.M.A.R.T. посредством DeviceIoControl.
function OpenSMART(DrvNum:Byte): THandle;
var
hSMARTIOCTL: THandle;
begin
// Если у нас Windows семейства NT
if OSVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT then
hSMARTIOCTL := CreateFile(PChar('\\.\PhysicalDrive' + inttostr(DrvNum)),
GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, 0, 0);
else // Если у нас Windows семейства 9х
begin
hSMARTIOCTL := CreateFile('\\.\SMARTVSD', 0, 0, nil, CREATE_NEW, 0, 0);
if hSMARTIOCTL = INVALID_HANDLE_VALUE then
ShowMessage('Невозможно открыть SMARTVSD, код ошибки: '
+ inttostr(GetLastError) + ' - ' + SysErrorMessage(GetLastError))
end;
result := hSMARTIOCTL;
end;
|
В качестве передаваемого параметра данной функции выступает номер физического диска. Максимальное количество IDE-дисков - 4. Этот параметр игнорируется, если программа запущена в операционной системе Win9х.
Немного поясню. В операционных системах Windows 95 OSR, 98, 98SE, Me за работу со S.M.A.R.T. отвечает драйвер виртуального устройства SMARTVSD.VXD, который находится в папке …\WINDOWS\SYSTEM\IOSUBSYS. В операционных системах линейки NT работа с устройствами (как физическими, так и виртуальными) построена иным образом, поэтому при работе с S.M.A.R.T. в этих операционных системах необходимо открывать дескриптор доступа к физическому диску.
Переменная OSVersionInfo является глобальной, и ее тип определен как TOSVersionInfo. Она заполняется до вызова функции OpenSMART.
Получив дескриптор S.M.A.R.T., мы должны определить версию S.M.A.R.T. IOCTL. За это отвечает следующая функция:
function GetVersionSMART(hSMARTIOCTL: THandle):TGetVersionOutParams;
var
VersionParams: TGetVersionOutParams;
cbBytesReturned: DWORD;
begin
ZeroMemory(@VersionParams, sizeof(TGetVersionOutParams));
if not DeviceIoControl (hSMARTIOCTL, DFP_GET_VERSION, nil, 0,
@VersionParams, sizeof(VersionParams), cbBytesReturned, nil)
then
ShowMessage(SysErrorMessage(GetLastError));
Result := VersionParams;
end;
|
В качестве параметра передаётся дескриптор S.M.A.R.T. Функция возвращает структуру типа TGetVersionOutParams.
type
TGetVersionOutParams = packed record
bVersion: BYTE; // Бинарная версия драйвера.
bRevision: BYTE; // Бинарная подверсия драйвера.
bReserved: BYTE; // Не используется.
bIDEDeviceMap: BYTE; // Битовый массив IDE - устройств.
fCapabilities: DWORD; // Битовая маска возможностей драйвера.
// Зарезервировано для будущего использования.
dwReserved: array [0..3] of DWORD;
end;
GETVERSIONOUTPARAMS = TGetVersionOutParams;
PGetVersionOutParams = ^TGetVersionOutParams;
|
Константа DFP_GET_VERSION = $00074080 является командой получения версии S.M.A.R.T. IOCTL.
Следующий шаг - найти IDE-диски и попытаться активировать на них S.M.A.R.T.
Перед тем как рассказать о функции, отвечающей за активацию S.M.A.R.T., необходимо описать структуры, которые нам понадобятся для её реализации.
type
TIDERegs = packed record
// Используется для определения "подкоманды" S.M.A.R.T.
bFeaturesReg: BYTE;
// Регистр количества секторов IDE
bSectorCountReg: BYTE;
// Регистр номера сектора IDE
bSectorNumberReg: BYTE;
// Младший разряд номера цилиндра IDE
bCylLowReg: BYTE;
// Старший разряд номера цилиндра IDE
bCylHighReg: BYTE;
// Регистр диска/головки IDE
bDriveHeadReg: BYTE;
// Фактическая команда IDE
bCommandReg: BYTE;
// Зарезервировано для будущего использования. Должно быть 0.
bReserved: BYTE;
end;
IDEREGS = TIDERegs;
PIDERegs = ^TIDERegs;
|
Тип TIDERegs описывает регистры IDE-диска. Допустимые значения параметра bCommandReg:
const
// Возвращает ID сектора для ATAPI.
IDE_ATAPI_ID = $A1;
// Возвращает ID сектора для ATA.
IDE_ID_FUNCTION = $EC;
// Выполняет команду SMART. Требует правильных значений для параметров
// bFeaturesReg, bCylLowReg, bCylHighReg.
IDE_EXECUTE_SMART_FUNCTION = $B0;
|
Параметры bCylLowReg и bCylHighReg должны быть обязательно равны $4F (SMART_CYL_LOW) и $C2 (SMART_CYL_HI) соответственно.
type
TSendCmdInParams = packed record
// Размер буфера в байтах.
cBufferSize: DWORD;
// Структура со значениями регистров диска.
irDriveRegs: TIDERegs;
// Физический номер диска для выполнения команд.
bDriveNumber: BYTE;
// Зарезервировано для будущего расширения.
bReserved: array [0..2] of Byte;
// Зарезервировано для будущего использования.
dwReserved: array [0..3] of DWORD;
// Входной буфер.
bBuffer: array [0..0] of Byte;
end;
SENDCMDINPARAMS = TSendCmdInParams;
PSendCmdInParams = ^TSendCmdInParams;
|
Тип TSendCmdInParams содержит входные параметры для функции, которая посылает команды диску.
type
TDriverStatus = packed record
// Код ошибки драйвера.
bDriverError: Byte;
// Содержание регистра ошибки. Правильно, только когда
// bDriverError = SMART_IDE_ERROR (1).
bIDEStatus: Byte;
// Зарезервировано для будущего расширения.
bReserved: array [0..1] of Byte;
// Зарезервировано для будущего расширения.
dwReserved: array [0..1] of DWORD;
end;
DRIVERSTATUS = TDriverStatus;
PDriverStatus = ^TDriverStatus;
|
Тип TDriverStatus предназначен для отслеживания ошибок драйвера. Если параметр bDriverError содержит значение, отличное от нуля, значит, произошла ошибка.
type
TSendCmdOutParams = packed record
// Размер bBuffer в байтах
cBufferSize: DWORD;
// Структура состояния драйвера.
DriverStatus: TDriverStatus;
// Буфер произвольной длины для сохранения данных, прочитанных с диска.
bBuffer: array [0..0] of BYTE;
end;
SENDCMDOUTPARAMS = TSendCmdOutParams;
PSendCmdOutParams = ^TSendCmdOutParams;
|
Тип TSendCmdOutParams предназначен для некоторых команд, которые возвращают через него данные.
Теперь собственно функция активации S.M.A.R.T.:
function DoEnableSMART (hSMARTIOCTL: THandle; pSCIP: PSENDCMDINPARAMS;
pSCOP: PSENDCMDOUTPARAMS; bDriveNum: BYTE): BOOL;
var
lpcbBytesReturned: DWORD;
begin
pSCIP.cBufferSize:= 0;
// Активировать S.M.A.R.T.
pSCIP.irDriveRegs.bFeaturesReg := SMART_ENABLE_SMART_OPERATIONS ($D8);
pSCIP.irDriveRegs.bSectorCountReg := 1;
pSCIP.irDriveRegs.bSectorNumberReg := 1;
pSCIP.irDriveRegs.bCylLowReg := SMART_CYL_LOW;
pSCIP.irDriveRegs.bCylHighReg := SMART_CYL_HI;
// Вычисляем номер накопителя.
pSCIP.irDriveRegs.bDriveHeadReg := $A0 or ((bDriveNum and 1) shl 4);
// Выполнить функцию S.M.A.R.T.
pSCIP.irDriveRegs.bCommandReg := IDE_EXECUTE_SMART_FUNCTION;
pSCIP.bDriveNumber := bDriveNum;
result := DeviceIoControl (hSMARTIOCTL, DFP_SEND_DRIVE_COMMAND, pSCIP,
sizeof(SENDCMDINPARAMS) - 1, pSCOP, sizeof(SENDCMDOUTPARAMS) - 1,
lpcbBytesReturned, nil);
end;
|
Следует сказать пару слов о передаваемых параметрах. В качестве параметров pSCIP и pSCOP передаются обнулённые структуры TSendCmdInParams и TSendCmdOutParams, соответственно. Параметр bDriveNum - это номер диска в пределах от 0 до 3. После заполнения необходимых параметров структуры PSENDCMDOUTPARAMS выполняем функцию DeviceIoControl с управляющим кодом DFP_SEND_DRIVE_COMMAND ($0007C084). Если функция выполнена успешно, возвращаемый результат - TRUE.
Приведу код, который определяет тип диска и пытается активировать S.M.A.R.T.:
for i := 0 to 3 do
// Количество и тип устройств определяется параметром bIDEDeviceMap
// структуры TGetVersionOutParams
begin
// Если устройство с номером "i" - IDE, передаём ему команды.
if VersionParams.bIDEDeviceMap shr i and 1 = 1 then
begin
// Игнорируем ATAPI - устройства.
if VersionParams.bIDEDeviceMap shr i and $10 = 0 then
begin
ZeroMemory(@scip, sizeof(scip)); // Обнуляем TSendCmdInParams
ZeroMemory(@OutCmd, sizeof(OutCmd)); // Обнуляем TSendCmdOutParams
// Пытаемся активировать SMART.
if DoEnableSMART(hSMARTIOCTL, @scip, @OutCmd, i) then
ShowMessage ('Команда запуска S.M.A.R.T. выполнена, диск: '
+ inttostr(i))
else ShowMessage ('Команда запуска S.M.A.R.T. не выполнена, диск: '
+ inttostr(i));
end;
end;
end;
|
Поговорим непосредственно о чтении атрибутов S.M.A.R.T. Как уже упоминалось в начале статьи, чтобы провести анализ состояния привода, необходимо знать текущие и пороговые значения атрибутов. Создадим два типа для чтения этих значений. Первый - для чтения значений атрибутов:
type
TDriveAttribute = packed record
bAttrID: BYTE; // Идентификатор атрибута
wStatusFlags: WORD; // Флаги состояния
bAttrValue: BYTE; // Текущее нормализованное значение
bWorstValue: BYTE; // Худшее значение
bRawValue: array [0..5] of BYTE; // Текущее ненормализованное значение
bReserved: BYTE; // Зарезервировано
end;
DRIVEATTRIBUTE = TDriveAttribute;
PDriveAttribute = ^TDriveAttribute;
|
Параметр wStatusFlags может принимать следующие значения либо их комбинации:
const
// Жизненно важный
PRE_FAILURE_WARRANTY = $01;
// Коллекция реального времени
ON_LINE_COLLECTION = $02;
// Атрибут, отражающий производительность диска
PERFORMANCE_ATTRIBUTE = $04;
// Атрибут, отражающий частоту появления ошибок
ERROR_RATE_ATTRIBUTE = $08;
// Счётчик событий
EVENT_COUNT_ATTRIBUTE = $10;
// Самосохраняющийся атрибут
SELF_PRESERVING_ATTRIBUTE = $20;
|
Второй тип предназначен для чтения пороговых значений:
type
TAttrThreshold = packed record
bAttrID: BYTE; // Идентификатор атрибута
bWarrantyThreshold: BYTE; // Пороговое значение
bReserved: array [0..9] of BYTE; // Зарезервировано
end;
ATTRTHRESHOLD = TAttrThreshold;
PAttrThreshold = ^TAttrThreshold;
|
Функция чтения значений атрибутов выглядит следующим образом:
function DoReadAttributesCmd (hSMARTIOCTL: THandle; pSCIP: PSENDCMDINPARAMS;
pSCOP: PSENDCMDOUTPARAMS; bDriveNum: BYTE): BOOL;
var
cbBytesReturned: DWORD;
begin
// Константа = 512
pSCIP.cBufferSize := READ_ATTRIBUTE_BUFFER_SIZE;
// Константа = $D0
pSCIP.irDriveRegs.bFeaturesReg := SMART_READ_ATTRIBUTE_VALUES;
pSCIP.irDriveRegs.bSectorCountReg := 1;
pSCIP.irDriveRegs.bSectorNumberReg := 1;
pSCIP.irDriveRegs.bCylLowReg := SMART_CYL_LOW;
pSCIP.irDriveRegs.bCylHighReg := SMART_CYL_HI;
// Вычисляем номер накопителя.
pSCIP.irDriveRegs.bDriveHeadReg := $A0 or ((bDriveNum and 1) shl 4);
pSCIP.irDriveRegs.bCommandReg := IDE_EXECUTE_SMART_FUNCTION;
pSCIP.bDriveNumber := bDriveNum;
result := DeviceIoControl (hSMARTIOCTL, DFP_RECEIVE_DRIVE_DATA,
pSCIP, sizeof(SENDCMDINPARAMS) - 1, pSCOP, sizeof(SENDCMDOUTPARAMS)
+ READ_ATTRIBUTE_BUFFER_SIZE - 1, cbBytesReturned, nil);
end;
|
Думаю, смысл передаваемых в функцию параметров объяснять не надо. Они аналогичны параметрам функции DoEnableSMART, как и большинство параметров структуры PSENDCMDINPARAMS. Различия лишь в размере буфера и в подкоманде S.M.A.R.T. В качестве управляющего кода функции DeviceIoControl передаётся константа DFP_RECEIVE_DRIVE_DATA ($0007C088).
Функция для чтения пороговых значений (назовём её DoReadThresholdsCmd) будет выглядеть аналогично, за тем лишь исключением, что параметр bFeaturesReg будет иметь значение $D1.
Пример чтения текущих и пороговых значений атрибутов диска "i" приведён ниже:
var
// Два буфера для получения данных
AttrOutCmd, ThreshOutCmd: array [0..(sizeof(SENDCMDOUTPARAMS) - 1)
+ (READ_ATTRIBUTE_BUFFER_SIZE - 1) ] of BYTE;
bSuccess: bool;
begin
ZeroMemory(@AttrOutCmd, sizeof(AttrOutCmd));
ZeroMemory(@ThreshOutCmd, sizeof(ThreshOutCmd));
bSuccess := DoReadAttributesCmd (hSMARTIOCTL, @scip,
PSENDCMDOUTPARAMS(@AttrOutCmd), i);
if bSuccess = false then ShowMessage(
'Ошибка при выполнении команды чтения атрибутов S.M.A.R.T. на диске: '
+ inttostr(i))
// Команда чтения атрибутов выполнена успешно.
// Пытаемся прочитать пороговые значения атрибутов.
else if not DoReadThresholdsCmd (hSMARTIOCTL, @scip,
PSENDCMDOUTPARAMS(@ThreshOutCmd), i)
then
ShowMessage(
'Ошибка при выполнении команды чтения пороговых значений '
+ 'атрибутов S.M.A.R.T. на диске: ' + inttostr(i));
if bSuccess <> false then
// Выводим информацию об атрибутах и их пороговых значениях
DoPrintData(@PSENDCMDOUTPARAMS(@AttrOutCmd).bBuffer,
@PSENDCMDOUTPARAMS(@ThreshOutCmd).bBuffer);
end;
|
Неизвестной для нас здесь является процедура DoPrintData.
procedure TForm1.DoPrintData(pAttrBuffer: PCHAR; pThrsBuffer: PCHAR);
var
i: integer;
pDA: PDRIVEATTRIBUTE;
pAT: PATTRTHRESHOLD;
begin
Label8.Caption := 'Версия структуры атрибутов: '
+ inttostr(WORD(pAttrBuffer[0]));
Label9.Caption := 'Версия структуры пороговых значений атрибутов: '
+ inttostr(WORD(pThrsBuffer[0]));
pDA := PDRIVEATTRIBUTE(@pAttrBuffer[2]);
pAT := PATTRTHRESHOLD(@pThrsBuffer[2]);
for I := 0 to 29 do
begin
// Выводим информацию:
// Идентификатор атрибута
StringGrid1.Rows[i + 1].Strings[0] := inttostr(pDA.bAttrID);
// Его название
StringGrid1.Rows[i + 1].Strings[1] := pAttrNames[pDA.bAttrID];
// Текущее значение
StringGrid1.Rows[i + 1].Strings[2] := inttostr(pDA.bAttrValue);
// Пороговое значение
StringGrid1.Rows[i + 1].Strings[3] := inttostr(pAT.bWarrantyThreshold);
// Худшее значение
StringGrid1.Rows[i + 1].Strings[4] := inttostr(pDA.bWorstValue);
inc(pDA);
inc(pAT);
end;
end;
|
В данной процедуре переменная pAttrNames - это массив строковых значений, содержащих название атрибута в соответствии со своим порядковым номером в массиве.
Вот, собственно говоря, и всё. Описание атрибутов можно найти в Интернете или послав запрос производителю вашего привода. Пару слов скажу лишь об атрибуте под названием Temperature (Температура). Его идентификатор - 194 или 231. Как ясно следует из его названия, он показывает температуру винчестера, которая измеряется в градусах Цельсия. Чтобы её вычислить, воспользуйтесь нижеприведённым кодом:
if (pDA.bAttrID = 194) or (pDA.bAttrID = 231) then
Label7.Caption := 'Температура: '
+ inttostr((84 - (pDA.bAttrValue - 1) div 3)) + #176 + 'C'
|
Ссылки по теме