이 글은 솔라리스 디바이스 드라이버에 촛점을 맞춰서, 일반적인 솔라리스 커널 아키텍쳐의 기본적인 정보와 함께 디바이스 드라이버에 대한 자세한 정보를 제공 합니다. 이 후에는 작은 뼈대(skeleton) 드라이버도 개발해 봅니다.

솔라리스 드라이버와 친숙해지기

다수의 엔지니어들이 모여서 운영체제 커널을 비교할때 마다 디바이스 지원에 대한 이슈가 항상 따라 옵니다. 커널은 하드웨어와 소프트웨어의 간격을 이어주기 위한 소프트웨어의 핵심 부분이고 디바이스 드라이버는 플랫폼 주변장비를 사용자들과 이어주기 위한 작은 코드 입니다.

디바이스 드라이버에서 커널로 혹은 커널에서의 접근은 잘 정의된 인터페이스들의 셋에 의존합니다 [1]. 솔라리스는 1992년에 처음 소개되었고 그러므로 다양한 디바이스 드라이버를 위한 충분한 인터페이스들을 가지고 있습니다. 사실 몇몇 개발자들은 수년전에 솔라리스를 사용하기 전에 그래픽, 네트워크 혹은 스토리지 컨트롤러 드라이버를 작성하는 것부터 시작해야 했습니다. 그러나 현재까지 솔라리스 디바이스 드라이버 개발자들의 수는 상대적으로 아주 작은 엘리트 그룹에 한정되었습니다. 몇몇 비평가들은 솔라리스가 이러한 이유로 매우 닫혀있는 소스라고 폄하하고 있습니다. 그러나 2005년 6월 코어 커널 코드를 오픈소스 커뮤니티에 공개한 이후로, 솔라리스 드라이버 개발에 참여하고 있는 개발자의 숫자들이 늘어나고 있고 드라이버에 관심있는 일반 유저나 개발자들을 끌어 들이고 있습니다.

이 글을 통해 우리는 솔라리스 드라이버에 대해 친숙해지고 혹은 몇몇 독자들은 다시한번 상기 할 수 있는 기회가 될 것입니다. 실용적인 접근을 통해서 디바이스 드라이버를 살펴 볼텐데, 첫째로 특정 디바이스를 위해 바이너리 드라이버를 설정하고자 하는 시스템 설치 혹은 관리자의 관점에서 살펴 볼 것이고 두번째로는 솔라리스용 드라이버를 처음 부터 작성하거나 드라이버를 솔라리스로 포팅하려고 하는 개발자의 관점에서 살펴 볼 것입니다. 필자는 이 글을 통해서 드라이버 개발을 어플리케이션 개발 만큼이나 확실하게 이해할 수 있게 되기를 바랍니다.

커널 모듈 들여다 보기

초보자로써 처음 디바이스 드라이버를 작성하는 것은 매우 어려운 일입니다. 성공적으로 디바이스 드라이버를 작성할 수 있게 되기 전까지는 그전에 수 많은 개념들을 습득해야 합니다. 배움의 장애물이 되는 것중 하나는 디바이스 드라이버가 커널과 하드웨어와 상호작용을 해야 한다는 것입니다. 품질에 대한 요구사항이 매우 높은데 왜냐하면 드라이버 모듈의 잘못된 프로그래밍은 전체 커널과 OS 를 죽일 수도 있기 때문입니다. 솔라리스에서든 혹은 잘 아키텍쳐링된 운영체제에서든 어플리케이션들은 커널에서 분리되어 있습니다. 잘못 작성된 프로그램은 과도하게 시스템 리소스를 사용하고, 코어를 덤프 시키고 혹은 원인 모를 이유로 종료 됩니다. 그러나 커널이나 혹은 다른 비의존적인 프로그램들을 내리는 일은 매우 드문 일입니다. 디바이스 드라이버가 전형적으로 커널내에서 실행된다는 사실만 빼면, 표준 어플리케이션을 작성하는 것과 디바이스 드라이버를 작성하는 것에는 아주 많은 유사성이 존재 합니다. 어플리케이션 처럼 디바이스 드라이버는 바이너리 코드이고 일반적으로 C 작성되며 라이브러리들로 컴파일 되고 링크 됩니다.

솔라리스 디바이스 트리

솔라리스는 드라이버들을 디바이스 트리(device tree) 라고 하는 형태로 조직적으로 관리하고 분류 합니다. 이것은 파일 디렉토리 구조와 비슷하다고 생각하시면 됩니다. 파일 구조가 디렉토리, 서브디렉토리, 그리고 파일들을 가지고 있는것과 마찬가지로 이 트리의 각 지점들은 지점(branch), 서브-지점, 혹은 엔드포인트를 나타낼 것입니다. 디바이스 파일 시스템, 혹은 devfs 는 디바이스 트리의 하나의 예제 입니다. 일반적으로 디바이스 파일 시스템의 부모 노드들은 버스 혹은 브리지 컨트롤러 하드웨어에 매핑되고 자식 노드들은 디바이스들 혹은 컨트롤러 들로 매핑됩니다. 버스와 브리지 컨트롤러 하드웨어를 위한 드라이버들은 nexus drivers 로 불립니다. 다른 드라이버들은 leaf node drivers 로 불립니다. 자식 노드 드라이버들은 독립적으로 기능을 가진 하드웨어들을 조종합니다. 자식 노드 드라이버의 예제는 네트워크 인터페이스 카드와 RAID 호스트 버스 어댑터들을 포함합니다. 디바이스 파일 시스템은 이후에 더 자세히 설명됩니다.


그림 1 샘플 디바이스 트리

