앞으로 몇 회에 걸쳐서 디바이스 드라이버 작성에 대해 알아 보도록 하겠습니다. 디바이스 드라이버에 대한 좋은 책이 이미 나와 있기 때문에 책에서 이미 언급된 내용은 피하고 이해하시는데 도움이 될 내용을 올리도록 노력하겠습니다.

앞으로 소개될 내용은 아래의 책을 학습하면서, 제가 경험한 것을 바탕으로 작성하겠습니다. 소개된 책은 이미 전문 서적 소개에 올려진 책으로 디바이스 드라이버 작성이 필요하신 분께는 꼭 권하고 싶습니다.

본 포럼은 개인이 학습하면서 올리는 자료와 정보를 교환하는 장소인 만큼, 본 강좌에 올려지는 내용이 책에서만큼 자세하고 깊은 내용을 담기 어려운 점을 말씀 드립니다.

디바이스 드라이버

아주 오래 전에는 컴퓨터에 달린 모든 장치에 대해 프로그래머가 모든 장치 제어에 대해 프로그램을 직접 작성해야 했습니다. 이러다 보니 프로그래머나 판매자, 사용자 모두 힘들었습니다. 예를 들어 아무리 좋거나 저렴한 프린터라도 프로그래머가 제어할 방법을 모른다면 사용할 수 없었습니다. 만일 사용자가 특정 제품을 사용하겠다고 고집하면 하는 수 없이 프로그래머는 학습과 코딩 수정이 필요했습니다.

그러나 디바이스 드라이버라는 개념이 생긴 후로는 프로그래머는 자신의 프로그램에 더욱 충실할 수 있었습니다. 즉, 외부 장치에 대해서는 제품을 만든 회사에서 함께 제공되는 드라이버를 이용하면 되기 때문입니다.

거기다가 이 드라이버를 사용하는 방법이 완전히 같지는 않아도 대동소이 하다면 그야말로 프로그래머는 장치에 대한 부담에서 많이 자유로워 질 것입니다.

우리가 흔히 디바이스 드라이버를 설명할 때 그리는 그림입니다. 하드웨어가 있고 그 안에 응용 프로그램이 실행되는데, 응용 프로그램에서 직접 하드웨어를 제어하는 것이 아니라 다비이스 드라이버를 통해 하드웨어를 제어합니다.

그러나 장치에 따라 디바이스 드라이버도 서로 다릅니다. 만들어진 회사도 다를 수 있습니다. 장치를 쉽게 다룰 수 있도록 디바이스 드라이버까지 만들어서 제공해 준 것 까지는 좋은데, 사용하는 방법이 디바이스 드라이버 마다 매우 다르다면 응용 프로그래머에게는 부담이 매우 클 것입니다.

리눅스에서는 모든 장치를 파일을 다루듯이 사용할 수 있다라는 말씀을 많이 들으셨을 것입니다. 리눅스에서는 디바이스 드라이버를 파일처럼 다룰 수 있도록 가상 파일 시스템(Virute File System)을 제공하기 때문에 가능한 것입니다.

 

디바이스 드라이버 사용 예

가상 파일 시스템이라는 내용을 이해해 보도록 하겠습니다. 이전에 시리얼 통신 강좌 시리즈 중에 시리얼 통신 - 통신포트 열기 글에서 디바이스 드라이버에 대해 말씀을 드린 적이 있습니다. 그 중에 일부를 올립니다.

시리얼 포트의 장치명

/dev 디렉토리에 있는 시리얼 포트의 목록을 보면 아래와 같은 내용이 출력됩니다. 도대체 뭔소리인지 하나씩 알아 보겠습니다.

