나래온 툴 내부 구조 – 7. SATA 장치에서 상태 정보(SMART) 가져오기

필요성

사실 이 파트에서는 필요성을 굳이 설명할 이유가 있을까 싶다. 디스크 관리 도구의 존재 의의이기 때문이다. 제품의 수명을 관리하고, 사용자가 놓치기 쉬운 이상 증상들을 확인하는 일은 모두 여기서 시작된다. 따라서 이번 글에서는 글을 다른 구성으로 가고자 한다.

S.M.A.R.T: 자가 진단, 분석, 보고 기술

이 기술은 이름이 말하는 대로, 저장 장치가 스스로 분석하고 진단해서 문제가 있는지 없는지를 판단하는 기술이다. 말은 화려하지만, 실제로는 무슨 최첨단 딥 러닝 기술로 어쩌고 하는 기술은 아니다. 개발 당시에 축적된 기술과 데이터로, 기존에 컨트롤러가 갖고 있는 여러 센서 입력 값들을 이용해 스스로 진단하도록 미리 루틴을 짜둔 것이다.

옛날에 하드 디스크에 많은 문제가 있었던 건 다들 기억할 것이다. 파킹 프로그램을 켜지 않으면 마음대로 끄지도 못했던 시절이었다. 예고 없이 가버리는 하드 디스크들 때문에, 사용자들이 준비라도 하게 만들고자 업체들은 각각 자가 진단 기술을 개발하기 시작했다. IBM의 ‘고장 예측 분석(PFA)’ 기술이나, 시게이트/퀀텀 등이 개발한 ‘IntelliSafe’ 기술이 그것이었다. 당연하게도 이들 기술은 표준화 되지 않았기 때문에, 각 업체가 제공하는 소프트웨어가 있어야만 진단 값을 읽을 수 있었다. 이 기술들 중 ‘IntelliSafe‘ 기술이 표준에 받아들여져 S.M.A.R.T 표준이 되었는데, 아마 이 당시부터 ‘S.M.A.R.T’라고 부르게 된 것 같다.

그런데 문제가 있었다. 위의 IntelliSafe 링크를 눌러서 보면 알겠지만, 이 기술에 데이터의 표준 테이블 같은 건 존재하지 않았다. 그렇다. 위에서 필자가 저 기술이 다루는 건 ‘기존에 컨트롤러가 갖고 있는 여러 입력 값들’이라 했었다. 결국 ATA 표준에서 다음과 같은 재앙이 발생한다.

맨 첫 줄의 [0..361] Vendor specific이 제일 중요한 부분이다 (출처: ACS-3 표준)

Vendor specific은 이름이 말해주듯 ‘알아서 구현하세요’다. 그런데 진단 관련 내용은 저 부분에 다 들어있다. 이러다 보니 그냥 컨트롤러가 가지고 있는 정보를 어떤 방식으로든 표시만 해주면 되는 게 표준이다. 우리가 CrystalDiskInfo와 같은 툴에서 익숙한 ID, 현재 값, 최악 값, RAW 값 등은 표준에 없고, 그렇게 표시할 필요조차 없다. 실제로도 어떤 컨트롤러는 한 항목이 6바이트고, 어떤 컨트롤러는 4바이트며, 어떤 컨트롤러는 한 ID를 반으로 나눠 두 항목이 공유하는 경우도 있다. 세 번째 케이스는 툴 개발자를 미치게 하는 존재로, CrystalDiskInfo에서도 전혀 대응하지 못하고 있다.

물론 최소한의 규칙이 없진 않다. 왜냐면 진단 시스템을 개발하는 건 완제품을 파는 회사들(델, HP 등)의 몫인데, 장치마다 완전히 형식이 달라서야 관리가 되겠는가? 이래서 하드 디스크 시절에는 SMART 내용이 완제품 업체의 기준에 맞추어 개발되었다 한다. 그러다 보니 ID, 현재 값, 최악 값, RAW 값 등 최소한의 규칙이 생겨난 것으로 보인다.