디바이스 드라이버의 종류

Nexus 드라이버는 일반적으로 브리지, 버스, 허브 그리고 마더보드의 스위치 칩들을 관리하고 일반적으로 솔라리스 OS 에 의해 제공됩니다. Nexus 드라이버는 써드파티 드라이버 작성자들에 의해서는 일반적으로 작성되지 않습니다. 솔라리스는 아래와 같이 다양한 버스 및 컨트롤러를 위한 nexus 드라이버를 포함하고 있습니다:

  • PCI 와 PCI-express 버스 [3]
  • USB nexus
  • 키보드, 마우스 그리고 시리얼 포트들을 위한 전통적인 ISA 버스 컨트롤러
  • SCSI 버스
  • IDE nexus
  • 일반적인 LAN 드라이버를 위한 프레임워크

솔라리스가 nexus 드라이버를 제공하기 때문에 대부분의 개발자들은 자식 노드 드라이버들에 촛점을 맞춥니다, 이러한 디바이스들은 두가지 기본 타입으로 나뉩니다:캐릭터(character) 와 블럭(block). 캐릭터 디바이스 드라이버들은 시리얼 포트와 커스텀 I/O 보드들에 접근하기 위해 사용 됩니다. 캐릭터 디바이스들은 non-STREAMS 드라이버들 혹은 STREAMS 드라이버들로 구현이 가능 합니다. (이 글에서는 STREAMS 드라이버에 대해 자세히 다루지 않습니다. 대신 이후의 글에서 간단히 소개할 것입니다.) 블럭 디바이스 드라이버들은 종종 파일시스템 디스크와 스토리지에 접근하는데에 사용 됩니다.

여기서 반드시 이해해야 할 중요한 것은 디바이스 드라이버가 솔라리스 커널이 하위 레벨 하드웨어를 접근하는 것을 가능하게 한다는 것입니다. 상위 레벨에서 커널은 어플리케이션에 네트워킹, 디바이스 컨트롤, 프로세스와 메모리 관리 그리고 스토리지 접근을 위해 필요한 표준 API 셋들을 제공해 줍니다. 그러나 커널이 특정 벤더의 디바이스를 사용하기 위해서는 하위 레벨 기능을 제공하는 코드가 있어야 합니다. 이러한 코드는 하드웨어 디바이스에서 발생할 작업들을 호출하는 표준 커널 라이브러리 함수와 매핑됩니다. 이것이 디바이스 드라이버의 일 입니다.

다음의 그림은 솔라리스 운영체제의 아키텍쳐를 보여 줍니다. 어플리케이션은 유저 레벨이고 디바이스 드라이버는 커널 레벨 입니다. 하드웨어 레벨은 CPU, 디스크, NIC 들을 포함합니다.


그림 2 솔라리스 운영체제 아키텍쳐

솔라리스DDI/DKI

아마도 어플리케이션 프로그래밍과 드라이버 프로그래밍의 가장 큰 차이는 그 자유도에 있다고 할 수 있을 것입니다. 어플리케이션 프로그램의 구현은 일반적으로 어플리케이션의 유저의 쉘 혹은 유저의 그래픽 인터페이스와 동작하는 방법을 관장하는 최소의 룰에 의해서만 제약이 됩니다. 어플리케이션이 실제 어떻게 작업을 하는지는 아주 유연하고 개발자의 관점에서 OS 와는 가상적으로 의존성이 없습니다. 예를 들어 우리들은 고유의 서브루틴을 가진 단독 프로그램을 작성할 수 있고 오직 모든 단독 프로그램에서 반드시 구현해야 하는 표준 main() 진입 포인트 만을 구현 하면 됩니다. 물론 프로그램은 여전히 OS 안에서 실행되고 동작하지만 우리의 관점에서 우리는 테스크 스케쥴링, 동시성, 메모리 접근, 리소스 할당 및 I/O 충돌등을 다루지 않아도 됩니다. 훌륭한 소프트웨어 패키지를 작성하려고 노력하지 않더라도 커널과 관련 라이브러리들은 수많은 이런 종류의 이슈들로 부터 우리를 분리 시켜 줍니다.

드라이버 프로그래밍에서는 자유도가 좀 더 떨어지고 안전 장치가 잘 제공 되지 않습니다. 우리는 단독 코드를 작성하는 것이 아닙니다. 대신 우리는 솔라리스 커널이 디바이스에 접근할 수 있도록 하는 소프트웨어 확장을 작성하는 것입니다. 그러므로 커널이 디바이스를 사용해야 할때 우리는 커널이 예상 가능한 형태로 디바이스를 조정할 수 있도록 미리-동의된 서브루틴의 셋을 구현한 드라이버 모듈을 제공해야 합니다. 이러한 서브루틴들은 아주 엄격한 명명 규칙과 코딩 규칙을 따릅니다. 이렇게 엄격한 규칙이 없으면 커널은 그것이 로드할 모든 드라이버들의 서브루틴을 직접 작성해야 할 것입니다. 각 드라이버들 마다 커스텀 서브루틴을 작성함으로써 커널이 디바이스 변화에 매우 민감하도록 영향을 주고 OS가 써드파티 주변기기 드라이버 시장이 성장함에 따라서 이러한 성장을 따라가야 하는 비실용적인 일이 뒤따릅니다. 현재 모든 운영체제들은 상대적으로 표준화된 디바이스 드라이버 인터페이스 (DDI) 와 디바이스-커널 인터페이스(DKI) 를 정의하고 있습니다.

