IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  리눅스  >

윈도에서 리눅스로 디바이스 제어 응용 이식하기

윈도와 리눅스 디바이스 제어 방식을 이해함으로써 이식 과정에 등장하는 어려움을 극복한다

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

Sun Ling, 소프트웨어 엔지니어 인턴, IBM
Yang Yi, 소프트웨어 엔지니어 인턴, IBM

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 11 월 18 일

양쪽 운영체제에서 디바이스 제어 동작 원리를 이해함으로써 마이크로소프트 윈도(Microsoft® Windows®)에서 리눅스(Linux®)로 디바이스 제어 응용을 이식하는 어려움을 극복합시다. 양쪽 운영체제에 나타나는 차이점을 설명하고 C/C++ 이식 예제를 제공합니다.

다양한 플랫폼에서 디바이스 제어 응용을 개발한다면, 윈도와 리눅스가 디바이스를 제어하는 방식이 다르며, 한쪽에서 다른 쪽으로 이식하는 작업은 상당히 고통스러우리라는 사실을 알고 있다. 이 기사에서는 아키텍처부터 시스템 호출에 이르기까지 모든 차이점을 검사하고 집중하는 방식으로 양쪽 운영체제에서 동작하는 디바이스 제어 방식을 분석한다. 또한 이식 과정을 상세히 설명하기 위해 (C/C++로 만든) 이식 예제를 제공한다.

가정:
이 기사에서 윈도는 윈도 2000과 이후 버전을 의미하며, 마이크로소프트 비주얼 C++(Microsoft Visual C++®) 6 또는 이후 버전을 설치했다고 가정한다. 리눅스 쪽을 살펴보면, 리눅스 커널은 2.6 기반이며 GNU GCC를 설치했다고 가정한다.

디바이스 제어를 위한 아키텍처 비교

윈도와 리눅스를 비교해 보면 디바이스 제어 방식이 다르다.

윈도 디바이스 제어 아키텍처

윈도에서, I/O 하위 시스템은 사용자 응용이 디바이스 드라이버로 접속하도록 디바이스 드라이버를 지원하는 기반 구조를 정의한다. 디바이스 드라이버는 특정 디바이스 연결을 지원하는 외부 통로인 I/O 인터페이스를 제공한다(그림 1 참조).


그림 1. 윈도 디바이스 제어 아키텍처
윈도 디바이스 제어 아키텍처

디바이스 제어 과정에서, I/O 연산은 IRP(I/O Request Packet)로 캡슐화되어 있다. I/O 관리자는 IRP를 생성해 스택 상위로 보낸다. 그러면 디바이스 드라이버가 I/O 요청을 위한 매개변수가 담긴 IRP를 가리키는 스택 위치를 얻는다. IRP 요구사항에 따르면(create, read, write, devioctl, cleanup, close)) 각 드라이버는 하드웨어 인터페이스를 통해 작업을 수행한다.

리눅스 디바이스 제어 아키텍처

리눅스에서 디바이스 제어 아키텍처는 조금 다르다. 주요 차이점은 일반 파일, 디렉터리, 디바이스, 소켓이 모두 파일이라는 사실이다. 리눅스에서는 모든 것이 파일이다. 디바이스 접근을 위해, 리눅스 커널은 파일 시스템을 통해 디바이스 연산 호출을 디바이스 드라이버로 사상한다. 리눅스에는 I/O 관리자가 없다. 모든 I/O 요청은 처음부터 파일 시스템으로 간다(그림 2 참조).


그림 2. 리눅스 디바이스 제어 아키텍처
리눅스 디바이스 제어 아키텍처



위로


디바이스 파일 이름과 경로 이름 비교

개발 관점에서 바라보면, 디바이스 핸들을 얻는 과정은 디바이스 제어를 위한 선행 조건이다. 하지만 디바이스 제어 아키텍처가 다르므로, 디바이스 핸들을 얻는 방법도 윈도를 사용하느냐 리눅스를 사용하느냐에 따라 이야기가 완전히 다르게 전개된다.

