(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)
SmartBear Collaborator - Concurrent User License (Includes 1 Year Maintenance)
Bamboo
WinRAR 5.x 1 лицензия
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Реестр Windows. Секреты работы на компьютере
СУБД Oracle "с нуля"
Краткие описания программ и ссылки на них
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100