솔라리스 DDI/DKI 는 두 부분을 가지고 있습니다. 한 부분은 호출 가능한 안정적인 함수 서브루틴들을 제공함으로써 개발을 단순화 하는 것입니다. 이러한 함수들은 또한 포팅이 가능하므로 드라이버의 특정 DDI/DKI 함수를 사용하는 것은 솔라리스 디바이스 드라이버가 32-비트 x86 플랫폼이나 64-bit x86 플랫폼, 혹은 약간의 코드 변경으로 SPARC 플랫폼 에서도 컴파일이 가능하도록 합니다. DDI/DKI 는 특정 플랫폼에서 실행될때 필요한 적절한 작업을 수행할 것입니다. 디바이스 드라이버 작성자들은 여전히 표준 C 라이브러리들을 사용할 수 있습니다. 그러나 I/O 에 접근하거나 데이타 내의 비트들을 수정해야 할때, DDI/DKI 함수들을 사용하지 않는다면 사이즈, 인디안 차이, 플랫폼간 호환성 같은 큰 문제를 유발 할 수 있음을 알아야 합니다. 아래의 그림은 기본적인 솔라리스 DDI/DKI 를 이용한 드라이버 모듈의 블럭 다이어 그램을 보여 줍니다.


그림 3 드라이버 모듈 블럭 다이어그램

이러한 호출 가능함 함수들과 더불어 DDI/DKI 의 나머지 한 부분은 개발자들이 드라이버들에 반드시 구현해야할 콜백 루틴 인터페이스들을 정의 합니다. 커널은 각 드라이버들이 그들의 인터페이스들을 구현하고 있음을 기대하고 있고 드라이버 모듈의 라이프사이클의 잘 알려진 단계들마다 이러한 함수들을 호출 하거나 “콜 백” 을 수행합니다. 이러한 콜백들은 로딩과 언로딩 작업, 초기화와 완료작업, 부탁및 탈착 작업, 인터럽트 핸들링의 설정, 그리고 물리적인 디바이스의 표준 I/O 컨트롤등을 다루는데에 필요한 인터페이스들을 포함합니다. 마지막으로 개발자들은 SCSI, SATA, USB, LAN 같은 특정 타입의 nexus 컨트롤러에 연결되는 데에 필요한 추가 인터페이스들을 구현해야 합니다.

드라이버 함수와 인터페이스에 관해 논의할때 기본적으로 떠오르는 질문은 드라이버내에서 드라이버의 상태가 어떻게 그리고 어디서 관리되냐는 것입니다. 3가지 기본 데이타 구조체가 솔라리스에서의 드라이버 상태를 추적합니다. 모든 드라이버들은 이러한 3가지 데이타 구조체를 참조 합니다. 최상의 레벨은 모듈 작업 혹은 mod_ops 구조체 입니다. 이 구조체는 솔라리스 내에서 모듈의 상태를 참조 합니다. mod_ops 구조체를 초기화 시에 시스템에 전달하고 또한 종료시에도 전달 합니다. mod_ops 구조체안에 참조중에 하나는 두번째 디바이스 구조체는 dev_ops 를 가르키고 있습니다. 디바이스 작업 구조체는 디바이스 상태를 추적하고 attach(), detach(), getinfo(), 그리고 probe() 를 위한 디바이스 콜백 포인터들을 가지고 있습니다. dev_ops 구조체는 또한 cb_ops 구조체에 대한 포인터를 가지고 있는데 이것은 실제 캐릭터 혹은 블럭 드라이버 작업 구조체로써 드라이버내에 DDI 스펙에 따라 구현된 함수들의 리스트를 가지고 있습니다.

드라이버 라이프사이클

드라이버 라이프사이클에 대해 좀 더 자세히 알아 봅시다. 커널이 드라이버를 로드할때 커널은 드라이버가 구현된 특정 서브루틴을 가지고 있고 이 서브루틴의 참조를 상태 추적을 위해서 모듈의 mod_ops 구조체에 담아서 넘기기를 기대 합니다. 필요한 서브루틴들은 DDI/DI 에 정의되어 있고, 최소한도로 _info()_init() 에 대한 콜백을 구현해야 합니다. 반대로 드라이버가 언로드 될때 커널은 드라이버의 _fini() 함수를 호출 하려고 합니다. 우리는 모든 드라이버가 반드시 구현해야 하는 3가지 기본 콜백들을 다음과 같이 정의할 수 있습니다:

_info(9E)
_init(9E)
_fini(9E)

시스템이 모듈을 로드한 다음 (그리고 또한 시스템이 모듈을 언로드하려고할 때) 커널은 두개의 콜백중에 하나를 찾아서 실제로 하드웨어의 초기화 및 종료를 수행합니다. 이러한 콜백들은 시스템이 모듈을 관리하는데에 필요한 것들과 유사 합니다. 그러나 하드웨어를 관리하기 위해 디자인되었습니다. 우리는 모든 드라이버가 반드시 구현해야할 두가지 필수 콜백을 다음과 같이 요약할 수 있습니다.:

attach(9E)
detach(9E)

최소한 모든 디바이스 드라이버들은 DDI/DKI 에서 정의된대로 반드시 위의 5개 함수들을 구현해야 합니다. DDI/DKI 는 또한 디바이스간의 통신과 I/O 를 수행하기 위해 커널과 디바이스 간 통신을 위하여 몇가지 표준 함수들을 정의하고있습니다. 이러한 함수들은 다음과 같습니다:

open(9E)
close(9E)
read(9E)
write(9E)

이러한 콜백들은 솔라리스가 디바이스에 요구하는 특정 매개변수를 가진 함수의 이름들입니다. 이로인해 커널이 드라이버에 I/O 를 알리거나 수행할 수 있도록 합니다.