일반적으로 말하자면, 디바이스 핸들은 특정 디바이스 드라이버 이름이 결정한다.

윈도에서는 디바이스 드라이버 파일 이름은 일반 파일 형식과 다르다. 이를 일반적으로 디바이스 경로 이름이라고 하며, \.DeviceName과 같은 고정 형식을 따른다. C/C++ 프로그래밍에서, 이 문자열은 \\.\DeviceName과 같이 표현해야 한다. 코드에서는 \\\\.\\DeviceName과 같이 사용한다. DeviceName은 대응하는 디바이스 드라이버 프로그램에서 정의한 디바이스 이름과 똑같아야 한다.

몇몇 디바이스 이름은 마이크로소프트가 정의했으며, 바뀌지 않는다(표 1 목록 참조).


표 1. 윈도에서 디바이스 이름(x = 0, 1, 2 등)
디바이스경로 이름
플로피 드라이브 A: B:
하드 디스크 C: D: E: . . .
물리 드라이브PhysicalDrivex
CD-ROM, DVD/ROMCdRomx
테이프 드라이브Tapex
COM 포트COMx

예를 들어, C/C++ 프로그래밍에서 \\\\.\\PhysicalDrive1, \\\\.\\CdRom0, \\\\.\\Tape0 같은 디바이스 경로 이름을 사용한다. 상기 목록에 나와 있지 않은 다른 디바이스에 대한 세부 내역은 이 기사 뒷부분에 나오는 참고자료 절을 참조하자.

리눅스에서 디바이스는 파일로 기술하므로 ./dev 디렉터리에서 디바이스 파일 전부를 찾을 수 있다. 이 디렉터리에 있는 디바이스 드라이버는 다음을 포함한다.

  • IDE(Integrated Drive Electronics) 하드 드라이브: /dev/hda, /dev/hdb
  • CD-ROM 드라이브: 몇몇은 IDE, 몇몇은 SCSI 디바이스(/dev/scd0와 같이) 에뮬레이션으로 동작하는 CD-RW(CD Read/Write)
  • 직렬 포트: /dev/ttyS0(COM1), /dev/ttyS1(COM2)
  • 포인팅 디바이스: /dev/input/mice
  • 프린터: /dev/lp0

대다수 공통 디바이스 파일은 위에서 설명한 내용에 따라 찾을 수 있다. 다른 디바이스 파일 이름과 세부 디바이스 정보는 dmesg 명령으로 살펴보기 바란다.




위로


주요 시스템 호출 비교

디바이스 제어를 위한 주요 시스템 호출은 open, close, ioctl, read/write 같은 연산을 포함한다. 표 2에 정리한 윈도/리눅스 비교 내용을 살펴보자.


표 2. 디바이스 제어 함수 비교
윈도리눅스
CreateFileopen
CloseHandleclose
DeviceIoControlioctl
ReadFileread
WriteFilewrite

이제 가장 일반적인 함수인 create, close, devioctl을 좀 더 깊숙히 파고 들자.

윈도에서 디바이스 여닫기

지금부터는 윈도에서 CreateFileCloseHandle을 설명한다. 디바이스를 열기 위해 함수 CreateFile을 사용한다. 이 함수는 객체 접근을 위해 사용하는 핸들을 반환한다. Listing 1을 살펴보자.


Listing 1. 윈도에서 CreateFile 함수
                
HANDLE CreateFile (LPCTSTR lpFileName,          // 디바이스 파일 이름
                                                  (디바이스 경로 이름)
   DWORD dwDesiredAccess,                       // 객체에 대한 접근 모드(읽기, 쓰기, 둘 다)
   DWORD dwShareMode,                           // 객체 공유 모드(읽기, 쓰기, 둘 다)
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 자식 프로세스가 반환된 핸들을 상속받을
                                                // 수 있는지를 판단하는 보안 속성
   DWORD dwCreationDisposition,                 // 파일 존재 유무에 따라 적용할 연산
   DWORD dwFlagsAndAttributes,                  // 파일 속성과 플래그
   HANDLE hTemplateFile);                       // 임시 파일에 대한 핸들