(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
crwxrwxrwx 1 root tty 4 64 Jan 1 2006 ttyS00
crwxrwxrwx 1 root tty 4 65 Jan 1 2006 ttyS01
crwxrwxrwx 1 root tty 4 66 Jan 1 2006 ttyS02
crwxrwxrwx 1 root tty 4 67 Jan 1 2006 ttyS03

(1) 접근 권한을 보면 crwxrwxrwx 로 c로 시작하는 것은 장치가 "문자 장치"임을 알려 줍니다. c 가 아닌 b로 시작한다면 "블록 장치"를 말하는데, 예로 하드디스크와 같이 블럭 단위로 읽거나 쓰기를 하는 장치가 되겠습니다.

(5) 의 4는 메이저 장치 번호, (6)의 64, 65, 66 등은 마이너 장치 번호입니다. 우리가 작성하는 프로그램은 하드웨어 장치를 직접 제어하는 것이 아니라 커널을 통해 제어하게 됩니다. 하드웨어를 파일 개념으로 처리할 수 있는 것도 중간에 커널이 가상 파일을 만들어서 제공하기 때문에 가능 한 것입니다.

프로그램에서 하드웨어 장치에 대해 어떤 작업을 커널에게 요청하면, 커널은 메이저 번호를 가지고 어떤 디바이스 드라이버 사용할 지를 결정하게 됩니다. 디바이스 드라이버는 커널로부터 받은 정보 중 마이너 장치 번호를 가지고 자기에게 할당 된 장치 중 어떤 장치를 제어할 지를 결정하게 됩니다.

위의 장치 목록을 보시면 메이저 번호가 모두 4 로 똑 같습니다. 대신에 마이너 번호만 다르죠. 커널은 메이저 번호로 따라 디바이 드라이버를 선택하고 다음 처리를 넘기면 디바이스 드라이버는 마이너 번호를 가지고 어느 장치를 사용할 지를 결정한다는 얘기가 되겠습니다.

이렇게 하드웨어 장치 제어 흐름을 본다면 ttyS0, ttyS1 과 같은 이름은 별로 중요하지 않죠. 중요한 것은 메이저 장치 번호와 마이너 장치 번호가 되겠습니다.

위 내용을 조금 더 쉽게 이해하기 위해 통신 포트를 open 하는, 즉 통신 포트를 사용하기 위한 프로그램 코드를 보겠습니다.

fd = open( "/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK );

통신 포트라는 장치를 사용하기 위해서 /dev/ttyS0를 open했습니다. 그렇다면 /dev/ttyS0 가 디바이스 드라이버일까요? 디바이스 드라이버도 응용프로그램과 장치 사이에서 실행되는 프로그램이라고 생각한다면 /dev 에서 장치 목록을 출력할 때, 주 장치 번호와 부 장치 번호 뿐만 아니라 파일 사이즈도 나와야 할 것입니다. 파일 사이즈도 파이에 대한 중요 정보이니까요.

그리고 만약에 /dev/ttyS0 가 디바이스 드라이버라면 문제가 있습니다. 예로 시리얼포트가 8개가 있다고 한다면,

]$ ls -al | grep ttyS
crw-rw----   1 root uucp     4,  64  7월  3 14:18 ttyS0
crw-rw----   1 root uucp     4,  65  7월  3 14:18 ttyS1
crw-rw----   1 root uucp     4,  66  7월  3  2007 ttyS2
crw-rw----   1 root uucp     4,  67  7월  3  2007 ttyS3
crw-rw----   1 root uucp     4,  68  7월  3  2007 ttyS4
crw-rw----   1 root uucp     4,  69  7월  3  2007 ttyS5
crw-rw----   1 root uucp     4,  70  7월  3  2007 ttyS6
crw-rw----   1 root uucp     4,  71  7월  3  2007 ttyS7

이렇게 통신 포트별로 이름을 바꾸면서 다비이스 드라이버를 작성해서 올려야 합니다. 포트 번호만 다르고 처리하는 방법은 같은데, 통신 포트 개수만큼 소스를 수정하고 모두 컴파일해서 등록해야 된다면 매우 불편할 것입니다.

insmod

그래서 실제로는 하나의 디바이스 드라이버를 만들어서 커널을 올립니다. 커널을 올릴 때, 다른 디바이스 드라이버와 구별할 수 있도록 번호를 앞 가슴에다 부착하고 커널에 올립니다. 이 번호가 주 장치 번호, 주 번호가 되겠습니다.

insmod user_device_name

이렇게 insmod 명령을 실행하면 user_device_name 디바이스 드라이버 안에 주 번호를 몇 번으로 등록할 지 커널에 요청하는 코드가 들어 있습니다. 즉, user_device_name 디바이스 드라이버를 만들 때부터 프로그래머에 의해 주 장치 번호가 경정됩니다. 커널은 디바이스 드라이버에서 요청하는 장치 번호로 user_device_name 디바이스 드라이버를 커널 영역으로 로드합니다.

여기 글에서는 user_device_name 가 주 번호를 250을 사용한다고 하겠습니다.

mknod

이제 커널에서는 디바이스 드라이버를 사용할 준비가 되었습니다. 그러나 아직 커널만 알고 밖에서는 아무도 모릅니다. 커널 밖에서도 이 디바이스 드라이버를 사용할 수 있도록 공개해 주어야 하는데, 그냥 공개하기 보다는 응용 프로그램이 접근하기 쉽도록, 또한 같은 장치이면서 내부에 처리하는 번호가 다르다면 하나의 디바이스 드라이버에서 모두 처리할 수 있도록, 결국 응용프로그램에서 쉽게 사용할 수 있도록 가상 파일 시스템으로 제공해 줍니다.

그것이 바로 /dev/장치명이 되겠습니다.

]# mknod /dev/user_device_S0 c 250 0
]# mknod /dev/user_device_S1 c 250 1
]# mknod /dev/user_device_S2 c 250 2
]# mknod /dev/user_device_S3 c 250 3
]# mknod /dev/user_device_S4 c 250 4