아래의 커맨드를 이용해서 함수의 signature 와 매개변수를 확인하고 기능 요약에 읽고 드라이버에내에서 함수가 어떻게 사용되는지에 대한 샘플 코드들을 확인 하실 수 있습니다:

% man -s 9E function_name

좀 더 자세한 정보는 솔라리스에 따라오는 시스템 문서에서 찾으실 수 있습니다. 또한 자세한 레퍼런스 문서들을 온라인에서 찾아보실 수 있습니다 [1,4,5].

시스템 관리 - 솔라리스가 드라이버를 관리하는 방법

이 섹션은 디바이스 트리, 드라이버 관리 파일들 그리고 드라이버 관리 커맨드에 관해서 알아 봅니다.

커널의 32-비트와 64-비트 드라이버 소개

x86 플랫폼에서 솔라리스는 프로세서 아키텍쳐에 따라 32-비트 와 64-비트로 실행될 수 있습니다. 기본적으로 64-비트가 가능한 플랫폼에서 솔라리스는 64-비트 모드로 부팅되고 그러므로 드라이버들이 모두 64-비트 모듈 이기를 요구 합니다. 이것은 왜냐하면 커널 모듈은 나머지 커널들과 동일한 주소 공간을 공유하기 때문입니다. 만약 플랫폼이 오직 32-비트로만 동작이 가능하면 솔라리스는 32-비트 모드로 부팅되고 오직 32-비트 모듈들만 로드 합니다. 일반적인 규칙은 드라이버 바이너리는 반드시 커널의 비트-기반 포인터 사이즈와 매칭되어야 합니다. 그러나 어플리케이션은 커널 내에서 동작하지 않기 때문에, 64-비트 커널은 32-비트와 64-비트 어플리케이션을 실행시킬 수 있습니다. 32-비트 커널은 오직 32-비트 어플리케이션만을 실행할 수 있습니다.

grub 의 부트 메뉴를 바꾸고 grub 커맨드 라인을 수정함으로써 64-비트 시스템에서도 솔라리스를 32-비트 모드로 부팅 하는 것이 가능합니다. /boot/grub/menu.lst 파일을 수정하고 재부팅 하거나 혹은 재부팅 후에 grub 메뉴에서 e 를 입력 합니다.일반적으로 kernel/amd64/unix 혹은 kernel/amd64/unix or kernel/$ISADIR/unixkernel/unix 수정합니다. 이것은 /boot/grub/menu.lst 파일을 수정해서 32-비트에 적절한 항목을 추가하고 이것을 기본으로 설정함으로써 영구적으로 32-비트 부트모드가 되도록 합니다. [6]. 64-비트 시스템을 32-비트로 부팅할 퍼포먼스 측면에서의 이유는 하나도 없습니다. 단지 드라이버가 32-비트와 64-비트 모드에서 동일하게 동작하기 확인하기 위해 이런 일을 수행합니다.

드라이버 모듈 디플로이 장소

드라이버를 컴파일 한 다음에 모듈을 디플로이하려고 할때 솔라리스가 어디에 모듈들을 보관하고 있는지 알아야 합니다. x86 과 x64 플랫폼에서 솔라리스는 대부분의 드라이버들을 아래의 디렉토리에 보관합니다:

/kernel/drv

32-bit 드라이버 바이너리들과 driver.conf 파일

/kernel/drv/amd64

64-비트 바이너리들

모듈을 복사할 수 있는 다른 디렉토리들도 존재 합니다. 위의 디렉토리들은 x86 드라이버 바이너리들을 위해 추천되는 장소입니다. 일반적으로 드라이버 모듈을 빌드할때에는 32-비트와 64-비트 바이너리들을 생성하고 오직 하나의 모드로만 실행하길 원하더라도 이것들을 위의 디렉토리들에 배치합니다.

시스템 드라이버 상호작용 및 디바이스 트리

위에서 나온 솔라리스 디바이스 트리 에서 언급했듯이 솔라리스는 실제로 devfs 라고 부르는 파일 시스템형태로 디바이스 트리를 구현 합니다. 이 파일 시스템은 솔라리스에서 관리되고 있는 모든 물리 디바이스들의 네임 스페이스를 관리하고 부팅 시에 /devices 디렉토리에 마운트 됩니다. 이것은 일반적인 파일 시스템이 아니고 다른 어떠한 정규 파일 시스템 커맨드로도 변경될 수 없습니다. 대신 오직 운영체제만이 이 파일을 조정합니다. 디바이스에 연결된 드라이버 모듈이 초기화 되면 devfs 에 우선 노드가 생성 됩니다. ( 드라이버 라이프사이클 참고). 각 노드는 전체 디렉토리의 파일 리스트를 수행할때 볼 수 있는 것 처럼 메이저(major) 번호화 마이너(minor) 번호가 지정 됩니다. 메이저 번호는 커널이 해당 디바이스와 관련된 인터페이스를 로딩할때의 특정 드라이버 모듈을 나타 냅니다. 그리고 마이너 번호는 해당 디바이스의 인스턴스를 나타 냅니다. 기본적으로 첫번째 인스턴스 번호는 0 부터 시작 됩니다. 만약 동일한 드라이버 모듈을 사용하는 추가적인 유사 디바이스가 있다면 그들은 마이너 번호 1,2 등등으로 지정 됩니다.

/devices 디바이스 파일 시스템이 모든 물리적 디바이스를 나타낸다면/dev 파일 시스템은 이들 물리적 디바이스에 매핑되는 논리적인 이름 입니다. 다시 말해서 /dev 에 존재하는 논리 디바이스들은 단지 /devices 의 실제 노드에 대한 심볼릭 링크에 불과 합니다. /dev 파일 시스템은 실제 파일 시스템으로 수작업으로 변경이 가능하지만 추천되지는 않습니다.