매개변수 lpFileName은 직전에 명세한 디바이스 경로 이름이다. 일반적으로 디바이스를 열려면 dwDesiredAccess를 0이나 GENERIC_READ|GENERIC_WRITE로, dwShareModeFILE_SHARE_READ|FILE_SHARE_WRITE로, dwCreationDispositionOPEN_EXISTING으로, dwFlagsAndAttributeshTemplateFile을 0이나 NULL로 설정한다. 반환되는 핸들은 나중에 디바이스 제어 연산에 사용한다.

디바이스를 닫으려면 함수 CloseHandle을 사용한다. 매개변수 hObject는 디바이스를 열 때 반환된 핸들이다. BOOL WINAPI CloseHandle (HANDLE hObject); 형식을 따른다.

리눅스에서 디바이스 여닫기

리눅스에서 openclose를 설명한다. 직전에 언급했듯이, 디바이스 열기는 일반 파일 열기와 동일하다. Listing 2는 디바이스 핸들을 얻기 위한 open 사용법을 보여준다.


Listing 2. 리눅스에서 open 함수
                
int open (const char *pathname,
       int flags, 
       mode_t mode);

open 호출이 성공했을 때 반환되는 파일 기술자는 프로세스에서 현재 열리지 않은 가장 낮은 파일 기술자 번호가 될 것이다. 호출에 실패하면, -1을 반환한다. 파일 기술자는 디바이스 핸들로 쓰인다.

매개변수 플래그는 O_RDONLY, O_WRONLY, O_RDWR 중 하나를 포함해야 한다. 나머지 플래그는 옵션이다. 인수 mode는 새 파일을 생성할 경우에 접근 권한을 명세한다.

리눅스에서 함수 close는 파일과 마찬가지로 디바이스를 닫는다. int close(int fd); 형식을 따른다.

윈도에서 DeviceIoControl

윈도에서 DeviceIoControl, 리눅스에서 ioctl은 디바이스 제어를 위해 가장 흔히 사용되는 함수이며 디바이스 접근, 정보 인출, 명령 전송, 자료 교환 같은 작업을 수행한다. Listing 3은 DeviceIoControl을 보여준다.


Listing 3. 윈도에서 DeviceIoControl 함수
                
BOOL DeviceIoControl (HANDLE hDevice,
      DWORD dwIoControlCode,
      LPVOID lpInBuffer,
      DWORD nInBufferSize,
      LPVOID lpOutBuffer,
      DWORD nOutBufferSize,
      LPDWORD lpBytesReturned,
      LPOVERLAPPED lpOverlapped); 

이 시스템 호출은 제어 코드와 기타 자료를 지정된 디바이스로 보낸다. 대응하는 디바이스 드라이버는 제어 코드인 dwIoControlCode가 지시하는 방식대로 동작한다. 예를 들어, IOCTL_DISK_GET_DRIVE_GEOMETRY를 사용해 물리 드라이버에서 구조 매개변수를 얻을 수 있다(매체 유형, 실린더 개수, 각 실린더에 존재하는 트랙 개수, 각 트랙에 존재하는 섹터 개수 등). 제어 코드 정의, 헤더 파일, 기타 세부 정보는 MSDN 웹 사이트에서 얻을 수 있다(참고자료 절에서 링크를 살펴보자).

입/출력 버퍼가 필요한지 구조체와 크기가 무엇인지는 실제 관련 ioctl 과정과 관련이 있는 디바이스와 연산에 따라 달라진다. 이런 사항은 호출 시에 명세한 dwIoControlCode가 결정한다.

중첩 연산을 위한 포인터를 NULL로 설정하면, DeviceIoControl은 차단 방식(동기식)으로 동작한다. 그렇지 않으면 비동기식으로 동작할 것이다.

리눅스에서 ioctl