Dell 사의 진단 시스템 (출처: Dell)

하지만 그 외에는 대부분이 다르다. 필자가 OEM이 아닌 리테일 제품만 다뤄서 그런지는 모르겠으나, 많은 제품들은 형식 외에는 그냥 ID, 현재 값, 최악 값, RAW 값을 알려주는 데 그친다. 각 ID가 무엇을 뜻하는지? RAW는 환산을 어떻게 해야 하는지? 그런 건 다 툴 개발자가 찾아내야 하는 일이다. 업체의 입장에서는 민감한 자료이기 때문에, 되도록 각 항목의 해석을 공개하려 하지 않는다. 운이 좋다면 삼성이나 인텔의 사례같이 일부 공개되는 경우도 있지만, 아주 일부의 운 좋은 사례일 뿐이다. 그것들을 도합해 어떤 결과를 도출해야 하는지도, OEM의 경우는 완제품 벤더가 디바이스 벤더로부터 이런 정보를 제공 받겠지만, 우리들의 경우는 그저 툴 개발자의 몫이다.

문서가 주어지는 아주 일부를 제외하면, 분석은 샘플이 제공되는 경우도 있고, 아니라면 사서 해야 한다. 각 비율은 대략 반반이라고 보면 된다. 주로 유통사 수준에서 툴 지원 관련 협조가 진행되는 경우가 많으므로, 개발 내용에 대한 협조는 기대할 수 없다. 세계적으로 유명한 CrystalDiskInfo의 경우도, 많은 제품을 자비로 구입해서 분석한다.

입력

입력은 이 혼돈의 도가니에서 유일하게 제대로 규정된 부분이다. PreviousTaskFile과 데이터 버퍼는 비우면 되고, 나머지는 다음과 같다. (N/A 역시 비우라는 뜻)

  • Passthrough 구조체의 AtaFlags: ATA_FLAGS_DATA_IN
  • CurrentTaskFile[0]: 0xD0 (Threshold: 0xD1)
  • CurrentTaskFile[1]: N/A
  • CurrentTaskFile[2]: N/A
  • CurrentTaskFile[3]: 0x4F
  • CurrentTaskFile[4]: 0xC2
  • CurrentTaskFile[5]: N/A
  • CurrentTaskFile[6]: 0xB0
  • CurrentTaskFile[7]: N/A

이상한 게 있다. CurrentTaskFile[0](Feature)에 D0은 뭐고 D1은 뭔가? D1은 임계 값을 받아올 때 쓰는 값이다. 임계 값은 현재 값의 하한선을 정의하는데, 현재 값이 임계 값 밑으로 떨어지면 비정상이라는 얘기다. 표준에는 다음과 같이 나와있다.

feature field의 내용 (출처: ACS-3)

D1h(=0xD1)은 Obsolete라고 되어있다. 즉 과거 표준에 있었다가 사라진 내용이라 구현할 필요가 없다는 거다. 하지만 OEM의 요구였던 것인지, 임계 값 구현은 꾸준하게 최근 SSD에도 들어간다. 사실상 리테일 표준 진단 툴인 CrystalDiskInfo에 꾸준히 임계 값이 들어가서 그런지도 모르겠다. 어쨌든 그러므로 우리는 비슷한 내용의 두 개의 명령을 보내 두 결과 모두를 받아올 필요가 있다.

이제 ioctl로 두 명령어를 보내면, 진짜 문제가 시작된다.

출력

표준에서 규정한 유일한 정보는 ‘[0..361] Vendor specific’이다. 이 정보를 해석하는 건 처음부터 끝까지 여러분의 몫이다. 다행히도 많은 제품들이 다음의 구조를 따르고 있다. 출처는 CrystalDiskInfo 소스다.

    typedef    struct _SMART_ATTRIBUTE
    {
        BYTE    Id;
        WORD    StatusFlags;
        BYTE    CurrentValue;
        BYTE    WorstValue;
        BYTE    RawValue[6];
        BYTE    Reserved;
     } SMART_ATTRIBUTE;

    typedef    struct _SMART_THRESHOLD
    {
        BYTE    Id;
        BYTE    ThresholdValue;
        BYTE    Reserved[10];
    } SMART_THRESHOLD;