디바이스 파일들을 위해 두개의 분리된 파일 시스템을 가지는 이유는 시스템 혹은 어플리케이션이 동일한 논리 디바이스 이름을 기대할 수도 있기 때문입니다. 그러나 다른 플랫폼 상에서 논리 디바이스는 다른 드라이버 모듈을 사용해서 다른 제조사에 의해 완전히 다른 디바이스에 의해 서비스 될 수도 있습니다. 다음의 오디오 예제를 생각해 보시기 바랍니다. 만약 오디오가 시스템에서 잘 작동하고 있다면 /dev/audio/dev/audioctl 논리 노드를 /dev 찾을 수 있을 것입니다. 이것은 오디오가 있는 어떠한 다른 플랫폼에도 동일합니다. 그러나 /dev/audio 는 어떤 플랫폼에서는 AC'97 오디오를 가르킬 수도 있고 다른 플랫폼에서는 HD 오디오를 가르킬 수도 있습니다. 그리고 이러한 디바이스들은 서로다른 드라이버 타입을 요구 합니다. 이러한 측면은 디스크 스토리지에도 적용 가능합니다. 솔라리스는 SCSI 디스크의 번호와 특정 타겟 컨트롤러를 찾을 수 있습니다. 이것은 일반적으로 /dev/rdsk/cXtYdZsN 에 매핑되고 여기서 X, Y, Z, 와 N 은 컨트롤러 버스, 컨트롤러 타겟, 디스크 넘버 혹은 LUN 그리고 슬라이스 넘버를 가르킵니다. 이러한 로지컬 디바이스는 온-보드 SCSI 컨트롤러를 가르킬 수도 있고 완전히 다른 RAID 디스크 호스트-버스 어댑터를 가르킬 수도 있습니다. 여전히 경로 이름은 유사한 논리 규칙을 따르기 때문에 이러한 방법을 통해 시스템 관리는 좀 더 쉽게 할 수 있습니다.

참고적으로 /devices 의 내용은 수정이 불가능 하지만 여전히 우리는 ls(1) 를 디렉토리에서 실행 할 수 있고 이것은 기억할만한 팁을 제공합니다: 드라이버가 디바이스에 연결되었는지 테스팅 하려면 드라이버가 로딩시에 /devices 에 생성되는 노드가 실제로 존재하는지를 단순히 리스트를 봄으로써 확인할 수 있습니다. 우리들은 /devices/dev 에 노드들을 로딩, 언로딩 및 관리하는 방법에 대해서 드라이버 관리, 추가, 제거, 및 업데이틀 위한 커맨드들 섹션에서 알아볼 것입니다.

드라이버 관리 파일들

지금까지 몇몇 독자들은 여전히 솔라리스가 어떻게 모듈을 로드하는지에 대해서 머리를 긁으면서 고민하고 있을지도 모릅니다. 다른 어떤 운영체제와 마찬가지로 PCI 버스 혹은 USB nexus 같은 다른 디바이스 허브에 의해 추출된 디바이스 ID 나 벤더에 의해 정의된 실제 디바이스들과 매핑되는 드라이버 모듈의 마스터 데이타베이스가 존재 합니다. 텍스트 파일은 다음의 위치에 존재하고 있습니다:

/etc/driver_aliases

다른 텍스트 파일은 실제 물리 디바이스 이름(실질적으로는/devices 로의 경로) 과 드라이버 의 인스턴스 번호의 매핑 정보를 담고 있습니다. 파일은 아래에 존재 합니다:

/etc/path_to_inst

시스템은 드라이버 이름과 메이저 번호와의 매핑 정보를 아래의 파일에 담고 있습니다e:

/etc/name_to_major

시스템은 드라이버의 각각의 인스턴스에 기본 허가 권한 및 접근 제어 목록을 아래의 파일을 이용해서 추적하고 있습니다:

/etc/minor_perms

driver_aliases, path_to_inst(4), name_to_major, 그리고 minor_perms 파일들이 직접 수정이 가능하지만 여러분들은 다음 섹션에서 다루는 드라이버 커맨드를 이용해서만 수정할 것을 권고 드립니다.

드라이버 관리, 추가, 제거, 및 업데이틀 위한 커맨드들

아래는 디바이스를 관리할때 사용되는 기본적인 커맨드들의 목록 입니다. 이러한 커맨드들은 위에서 설명한바와 같이 파일들을 직접 수정하는 대신에 드라이버 관리 파일들을 수정할때 권고되는 커맨드들 입니다.

add_drv

add_drv(1M) 커맨드는 새로운 드라이버를 추가 합니다. 예를 들어 드라이버 foobar 를 루트만이 접근 가능한 PCI 디바이스로 매핑 한다고 할때 우리는 아래의 커맨드를 실행 합니다:

# /usr/sbin/add_drv -i '"pci0909,5c"' \

    -m '* 0600 root sys' foobar

몇몇 경우에 드라이버는 특정 디바이스의 클래스를 구현하거나 속해 있을 수 있습니다. 예를 들어 호스트 버스 어댑터가 SCSI 인터페이스와 비슷하더라고 IDE, SATA 혹은 SAS 드라이브들을 연결시킬 수 있는 경우를 들 수 있습니다. 시스템에 이러한 드라이버를 명시해 주는 방법은 SCSI 클래스 인터페이스를 익스포팅 하는 것인데 예를 들어 간단히 -c scsiadd_drv(1M) 커맨드를 같이 사용 하는 것입니다.

