물리 디스크와 통신하기(NVM express, ATA, SAT, SCSI)

들어가기 전에 미리 이야기하지만, 리눅스에 대해서는 이미 수많은 문서가 있으므로 이 시리즈에서는 윈도에 대해서만 다룬다. 유저 레벨 프로그래밍은 당연하게도 윈도에 대한 문서가 더 많다. 그러나 특이하게도 저수준 프로그래밍으로 들어가면 윈도 방면 문서는 거의 존재가 사라지는 수준이다. 리눅스 관련해서는 굳이 이미 있는 글들과 별 차이가 없을 글을 올릴 가치가 느껴지지 않기 때문에 생략한다.

프로그래밍을 하다 보면 디스크의 모델명이나, 시리얼, 용량 등을 알아내야 할 때가 있다. 그 외에도 윈도에서 관련 API를 제공하지 않아 물리 디스크와 직접 통신을 필요로 하는 일들이 존재한다.

용도를 기본적인 정보(모델명, 시리얼, 용량)를 수신하는 데 한정하면, 안정적인 방법이며 유저 모드에서 편하게 작업할 수 있는 방법은 WMI가 있다. 그러나 그 방식은 여기서 다루지 않는다. 이 부분에 대한 자세한 정보는 Win32_DiskDrive(https://msdn.microsoft.com/en-us/library/windows/desktop/aa394132%28v=vs.85%29.aspx)를 참고하라. 다만 다른 방식들에 비해 WMI 특성상 속도가 약간 느리다는 단점이 있다. 또한 여러 가지 선택 사항이 있고 그 중 윈도가 지원이 미비한 쪽 정보를 제공할 경우 정확한 정보를 얻을 수 없다. 예를 들어 2015년 12월 시점에서 NVMe(윈도는 SCSI를 고수), 외장 하드 케이스에 들어간 저장 장치(역시 윈도는 SCSI를 고수)가 있다.

이 시리즈에서 다룰 방식은, 드라이버와 직접 통신하는 방식이다. 윈도에서는 DeviceIoControl이라는 함수를 이용하면 된다.

대부분의 사람들이 ReadFile이나 WriteFile은 써 보았을 것이다. fread, fwrite의 구현에 많이 등장하며 그 외에도 적지 않은 곳에 쓰이므로 모르는 사이에라도 ReadFile이나 WriteFile 사용해 본 사람은 많기 때문이다. 그러나 제 3의 함수인 DeviceIoControl을 사용해 본 사람은 적을 듯 하다.

DeviceIoControl에 대한 MSDN의 설명은 다음과 같다.

“컨트롤 코드를 지정한 디바이스 드라이버로 직접 전송하여, 해당하는 장치가 해당 동작을 하도록 합니다.”

말 그대로 읽기/쓰기 외에 모든 것을 담당하는 명령이다. 물론 읽기/쓰기도 ReadFile/WriteFile이라는 별도의 함수가 존재할 뿐, 패스스루로 읽기/쓰기 명령을 전달하면 불가능하지 않다.

각설하고 MSDN의 설명을 읽어보면 우리가 알아내야 할 것은 두 가지다. 컨트롤 코드, 장치. 이들을 아래에 하나씩 살펴보자.

  1. 지정할 디바이스 드라이버

디바이스 드라이버라고 하면 감이 안 잡히겠지만 쉽다. C 드라이브의 부모 저장장치 정보를 알아내고 싶다, 운영체제의 0번 저장장치 정보를 알아내고 싶다 정도면 된다. 내가 알고 싶은 대상 그 자체다.

  1. 컨트롤 코드

지정한 디바이스 드라이버가 어떤 동작을 하게 하고 싶은지를 결정하는 코드다. 보통 지원하는 명령어 셋의 종류에 따라 달라진다. DFP_RECEIVE_DRIVE_DATA 와 같은 한 번에 해결하는 코드를 쓰고 싶을 수도 있지만 참으시라. MSDN에도 안 나오는 저런 코드는 WMI보다도 못한 부실한 결괏값을 자랑한다. 이 방법을 선택하기로 결정했다면 각 명령어 셋마다 구현을 해서 일일이 시험해 주어야 한다. 2015년 12월 시점으로 현재까지 나온 모든 오픈 소스 프로젝트는 이 방법을 사용하고 있다. 물론 WMI로 명령어 셋의 종류를 알아낼 수도 있겠지만, 그 방법으로는 다시 윈도의 한계에 걸리기 때문에 의미가 없음을 다시금 말하는 바이다.

우리가 사용할 컨트롤 코드는 다음과 같다.

  • IOCTL_SCSI_MINIPORT (NVMe 중 Intel/nvmewin 드라이버)
  • IOCTL_ATA_PASS_THROUGH_DIRECT (ATA 드라이버 중 Intel IRST 일부 버전)
  • IOCTL_ATA_PASS_THROUGH (ATA)
  • IOCTL_SCSI_PASS_THROUGH (SCSI와 NVMe 중 Samsung 드라이버)

참고로 구현할 때는 명령어 셋을 다음의 순서로 체크해주면 좋다. 일부 드라이버는 다른 명령어 셋을 지원하기도 하는데 대부분 부실하므로 이 순서를 지켜서 체크해야 부실한 명령어 셋이 선택되지 않는다.

  • NVMe nvmewin
  • NVMe Samsung
  • ATA Direct
  • ATA
  • SAT
  • SCSI

특히 NVMe – SCSI간 번역은 심각하게 저품질이므로 NVMe SCSI간 번역 표준 문서에 나와있는 부분 이상을 기대해서는 안 된다. 그리고 ATA(비Direct)는 IRST 일부 버전에서 버퍼가 망가지는 문제가 있으므로 Direct가 불가능할 때가 아니면 사용하지 않는 것이 좋다.