하지만 중요한 건, ‘아니어도 상관 없다’는 것이므로 상당히 주의해서 접근해야 한다. 다른 포맷을 쓰는 제품들도 다수 존재하므로, 개인적으로는 캐스팅이나 copy 명령으로 한 번에 전체를 해결하려 하는 건 권장하지 않는다. 제품을 실제로 확인해가며 개별적으로 해석하는 것이 오류를 줄이는 길이다. 이 과정을 넘어서면, 각 Id가 나타내는 값이 무엇인지를 알아내야 하는데, 그런 건 당연히 알려주지 않으므로 형식이라도 대부분 비슷하다는 수준에서 만족하자.

구현

나래온 툴에서 두 명령어를 보내는 부분은 각각 TATACommandSet의 SetBufferAndSMARTReadData와 SetBufferAndSMARTReadThreshold다. 각 함수의 자세한 내용은 입력 부분에서 설명했으므로 생략한다.

procedure TATACommandSet.SetBufferAndSMARTReadData;
begin
  SetInnerBufferToSMARTReadData;
  SetOSBufferByInnerBuffer;
  IoControl(TIoControlCode.ATAPassThroughDirect, IoOSBuffer);
end;

procedure TATACommandSet.SetBufferAndSMARTReadThreshold;
begin
  SetInnerBufferToSMARTReadThreshold;
  SetOSBufferByInnerBuffer;
  IoControl(TIoControlCode.ATAPassThroughDirect, IoOSBuffer);
end;

결과들을 받아와 해석하는 부분은 TATABufferInterpreter의 BufferToSMARTValueList와 BufferToSMARTThresholdValueList다. 두 함수 내에서 일어나는 일은 출력에서 설명한 내용과 동일하다.

function TATABufferInterpreter.BufferToSMARTValueList(
  const Buffer: TSmallBuffer): TSMARTValueList;
const
  SMARTStartPadding = 2;
  SMARTValueLength = 12;
  function CalculateRow(const CurrentRow: Integer): Integer;
  begin
    result := (CurrentRow * SMARTValueLength) + SMARTStartPadding;
  end;
var
  CurrentRow: Integer;
  MaxRow: Integer;
begin
  SMARTValueList := TSMARTValueList.Create;
  BufferInterpreting := Buffer;
  MaxRow :=
    (Length(BufferInterpreting) - SMARTStartPadding) div SMARTValueLength;
  for CurrentRow := 0 to MaxRow do
    if not IfValidSMARTAddToList(CalculateRow(CurrentRow)) then
      break;
  result := SMARTValueList;
end;

function TATABufferInterpreter.BufferToSMARTThresholdValueList(
  const Buffer: TSmallBuffer): TSMARTValueList;
const
  SMARTStartPadding = 2;
  SMARTValueLength = 12;
var
  CurrentRow: Integer;
begin
  SMARTValueList := TSMARTValueList.Create;
  BufferInterpreting := Buffer;
  for CurrentRow := 0 to
    (Length(BufferInterpreting) - SMARTStartPadding) div SMARTValueLength do
    IfValidSMARTThresholdAddToList(
      (CurrentRow * SMARTValueLength) + SMARTStartPadding);
  result := SMARTValueList;
end;

이번 시간에는 SMART 값과 관련된 내용을 알아보았고, 다음 시간에는 NVMe와 관련된 내용을 알아보려 한다. 나래온 툴의 SMART 값 해석에 대한 자세한 내용이 궁금한 사람도 있을 것이다. 하지만 그 내용은 깔끔한 규칙이 있는 게 아니라서 정리하기엔 너무 엉망이라 일단 ‘제품마다 다르다’는 정도로만 알면 되겠다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다