rem_drv

rem_drv(1M) 커맨드는 기존의 드라이버를 제거 합니다. 드라이버를 /etc/driver_aliases 파일에서 제거 하지만 재부팅 후에도 여전히 모듈이 사용중이라면 /kernel/drv 에서 제거하지는 않을 것입니다.

# /usr/sbin/rem_drv foobar
update_drv

update_drv(1M) 커맨드는 현재 시스템에서 사용중인 드라이버의 속성을 변경 합니다. 종종 현재 드라이버가 지원하는 새로운 PCI 디바이스를 추가할때에 사용 됩니다. 이 커맨드의 포맷은 add_drv 커맨드와 유사 합니다. 예를 들어 foobar 드라이버 하에 지원되는 새로운 PCI 디바이스를 추가하고자 한다면 아래의 커맨드를 사용할 수 있습니다:

# /usr/sbin/update_drv -i '"pci0909,8f"' \

    -m '* 0600 root sys' foobar
modinfo

modinfo(1M) 커맨드는 현재 로딩된 모듈의 정보를 출력 합니다. 이 커맨드는 모듈이 시스템에 로딩 되어 있는지 그리고 모듈의 사이즈는 얼마인지를 확인하는 간편한 방법을 제공 합니다.

modload

modload(1M 커맨드는 수동으로 모듈을 로드 합니다.

modunload

modunload(1M) 커맨드는 모듈을 언로드 합니다. 만약 드라이버가 현재 사용중이라면 어떠한 효과도 없습니다.

devfsadm

devfsadm(1M) 커맨드는 /dev 링크와 /etc/path_to_inst 내의 내용을 유지합니다.

드라이버 제작의 기본

드라이버 바이너리를 빌딩할때에는 두가지 단계가 필요 합니다. 첫번째 단계는 드라이버 바이너리 객체를 생성하는 것입니다. 두번째 단계는 바이너리 객체를 링크 하는 것입니다.

단계 1: 바이너리 오브젝트 컴파일

드라이버 바이너리 객체 생성을 위해서 우리는 편의에 따라 썬 스튜디오 컴파일러 [7] 혹은 GNU C 컴파일러 (gcc) 를 이용할 수 있습니다. 리눅스 같은 몇몇 운영체제들은 개발자들에게 특정 커널 버전을 위해 엄격한 컴파일러 버전 가이드라인을 요구하고 있는데 이것은 새롭게 혹은 수정된 커널 모듈이 다른 커널들과 바이너리 호환이 되도록 하기 위해서 입니다. 리눅스와는 다르게 솔라리스는 커널과 C 라이브러리에 의해 공개적으로 사용되는 바이너리 인터페이스들에 대한 컨트롤을 강력하게 유지하고 있습니다. 그래서 드라이버 객체 컴파일을 위해 gcc 를 사용하든 썬 스튜디오 컴파일러를 사용하든 보통 안전합니다. 추가자료 [1] 의 리스트를 통해서 지원되거나 지원되지 않는 인터페이스들의 리스트를 확인하시기 바랍니다.

이것이 지원되거나 보장되진 않을 수 있지만 솔라리스에서 컴파일된 드라이버 바이너리들은 향후의 동일한 버전의 OS에서도 일반적으로 항상 호환될 것입니다. 다시 말해서 솔라리스10 1/06 드라이버 바이너리는 DDI/DKI 인터페이스를 따른다고 했을대 솔라리스10 5/08 에서도 어떠한 수정도 없이 잘 동작할 것입니다. 동일한 원리가 솔라리스 익스프레스 혹은 오픈솔라리스 드라이버에도 적용 됩니다. 향후의 운영체제 버전에서도 어떠한 변경이나 재컴파일 없이 잘 동작되어야 합니다. 여기서 참고로 그 반대는 그렇지 않을 수도 있습니다: 새로운 버전의 OS 에서 컴파일된 드라이버 구버전의 OS 에서는 사용이 불가능할 수 있습니다.

아래에는 몇가지 컴파일리 커맨드의 예제를 요약했습니다. 완전한 요약을 보시려면 추가자료 [5] 의 첫번째 장을 참조하시기 바랍니다.

만약 썬 스튜디오 10 혹은 썬 스튜디오 11을 이용해서 64-비트 x86 아키텍처를 컴파일 한다면 -xarch=amd64 옵션과 -xmodel=kernel 옵션 둘다 사용하시기 바랍니다:

# cc -D_KERNEL -xarch=amd64 -xmodel=kernel -c foobar.c

우리는 반드시 -D_KERNEL 플래그를 사용해서 커널 모듈을 컴파일 하고 있음을 알려야 합니다. 64-비트 컴파일의 경우 아키텍쳐(-xarch=amd64) 와 -xmodel 플래그를 둘다 지정함으로써 올바르게 64-비트 드라이버 오브젝트를 빌드할 수 있도록 해야 합니다. 또한 스튜디오12 와 최신 컴파일러들에서 -xarch=amd64 더이상 지원되지 않고 새로운 플래그인 -m64 가 추가 되었고 이것은 gcc 와 동일 합니다. 궁금해할 독자들을 위해 얘기하자면 우리들은 64-비트 바이너리를 32-비트 운영체제에서 빌드할 수 있습니다. 그러나 기본적으로 32-비트 와 64-비트 시스템 모두 기본적으로 특정 타겟 아키텍쳐 플래그가 지정되지 않았을때 32-비트 바이너리로 빌드 함을 기억해야 합니다.

만약 64-비트 x86 아키텍쳐를 GNU C 컴파일러를 이용해 컴파일 하고자 한다면 아래의 커맨드를 이용하시기 바랍니다:

# gcc -D_KERNEL -ffreestanding -m64 -c foobar.c

만약 32-비트 아키텍쳐를 썬 스튜디오 C 컴파일러를 이용해 컴파일 한다면 다음의 커맨드를 이용합니다:

# cc -D_KERNEL -c foobar.c

만약 32-비트 아키텍쳐를 GNU C 컴파일러를 이용해 컴파일 한다면 다음의 커맨드를 이용합니다:

# gcc -D_KERNEL -ffreestanding -c foobar.c

단계 2: 바이너리 오브젝트 링크

모든 드라이버 바이너리들은 반드시 최종 바이너리 오브젝트에서 심볼 해석이 가능하도록 링크 되어야 합니다. 다른 커널 드라이버 프레임워크나 드라이버 모듈에 의존적이지 않은 기본 모듈들에는 아래와 같이 간단한 커맨드를 사용합니다:

# ld -r -o foobar foobar.o

많은 경우에 드라이버 모듈은 다른 드라이버 모듈에 의존하고 있거나 반드시 다른 커널 라이브러리들과 동적으로 링크 되어야 합니다. 아래에 두가지 예를 보여 드립니다.

아래의 예제는 솔라리스 오디오 지원, 믹서에 의존하고 있는 오디오 드라이버 바이너리 오브젝트를 링크하고 있는 예제 입니다. -dy-N 플래그들은 일반적으로 같이 사용되서 드라이버 모듈을 커널 라이브러리들에 동적으로 링크 시킬때 사용됩니다.

# ld -r -dy -N misc/audiosup -N misc/mixer \

    -N misc/amsrc -o audiodrv audiodrv.o

아래의 예제는 일반적인 네트워크 드라이버를 솔라리스 의 범용 LAN 드라이버(GLD) 레이어와 함께 링크 시키는 예제 입니다.

# ld -r -dy -N misc/gld -o nicdrv nicdrv.o

여러분은 file(1) 커맨드를 통해서 손쉽게 주어진 모듈이 32-비트 인지 64-비트인지를 알 수 있습니다. file 커맨드는 바이너리가 ELF 32-비트 인지 혹은 ELF 64-비트 바이너리 인지를 출력해 줍니다. 마지막으로 make(1S) 시스템 Makefile 에 이러한 커맨드를 추가시켜 줄 것을 강력히 추천 드립니다.

종합편 - 샘플 드라이버

이 섹션은 최소한 요구되는 인터페이스들을 구현한 뼈대(skeleton) 드라이버를 보여주고 있습니다. 이 섹션은 뼈대 드라이버의 헤더 파일과 드라이버의 설정 파일들을 포함합니다.

뼈대 드라이버

일반적으로 드라이버는 하나 혹은 그 이상의 C 파일로 이루어지고 커널이 드라이버를 접근하기 위한 함수들을 구현하고 있습니다. 모듈, 디바이스, 드라이버 관련 구조체들은 일반적으로 헤더 파일이 아닌 각 함수가 구현된 C 파일에 정의 되는 것이 일반적입니다. 드라이버의 이름을 함수의 prefix 나 전역 변수로 지정하는 것이 일반적입니다. 예를 들어 attach() 서브루틴은 foobar_attach() 로 구현됩니다. 또한 모듈을 로딩하고 시스템이 메이저 및 마이너 번호를 생성하도록 하는 시스템과의 상호작용의 대부분이 foobar_attach() 루틴 안에서 이루어 집니다.

/*
* foobar.c - example skeleton driver
*/
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/sunddi.h>
#include <sys/stat.h>

#include "foobar.h"

static int foobar_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);

