(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

S.M.A.R.T. + Delphi

Источник: rsdn
Алексей Фоминов

Автор: Алексей Фоминов

Большинство современных жестких дисков поддерживают технологию 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'

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 30.03.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
Oracle Database Standard Edition 2 Named User Plus License
Quest Software. TOAD for Oracle Edition
Quest Software. Toad for SQL Server Development Suite
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Мастерская программиста
Проект mic-hard - все об XP - новости, статьи, советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100