/dev 안에 있는 아이템을 계속 장치명이라고 말씀드렸습니다만 정확한 명칭이 node 라고 하더군요. 그래서 명령어 이름이 mknod 인것으로 생각됩니다.

응용프로램과 커널 그리고 디바이스 드라이버

자, 이제 응용프로그램에서 다시 보겠습니다.

fd = open( "/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK );

프로그램에서는 /dev/ttyS0 만 언급햇습니다. 실제 디바이스 드라이브 파일 명이 아닙니다. 이 프로그램 코드는 당연히 커널로 전송되어지고 커널에게 부탁하게 됩니다. 커널은 /dev/ttyS0에서 주 장치 번호를 확인합니다. 또한 주 장치를 보고 커널에 등록된 디바이스 드라이버중에 장치 번호에 해당하는 디바이스 드라이버를 찾아서 응용 프로그램의 open()에 대한 명령을 전송해 줍니다. 이때, 함께 전송되는 것이 /dev/ttyS0에 해당하는 부 장치 번호입니다.

디바이스 드라이버는 커널로부터 받은 부 장치 번호를 가지고 자신이 처리하는 여러 장치 중 어느 장치를 처리할 지를 파단하게 됩니다.

(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
crwxrwxrwx 1 root tty 4 64 Jan 1 2006 ttyS0
crwxrwxrwx 1 root tty 4 65 Jan 1 2006 ttyS1
crwxrwxrwx 1 root tty 4 66 Jan 1 2006 ttyS2
crwxrwxrwx 1 root tty 4 67 Jan 1 2006 ttyS3

즉, /dev/ttyS0 장치를 사용한다면,

  1. 커널은 /dev/ttyS0 의 주 장치 번호 4번에 해당하는 디바이스 드라이버를 찾고
  2. 디바이스 드라이버에 부 장치 번호 64를 전송해 주면,
  3. 디바이스 드라이버는 자기가 처리하는 여러 통신 포트 중에 부 장치 번호인 64로 처리할 포트를 알게 됩니다.

 

태그: *디바이스드라이버