static int foobar_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

static struct cb_ops foobar_cb_ops = {
nodev, /* no open */
nodev, /* no close */
nodev, /* no strategy (only for block drivers) */
nodev, /* no print */
nodev, /* no dump (only for block drivers) */
nodev, /* no read */
nodev, /* no write */
nodev, /* no ioctl */
nodev, /* no devmap */
nodev, /* no mmap */
nodev, /* no segmap */
nochpoll, /* no chpoll entry point */
ddi_prop_op, /* Use system-supplied prop_op entry point */
NULL,
D_NEW | D_MP
};

static struct dev_ops foobar_ops = {
DEVO_REV,
0,/* reference count: always 0 initially */
nulldev, /* No getinfo entry point */
nulldev, /* DEPRECATED: identify entry point */
nulldev, /* no probe entry point */
foobar_attach,
foobar_detach,
nodev, /* no reset entry point */
&foobar_cb_ops, /* Reference the cb_ops defined above */
(struct bus_ops *)NULL /* Not a nexus driver, so no bus_ops */
};

extern struct mod_ops mod_driverops;

static struct modldrv Modldrv = {
&mod_driverops, /* Use system-supplied mod_driverops */
"foobar driver v" FOOBAR_VERSION, /* Module Name/Version */
&foobar_ops,
};

static struct modlinkage Modlinkage = {
MODREV_1,
&Modldrv,
NULL
};

/*
* This bit of static data is used by the DDI to
* keep track of the per-instance driver "soft state"
*/
static void *soft_statep;
int
_init(void)
{
/*
* Initialize the soft state APIs so we can
* allocate soft state in foobar_attach()
*/
if (ddi_soft_state_init(&soft_statep,
sizeof (struct foobar_state), 1)
!= DDI_SUCCESS)
return (DDI_FAILURE);

if (mod_install(&Modlinkage) != 0) {
ddi_soft_state_fini(&soft_statep);
return (-1);
}

return (0);
}

int
_info(struct modinfo *modinfop)
{
return (mod_info(&Modlinkage, modinfop));
}