리눅스에서는 특정 디바이스에 제어 정보를 전달하는 데 ioctl을 사용한다. int ioctl(int fildes, int request, /* arg */ ...); 형식을 따른다. 첫 번째 매개변수인 fildesopen() 함수가 반환한 열린 파일 기술자이며 특정 디바이스를 가리킨다.

대응하는 DeviceIOControl 시스템 호출과는 달리, ioctl 입력 매개변수 목록은 고정되어 있지 않다. 매개변수 목록은 ioctl에서 매개변수 request가 지정한 내용과 윈도 DeviceIOControl에서 dwIoControlCode와 같이 ioctl이 수행하는 요청 유형에 따라 달라진다. 하지만 이식 과정에서 올바른 request 매개변수를 선택할 때 주의해야 한다. DeviceIOControl에서 dwIoControlCodeioctl에서 request는 동일한 값이 아니며, dwIoControlCode/request를 위한 명시적인 사상 방법도 없기 때문이다. 일반적으로 헤더 파일에서 정의를 살펴보는 방법으로 매개변수 request 값을 선택한다. 모든 제어 코드 정의 내용은 /usr/include/{asm,linux}/*.h에 실려있다.

매개변수 arg는 필요한 작업을 수행하도록 특정 디바이스가 필요한 세부 명령어 정보를 전송하기 위해 남겨져 있다. arg 자료 유형은 제어 요청에 따라 달라진다. 이 인수를 사용해 세부 명령을 보내고 반환 자료를 받을 수 있다.




위로


이식 예제

지금부터 윈도에서 리눅스로 이식하는 과정을 예를 들어 살펴본다. 이 예제는 개인용 컴퓨터에 장착된 주 IDE 하드 드라이브에서 SMART 로그를 읽는 방법을 보여준다.

1단계: 디바이스 유형 파악

이미 설명했듯이 리눅스는 각 디바이스를 파일로 취급한다. 첫 단계는 리눅스에서 디바이스 파일 이름을 찾아내는 작업이다. 이 파일 이름을 사용해야 디바이스 제어에 필요한 디바이스 핸들을 얻을 수 있다.

이 예제에서 대상 디바이스는 IDE 하드 드라이브다. 리눅스에서 IDE 하드 드라이브는 /dev/hda, /dev/hdb로 기술한다. 우리가 이식할 예제에서 하드 디스크 디바이스 경로 이름은 \\\\.\\PhysicalDrive0이다. 리눅스에서 대응하는 디바이스 파일 이름은 /dev/hda다.

2단계: 인클루드 헤더 변경

#include 헤더 파일을 리눅스 형식으로 변경해야 한다(표 3 참조).


표 3. #include 헤더 파일
윈도리눅스
#include <windows.h> #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <devioctl.h> #include <sys/ioctl.h>
#include <ntddscsi.h> #include <linux/hdreg.h>

windows.h는 디바이스를 여닫는 함수(CreateFileCloseHandle)를 위해 인클루드한다. 여기에 대응하도록 리눅스에서 open()close()를 위해 필요한 헤더 파일을 인클루드해야 한다. 헤더 파일로 sys/types.h, sys/stat.h, and fcntl.h가 있다.

윈도에서 devioctl.h는 함수 DeviceIoControl을 정의하며, 이 파일을 sys/ioctl.h로 변경해 함수 ioctl이 동작하도록 만든다.

(DDK에 들어있는 헤더 파일인) ntddscsi.h는 디바이스 제어를 위한 제어 코드 집합을 정의한다. 이 예제는 IDE 하드 드라이브만 다루고 있기에 리눅스 프로그램에서는 linux/hdreg.h를 추가하기만 하면 끝난다.

다른 상황에서는 필요한 제어 코드 정의가 들어있는 모든 헤더 파일을 인클루드해야 한다. 예를 들어, 하드 드라이브가 아니라 CD-ROM에 접근하려면 linux/cdrom.h을 대신 인클루드한다.

3단계. 함수와 매개변수 수정

이제 코드를 상세히 들여다보자. Listing 4는 명령어 세부 사항을 보여준다.


Listing 4. 명령어 세부 사항
                
unsigned char cmdBuff[7];
cmdBuff[0] = SMART_READ_LOG;  // SMART "명령"을 명세하기 위해 쓰인다.
cmdBuff[1] = 1;               // IDE 섹터 계수 레지스터
cmdBuff[2] = 1;               // IDE 섹터 번호 레지스터
cmdBuff[3] = SMART_CYL_LOW;   // IDE 실린더 값(LOW)
cmdBuff[4] = SMART_CYL_HI;    // IDE 실린더 값(HI)
cmdBuff[5] = 0xA0 | (((Dev->Id-1) & 1) * 16); // IDE 드라이브/헤더 레지스터
cmdBuff[6] = SMART_CMD;       // 실제 IDE 명령

명령어 정보는 ATA 명령어 명세에서 가져왔다. 리눅스로 코드를 이식하기 위해 변경이 필요하지 않으므로 추가 분석은 여기서 마친다.

Listing 5에 제시한 코드는 윈도에서 주 하드 드라이브를 연다.


Listing 5. 윈도에서 주 하드 드라이브를 열기
                
HANDLE devHandle = CreateFile("\\\\.\\PhysicalDrive0",           // 경로 이름
                             GENERIC_WRITE|GENERIC_READ,         // 접근 모드
                             FILE_SHARE_READ|FILE_SHARE_WRITE,   // 공유 모드
                             NULL,OPEN_EXISTING,0,NULL);

리눅스에서 여닫기에는 매개변수 두 개가 필요하다는 사실을 기억하자(파일 경로 이름과 디바이스에 대한 접근 모드). 직전에 살펴본 원본 코드에서 첫 번째는 /dev/hda이며 두 번째는 O_RDONLY|O_NONBLOCK이다. 변경된 코드는 다음과 같다. HANDLE devHandle = open("/dev/hda", O_RDONLY | O_NONBLOCK); 또한 CloseHandle(devHandle);close(devHandle);로 바꾼다.

특정 디바이스에 접근해 원하는 정보를 가져오기 위한 ioctl 용법이 핵심이다. Listing 6에 원본 윈도 코드를 제시한다.


Listing 6. 윈도에서 DeviceIoControl 원시 코드
                
typedef struct _Buffer{
       UCHAR   req[8];              // 제어 코드 이외 세부 명령 정보
       ULONG   DataBufferSize;      // DataBuffer 크기, 여기서는 512
       UCHAR   DataBuffer[512];     // 자료를 담을 버퍼
} Buffer;

Buffer regBuffer;
memcpy(regBuffer.req, cmdBuff, 7);  // req[7]은 장래 사용을 위해 예약되어 있으며 0이다.
regBuffer.DataBufferSize = 512;
unsigned int size = 512+12;         // regBuffer 크기
                                    // req를 위해 8, DataBufferSize를 위해 4, 자료를 위해 512
DWORD bytesRet = 0;                 // 반환되는 바이트 개수
int retval;                         // 반환값

retval = DeviceIoControl(devHandle,
                         IOCTL_IDE_PASS_THROUGH,  // 제어 코드
                         regBuffer, // 입력 버퍼, 세부 명령 크기를 포함한다.
                         size, 
                         regBuffer, // 출력 버퍼, 여기서는 동일한 버퍼를 사용한다.
                         size, 
                         &bytesRet, NULL);
if (!retval)
	cout<<"DeviceIoControl failed."<<endl;
else
memcpy(data, retBuffer.DataBuffer, 512);

DeviceIoControlioctl보다 많은 매개변수를 요구한다. 디바이스 핸들은 양쪽 플랫폼에서 첫 번째 매개변수이며, 리눅스에서는 open(), 윈도에서는 CreateFile에서 반환되는 값이다. 하지만 윈도에서 dwIoControlCode와 리눅스에서 request는 다른 방식으로 정의되기에, 직전에 설명했듯이 두 매개변수 사이에 사상 관계를 정의하는 고정 규칙은 없다. IOCTL_IDE_PASS_THROUGH는 헤더 파일 ntddscsi.h에 CTL_CODE (IOCTL_SCSI_BASE, 0x040a, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)로 정의되어 있다. /usr/include/linux/hdreg.h 헤더 파일에서 정의를 찾아서 리눅스용 대응 제어 코드인 HDIO_DRIVE_CMD를 선택한다.

추가적으로 특정 과업을 수행하기 위해 디바이스가 세부 명령어 정보를 요구한다. 이 명령어는 프로세스에서 커널로 전달 자료를 저장하는 버퍼에 넣는다. 또한 이 버퍼로 반환 자료가 넘어온다. 명령어를 보내고 필요한 로그 정보를 얻기 위해 동일한 버퍼를 사용한다. 리눅스에서 자료 버퍼를 지정하는 크기 매개변수는 제거할 수 있다. 이 예제에서는 (윈도처럼) 명령어 8바이트를 모두 사용하는 대신 (리눅스에서는) 명령어 4바이트만 사용한다.

대응하는 리눅스 코드(Listing 7)가 상당히 단순해 보이는 이유는 윈도보다 구조체와 함수 인수가 더 간단하기 때문이다.


Listing 7. 리눅스에서 ioctl 원시 코드
                
int retval;
unsigned char req[4+512]; // 세부 제어 정보와 반환 자료를 위해 충분한 공간
req[0]= cmdBuff[6];       // 이 예제에 필요한 요구사항을 고려할 때 4바이트만 사용한다.
req[1]= cmdBuff[2];
req[2]= cmdBuff[0];
req[3]= cmdBuff[1];

retval = ioctl(devHandle, HDIO_DRIVE_CMD, &req);
if(ret)
	cout<<"ioctl failed."<<endl;
else 
memcpy(data, &req[4], 512);

4단계. 리눅스 환경에서 테스트하기

헤더 파일, 함수, 매개변수를 수정했다면 프로그램은 리눅스에서 동작할 준비가 끝났다. 이제 리눅스 플랫폼에서 컴파일한 다음에 남아있는 구문 오류를 수정한다. 리눅스 배포판과 컴파일 환경에 따라 몇 가지 추가 수정이 필요할지도 모른다.



참고자료

교육

제품 및 기술 얻기
  • SEK for Linux 주문: IBM 최신 리눅스 평가판 소프트웨어가 담긴 두 장짜리 DVD 세트를 신청하자. DB2®, Lotus®, Rational®, Tivoli®, WebSphere®를 포함한다.

  • IBM 평가판 소프트웨어를 developerWorks에서 직접 내려받아, 다음 번 리눅스 개발 프로젝트를 진행하자.


토론


필자소개

Sun Ling은 상해 교통대학교에서 정보 보안 공학 석사 과정을 다니면서, 중국 IBM 시스템과 기술 연구소에서 인턴 소프트웨어 엔지니어로 일한다. 현재 Ling은 CIM 컨버전스 팀에서 일하며, 리눅스 분야 디바이스 제어 개발과 이식 경험을 쌓았다.


Yang Yi는 중국 IBM 시스템과 기술 연구소에서 인턴 소프트웨어 엔지니어로 일한다. 현재 Yi는 상해 교통대학교에서 전기 공학 석사 과정을 다닌다. Yi는 다양한 플랫폼에서 동작하는 디바이스 관리 제품 경험을 쌓았다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


IBM, the IBM logo, ibm.com, DB2, developerWorks, Lotus, Rational, Tivoli, and WebSphere are trademarks or registered trademarks of International Business Machines Corporation in the United States, other countries, or both. These and other IBM trademarked terms are marked on their first occurrence in this information with the appropriate symbol (® or ™), indicating US registered or common law trademarks owned by IBM at the time this information was published. Such trademarks may also be registered or common law trademarks in other countries. See the current list of IBM trademarks. Linux is a trademark of Linus Torvalds in the United States, other countries, or both. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의