Сообщений 8 Оценка 200 Оценить |
Исходный код к статье - smart.zip
Большинство современных жестких дисков поддерживают технологию 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)); ifnot 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 = packedrecord 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 = packedrecord // Используется для определения "подкоманды" 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 = packedrecord// Размер буфера в байтах. 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 = packedrecord// Код ошибки драйвера. 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 = packedrecord// Размер 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 = packedrecord 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 = packedrecord 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)) // Команда чтения атрибутов выполнена успешно. // Пытаемся прочитать пороговые значения атрибутов. elseifnot 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' |
Исходный код программы, демонстрирующей основные принципы работы с S.M.A.R.T., вы также можете скачать по адресу: http://fominov.boom.ru/
Эта программа тестировалась в операционных системах Windows 98 и Windows XP. Программа не работает со SCSI-приводами и RAID-массивами.
Сообщений 8 Оценка 200 Оценить |