int
_fini(void)
{
ddi_soft_state_fini(&soft_statep);
return (mod_remove(&Modlinkage));
}

static int
foobar_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
/* Use the instance number as the minor number */
instance = ddi_get_instance(dip);

if (ddi_soft_state_zalloc(soft_statep, instance)
== DDI_FAILURE)
return (DDI_FAILURE);

softp = ddi_get_soft_state(soft_statep, instance);
ASSERT(softp != NULL);

if (ddi_create_minor_node(dip, FOOBAR_MINOR_NAME,
S_IFCHR, instance, DDI_PSEUDO, 0)
!= DDI_SUCCESS) {
cmn_err(CE_WARN, "Minor creation failed!");
return (DDI_FAILURE);
}
softp->init_state |= FOOBAR_INIT_MINOR;
softp->dip = dip;
mutex_init(&softp->mutex, NULL, MUTEX_DRIVER, 0);
softp->buffer = (char *)kmem_alloc(FOOBAR_BUFLEN,
KM_SLEEP);
ddi_report_dev(dip); /* Announce we've attached! */
return (DDI_SUCCESS);
}

static int
foobar_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
struct foobar_state *softp;

if (cmd != DDI_DETACH)
return (DDI_FAILURE);

/* Use the instance number as the minor number */
instance = ddi_get_instance(dip);

softp = ddi_get_soft_state(soft_statep, instance);

ASSERT(softp != NULL);
if (softp->init_state & FOOBAR_INIT_MINOR) {
/* Remove minor nodes associated with dip */
ddi_remove_minor_node(dip, NULL);
}
ASSERT(softp->buffer != NULL);
kmem_free(softp->buffer, FOOBAR_BUFLEN);

ddi_soft_state_free(soft_statep, instance);

return (DDI_SUCCESS);
}

뼈대 드라이버 헤더 파일

헤더 파일은 아래와 같이 나머지 정의들을 포함하고 있습니다.

/*
* foobar.h - example skeleton driver header
*/

#ifndef _FOOBAR_H
#define _FOOBAR_H

#define FOOBAR_INIT_MINOR 0x00000001

#define FOOBAR_VERSION "1.0"
#define FOOBAR_BUFLEN 1024
#define FOOBAR_MINOR_NAME "xyzzy"

struct foobar_state {
dev_info_t *dip; /* Opaque dev_info pointer */
int init_state; /* See FOOBAR_INIT_* */
kmutex_t mutex; /* driver lock */
char *buffer; /* message buffer */
};

#endif /* #ifdef _FOOBAR_H */

뼈대 드라이버 설정 파일

마지막 파일은 드라이버 설정 파일 입니다. 이 예제에서 드라이버 설정 파일은 foobar.conf 입니다. 드라이버의 .conf 파일은 드라이브의 내부 설정을 오버라이드 할때 사용되고 드라이버 초기시 딱 한번 읽혀 집니다. 대부분의 현대 드라이버에서는 .conf 파일이 필요하지 않습니다. 왜냐하면 드라이버 코드 자체 내에서 명시적으로 지정될 수 있기 때문입니다. 파일의 포맷은 보통 아래와 같이 타겟 드라이버의 이름과 드라이버의 부모 노드를 지정합니다.

#
# foobar.conf - example skeleton driver conf file
#

name="foobar" parent="pseudo" instance=0;

시스템이 64-비트 더라도 이 driver.conf 파일은 반드시 32-비트 드라이버 디렉토리(/kernel/drv) 에 위치해야 함을 기억하시기 바랍니다. even if the system is 64-bit.

향후 작업

이 시리즈의 다음글에서 우리는 사람들이 미리 만들어 놓은 특정 타입의 드라이버를 위한 프레임워크에 대해 다룰 것이고 이 프레임워크에 대한 정보들을 제공할 것입니다.

감사의 인사

이 문서는 썬에서 근무하고 있는 Seth Goldberg 없이는 작성되지 않았을 것입니다. 이 문서는 Seth 의 작업을 기반으로 James 와의 공동작업으로 쓰여 졌고 인텔 개발자 포럼 2007 에 솔라리스 디바이스 드라이버란 제목으로 소개 되었습니다.

참고자료

  1. Writing Device Drivers, http://docs.sun.com/app/docs/doc/819-3196, Sun Microsystems, Inc., 2008.
  2. OpenSolaris.org source tree, http://cvs.opensolaris.org/source/.
  3. PCI SIG: PCI documentation and standards, http://www.pcisig.com/home.
  4. Max Bruning, “Inside OpenSolaris: Introduction to Solaris Drivers,” http://opensolaris.org/os/article/2005- ··· ivers%2F, March 31, 2005.
  5. Device Driver Tutorial, http://docs.sun.com/app/docs/doc/819-3159, Sun Microsystems, Inc., 2008.
  6. “Boot into 32-bit kernel on 64-bit platform,” http://blogs.sun.com/alta/entry/boot_into_32_bit_kernel, January 30, 2008.
  7. Sun Developer Network: Sun Studio, http://developers.sun.com/sunstudio/index.jsp.
  8. OpenSolaris Device Drivers Community, http://www.opensolaris.org/os/community/device_drivers/.

이 글의 영문 원본은
So You Wanna Write Solaris Device Drivers?
에서 보실 수 있습니다.

"개발자코너" 카테고리의 다른 글

2008/09/18 15:13 2008/09/18 15:13

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/661

댓글을 달아 주세요

[로그인][오픈아이디란?]

◀ Prev 1  ... 36 37 38 39 40 41 42 43 44  ... 641  Next ▶