2. devfs 를 사용할 이유

devfs를 필요로 하는 여러 문제점이 있다. 이들 문제들 중에 어떤 것들은 (당신의 관점에 따라서) 매우 심각하고, 어떤 것들은 devfs 없이 해결할 수 있다. 그러나, 전체적인 면에서 보면 이 문제들은 devfs를 필요로 한다.

선택할 수 있는 것은 복잡하고 헛점투성이인 필요없는 userspace 에서의 해결 패치를 내놓는 것과, 간단하고 견고하며 효과적인 devfs를 사용하는 것의 둘 중 하나이다.

devfs에 대한 많은 반론이 있고, 모두다 devfs를 구현하지 않으면 얻어지는 이익들을 포함하고 있다. 하지만, 지금까지는 코드가 없거나, devfs가 제공하는 모든 기능들을 제공할수 있는 어떤 대안도 없다. 게다가, 제안된 대안들은 사용자 공간에서 더 복잡하다. (그리고 devfs보다 더 적은 기능을 가진 상태로 제공된다) 어떤 사람들은 "커널 크기"의 축소를 목표로 삼지만, 그들은 userspace 에 미치는 영향은 고려하지 않는다.

커널공간과 사용자 공간의 전체적인 복잡함의 정도를 제한한 (복잡성이 덜한) 방법이 좋은 솔루션이다.

2.1. 메이저 번호와 마이너 번호의 할당

현존하는 스키마는 각각의 디바이스 마다 메이저/마이너 디바이스 번호의 할당을 요구한다. 이것은 각 디바이스마다 고유한 번호를 가질 수 있게 하기 위하여 (당신이 "개인적"인 디바이스 드라이버를 개발하지 않는한) 이들 디바이스 번호들을 처리하기 위하여 중앙집중적인 권한이 요구된다는 것을 의미한다. Devfs는 네임스페이스에 대한 부하를 덜어준다. 이것은 크게 부하를 덜어주는 것처럼 보이지 않지만, 실제로는 엄청난 효과를 낸다. 드라이버 관리자들은 자연적으로 그 디바이스의 기능을 반영하는 디바이스 이름을 선택하므로, 네임스페이스의 충돌에 대한 잠재적 위험은 적다. 이것을 해결하기 위해서는 커널의 변경이 필요하다.

2.2. /dev 관리

현재의 디바이스 접근 방식은 디바이스 노드를 통해서 행해지는 방식이기 때문에, 시스템 관리자에 의해서만 디바이스 노드가 만들어져야만 한다. 표준 디바이스에 대하여 당신은 모든 (수백개의!)노드를 만들어내는 MAKEDEV라는 프로그램을 찾을 수 있다. 이것은 커널에서의 변화는 MAKEDEV 프로그램에서 반영되어야 하거나, 시스템 관리자가 수동으로 디바이스 노드를 만들어야 한다는 것을 의미한다.

기본적인 문제는 메이저 번호와 마이너 번호를 구분하는 두개의 데이터베이스가 존재한다는 것이다. 하나는 커널에 있고, 나머지는 /dev에 있다 (또는 만약 당신이 그런 방법으로 보기 원한다면 MAKEDEV 프로그램에 있다.). 이것은 실용적이지 못한 정보의 중복이다. 이 문제를 해결하기 위해서는 커널의 변경이 필요하다.

2.3. /dev 디렉토리의 증가

전형적인 /dev 디렉토리에는 1200개 이상의노드가 있다! 이 디바이스들의 대부분은 하드웨어가 없기 때문에 존재하지 않는다. 이 거대한 /dev 는 디바이스 접근 시간을 증가시킨다 (나는 단지 dentry 탐색 시간과 존재하지 않는 디스크의 inode를 읽는데 걸리는 시간만을 계산한 것이다 : 다음 항목에서는 좀더 끔찍한 예를 보여준다)

다음의 예는 SCSI 디바이스를 고려한다면 /dev 디렉토리가 얼마나 커질수 있는 가를 보여준다.
host           6  bits  (say up to 64 hosts on a really big machine)
channel        4  bits  (say up to 16 SCSI buses per host)
id             4  bits
lun            3  bits
partition      6  bits
TOTAL          23 bits
	
가능한 모든 디바이스 노드를 저정하기 원한다면 8메가(1024*1024)의 inode를 필요로 한다. 만약 id, partition 을 제외하고 SCSI 타겟(id)당 하나의 SCSI 버스와 하나의 논리적 유닛을 가지는 호스트 어뎁터로 가정한다 할지라도, 여전히 10bits 또는 1024개의 노드가 남는다. 각 VFS inode는 대략 256 bytes를 차지하므로 (kernel 2.1.78), (실제 inode 역시 VFS inode와 유사한 양의 공간을 차지한다고 가정하면) 디스크상에 256kBytes의 inode 저장공간이 필요하다. 오늘날 디스크는 저렴하므로, 나쁘지는 않다. 임베디드 시스템은 /dev inode의 256kBytes에 대해 주의해야 하지만 임베디드 시스템은 /dev 디렉토리를 수동으로 조정할 수 있다. 나 자신의 임베디드 시스템에서는 그렇게 해야겠지만, devfs를 사용하는 방법을 택할 것이다.

다른 문제는 최초로 참조될때 inode 를 탐색하는데 걸리는 시간이다. 메모리상의 리스트를 통하여 탐색하는데 걸리는 시간 뿐만 아니라, 디스크에서 inode를 읽는데 걸리는 시간도 있다. 이 문제는 커널로그를 분석하여 필요하지 않으면 /dev 개체를 삭제하고 필요할때 생성하는 프로그램을 사용하여 사용자 공간상에서 해결할 수 있다. 이 프로그램은 새로운 모듈이 로드될때마다 실행될 것이고, 많은 부분 느려지게 될 것이다.

SCSI 디바이스에 대하여 디바이스 노드를 자동으로 만드는 scsidev 라 불리는 프로그램이 있다. 이 프로그램은 /proc/scsi에서 파일을 탐색하는 방법으로 작동한다. 불행히도, 다른 디바이스 노드에 대해 이 개념을 확장하기 위해서는 현존하는 드라이버들을 많은 부분 수정하는 것이 필요하다 (/proc 파일 시스템에 정보를 제공해야 하기 때문에). 이것은 간단한 변경이 아니다 (나는 devfs가 유사한 일을 한다는 것을 알고 있다). 당신이 이러한 모든 노력을 하더라도, (이 정보를 제공하고 있는) devfs를 사용하는 것이 나을 것이다. 게다가, 여러개의 서로 다른 디바이스 드라이버들이 서로 다른 방법으로 각각의 정보를 제공하는 것처럼, 그러한 시스템은 특수한 목적을 위해 만들어지는 시스템들과 마찬가지로, 각각의 특성에 맞게 구성되어야 한다.

Devfs는 디바이스 노드 스스로 자연스럽게 정보를 제공하게끔 하는 일정한 메커니즘을 가지고 있으므로 시스템의 구성이 보다 더 명확해진다!!

2.4. 디바이스 드라이버의 struct file_operations 구조체에 대한 노드

디바이스 넘버를 가지고 (disc-based) /dev 디렉토리 아래의 문자 / 블럭 장치 노드들과 실제 디바이스 드라이버를 연결하는 방법과, devfs 에서 등록된 개체들이 실제 디바이스 드라이버에 연결되는 방법 사이에는 매우 중요한 차이점이 있다.

8비트의 메이저/마이너 번호를 가지고 disc-based c&b 노드들과 해당 메이저 넘베의 디바이스 드라이버를 연결하는 것은 고정된 128 개의 엔트리 테이블을 통하여 이루어진다. 파일시스템들은 문자/블록 장치파일에 대한 inode operation 을 {chr,blk}dev_inode_operations 로 설정해 둔다. 따라서, 디바이스가 open 되었을 때, 약간의 indirect call 만으로 빠르게 file_operation 구조체의 함수 포인터들을 참조할 수 있게 된다.

다른 기타의(miscellaneous) 문자 디바이스들 -- (역자주 : 마이너 넘버들에 따라 전혀 다른 장치가 될 수도 있는 장치들, 예:tty (major number 3,4)) -- 때문에 두번째 단계가 필요하다 : 그 파일이 열렸을때 같은 마이너 번호를 가진 드라이버 엔트리를 탐색하고, 적당한 마이너 오픈 메쏘드를 호출한다. 이 탬색은 디바이스 노드를 열때 항상 수행된다. 어쩌면, 오픈 메쏘드를 찾기 전에 수십개의 miscellaneous 개체를 탐색할 것이다. 많은 오버헤드를 일으키지는 않는다 하더라도, 필요없는 작업으로 보인다.

리눅스는 때때로 8비트의 메이저/마이너 경계를 넘어서야만 하는 경우도 있다. 만약 16비트로 각각의 번호의 크기를 증가시킨다면, 메이저 테이블은(캐릭터 및 블럭 디바이스에 대해 하나의) 64k 엔트리가 필요하기 때문에 (x86 에서는 512kBytes, 64 비트 시스템에서는1 메가바이트), 그 메이저 드라이버를 사용하는 인덱싱 스키마는 사용할 수 없게 된다. 따라서 miscellaneous 캐릭터 디바이스를 사용하는 것과 비슷한 스키마를 사용해야만 한다. 이것은 탐색시간이 당신의 시스템에서 메이저 디바이스 드라이버의 평균 수에 따라 선형적으로 증감한다는 것을 나타낸다. 모든 "디바이스"가 하드웨어는 아니다. 어떤 것은 KGI처럼 좀더 고수준의 드라이버들이다. 따라서 당신은 하드웨어의 증가 없이 더 많은 "디바이스"들을 얻을 수 있다. 당신은 질서정연한(균형잡힌:-) 바이너리 구조를 생성함으로써 이것을 향상 시킬수 있고, 이 경우에 탐색시간은 log(N)으로 된다. 다른 방법으로는, 탐색시간을 향상시키기 위해 해싱(hashing)을 사용할 수 있다. 그러나 꼭 해야만 하는 것이 아니라면 왜 모두를 탐색하려 하는가? 다시 한번, 이것은 필요없는 것처럼 보인다.

devfs는 메이저&마이너 시스템을 사용하지 않는 다는 것을 기억하라. devfs 엔트리에 대한 연결은 /dev 개체를 탐색했을때 일어난다. devfs_register() 함수가 호출되면, 내부의 테이블은 그 엔트리 이름과 file_operations을 추가한다. 그 dentry 캐시가 미리 /dev 엔트리를 가지고 있지 않다면, 이 내부의 테이블은 file_operations를 얻기 위하여 탐색되고, inode는 생성된다. 만약 dentry 캐쉬가 이미 그 엔트리를 가지고 있다면, 탐색시간은 필요없다 (other than the dentry scan itself, but we can't avoid that anyway, and besides Linux dentries cream other OS's which don't have them:-). 또한, devfs에 노드 엔트리의 수는 사용가능한 디바이스 엔트리의 수와 같다. 즉, "상상할 수 있는" 수가 아니다. disc-based /dev에서 필요없는 엔트리를 지운다 하더라도, 공간을 절약하기 위하여 스스로 제한하더라도 상상할 수 있는 엔트리의 수는 여전히 같은 채로 남아있다.

Devfs는 VFS 노드와 디바이스 드라이버 사이의 빠른 연결을 제공한다.

2.5. 시스템 관리 도구로서의 /dev

/dev 디렉토리는 내가 가지고 있지 않는 대부분의 디바이스를 포함하여 가능한 모든 디바이스의 노드를 포함한다. Devfs는 단지 시스템에서 사용가능한 디바이스만 보여준다. 이것은 /dev의 목록을 만든다는 사용가능한 디바이스를 편리한 방법으로 체크한다는 것을 의미한다.

2.6. 메이저&마이너 크기

현재 메이저/마이너 번호들은 각각 8비트로 제한되어 있다. 이것은 하나의 메이저 넘버를 소비하는 SCSI 디스크 드라이버와 같은 드라이버에서는 한계요인이 된다. 16개의 디스크만 지원되고 각 디스크는 15개의 파티션을 가질 수 있다. 아마 이것은 당신에게는 문제가 아닐지 모르나, 디스크 어레이를 사용하여 큰 리눅스 시스템을 만드는 이에게는 큰 문제가 된다. Devfs에서 임의의 포인터는 32비트 디바이스 구분자를 (i.e. 32비트 마이너 넘버를 가지는 것처럼 보이는) 사용가능한 각각의 디바이스 엔트리로 연결시킬수 있다. 이것은 커널에만 관련된 것이므로, 메이저/마이너 번호를 크기를 증가시키는 것에 대하여 C 라이브러리와의 호환에 대한 논란은 필요없다. 사용자 공간에서 호환성을 유지하는 것에 대한 세부사항은 "디바이스 번호의 할당"에 대한 섹션을 보라.

이 문제를 해결하는데는 커널의 변경이 필요하다.

이것을 작성함으로써, 커널은 SCSI 디스크 드라이버가 더 많은 메이저 번호를 할당받고, 128 디스크 이상을 지원하도록 수정되었다. 이들 메이저 번호들이 비연속적 (무계획적인 확장의 결과로 인해) 이므로, 그것의 구현은 처음보다 더 성가신 일이 되었다.

IP 주소의 부족을 일시적으로 극복하기 위해 IPv4 를 변경하는 것과 마찬가지로, 사람들은 제한사항이 생기면 그것을 둘러갈 트릭을 찾는다. 그러나 IPv6 나 devfs 와 같은 근본적인 해결책의 도입은 더이상 늦춰져서는 안된다.

2.7. 읽기전용 루트 파일시스템

루트 파일시스템에 디바이스 노드를 가진다는 것은, 만약 루트 파일 시스템이 읽기 전용으로 마운트되었을 경우엔 여러분의 시스템이 제대로 작동하지 못한다는 것을 의미한다. 그것은 tty 디바이스의 소유권과 보호권을 변경해야 하기 때문이다. 실제로 루트 파일시스템 디바이스로서 읽기 전용 장치인 CD-ROM 을 사용하는 것은 허용되지 않는다. 확실히, 당신은 CD-ROM을 이용하여 부팅을 할수는 있으나, tty의 소유권을 변경하지는 못한다. 따라서 그 방법은 인스톨할때나 좋은 방법이다.

또한, 디스크가 없는 (discless) 리눅스 머신 에서 (보통 /dev 에서 변경된 tty 소유권을 가지는 것은 좋지 못하다) 공유된 NFS 루트 파일시스템을 가질 수도 없다. 또한 ROM-FS 에 당신의 루트 파일시스템을 포함시킬수도 없다.

당신은 부팅시에 RAMDISC 를 만들고, 그것에 ext2 파일시스템을 만든후, 그것을 어떤 장소에 마운트하고 /dev의 목록을 그것에 복사한 후, 마운팅을 해제하고 /dev에 그것을 다시 마운팅 하는 방법을 사용하게 될 것이다.

Devfs는 이 문제를 해결하는데 있어 보다 명확한 방법을 제공한다.

2.8. Non-Unix 루트 파일시스템

Non-Unix 파일시스템(NTFS와 같은)은 그것들이 다양한 캐릭터/블럭 스페설 파일 또는 심볼릭 링크들을 지원하지 않기 때문에 루트 파일시스템으로 사용할 수 없다. 당신은 마운트 하기 전에 디바이스 노드를 필요로 하기 때문에 /dev 에 마운트된 파일시스템이 disc-based 인지 RAMDISC-based 인지 구분할 수 없다. Devfs는 디바이스 노드 없이 마운트 할 수 있다. Devlink는 심볼릭 링크(symlink)의 지원이 안되기 때문에 작동하지 않을 것이다. 다른 해결책은 RAMDISC 초기 루트 파일시스템 (소수의 선택된 디바이스 노드를 포함하는) 을 마운트 하기 위하여 initrd를 사용하고, 다른 RAMDISC 에 새로운 /dev를 만든 후, 마지막으로 non-Unix 루트 파일시스템으로 바꾸는 것이다 이것은 영리한 부트스크립트와 헛점이 많고 복잡한 부트 과정이 필요하다.

Devfs는 견고하고 개념적으로 간단한 방법으로 이 것을 해결한다.

2.9. PTY 보안

현재의 pseudo-tty(pty)는 루트가 소유권자이고, 모든 사용자가 읽고 쓰기가 가능하다. pty-pair의 사용자는 suid-root 없이는 소유권/보호권을 바꿀수 없다.

이 문제는 루트로 수행되고 실제 pty-pairs 를 만들어 내는 userspace 에서 작동하는 보안 데몬을 사용함으로써 해결될 수 있다. 이 같은 데몬은 이런 새로운 매커니즘을 사용하고자 하는 "모든" 프로그램에 수정을 요한다. 또한 pty-pairs를 생성하는 데에도 속도저하가 일어난다.

다른 해결책은 사용자공간의 데몬과 비슷한 일을 하는 새로운 open_pty() 시스템콜을 만드는 것이다. 다시 한번 말하지만, 이것은 pty 핸들링 프로그램들의 수정을 요구한다.

devfs 의 해결책은 열려있지 않은 디바이스가 열렸을때 디바이스 드라이버가 어떤 디바이스 파일을 "tag"하도록 허용하고, 소유권은 열린 프로세스의 현재 euid와 egid로 변화되고, 보호권은 그 드라이버에 의해 등록된 기본값으로 변경된다. 그 디바이스가 닫히면, 소유권은 루트로 돌아가고 보호권은 모든 이에게 읽고쓰기 가능으로 바뀐다. 어떤 프로그램도 수정될 필요가 없다. devpts 파일시스템은 Unix98 ptys 에 대하여 이러한 자동-소유권의 기능을 제공한다. 이것은 옛방식의 pty 디바이스는 지원하지 않고, devfs가 제공하는 다른 기능들을 모두 제공하지 않는다.

2.10. Intelligent device 유지

Devfs는 사용자 공간에서 작동하는 디바이스 관리 데몬(devfsd)과의 통신을 위하여 간단하지만 강력한 프로토콜을 구현한다. 이것은 디바이스 엔트리의 등록/등록해제, 디바이스의 open/close, inode 의 탐색 디렉토리와 다른 것들의 탐색 등과 같은 이벤트가 일어나면 (동기 또는 비동기적으로) devfsd 에 메세지를 보내는 것이 가능하다. 이것은 많은 잠재성을 가지게 된다. 그 잠재성 중의 어떤 것은 이미 구현되어 있다. http://www.atnf.csiro.au/~rgooch/linux/을 살펴보라

디바이스 엔트리의 등록 이벤트는 새롭게 생성되는 디바이스 노드의 퍼미션을 변경할때 devfsd에 의해 사용될 수 있다. 이것은 디바이스의 퍼미션을 제어하기 위한 하나의 매커니즘이다.

디바이스 엔트리의 등록/등록해제 이벤트는 프로그램이나 스크립트를 구동시키는데 사용된다. 이것은 새로운 블럭 디바이스 미디어에 드라이버가 추가되었을때 파일시스템의 자동적인 마운트를 제공하는데에도 사용될 수 있다.

비동기 디바이스의 open/close 이벤트는 퍼미션 관리를 구현하는데 사용될 수 있다. 예를 들어, /dev/dsp에 대한 기본 퍼미션은 모든 사용자가 그 디바이스를 읽는 것을 허용하지 않는다. 이것은 당신이 콘솔에 얘기하는 것을 원격 사용자가 녹음하는 것을 원하지 않을 것이기 때문에 합당한 일이다. 그러나, 콘솔 사용자 역시 녹음하는 것이 막혀있다. 이것은 바람직한 것이 아니다. 비동기 디바이스에 open/close 이벤트를 이용하여 당신은 콘솔디바이스가 다른 디바이스 노드 (예를 들어 /dev/dsp)에 대한 소유권을 변경하기 위해 열렸을때, devfsd가 어떤 프로그램이나 스크립트를 수행하도록 할 수 있다. 마지막으로, 당신은 다른 스크립트를 이용하여 퍼미션을 복구 할 수 있다. C 라이브러리의 tty 핸들링의 수정이 필요없는 이런 설계의 이득은 당신의 프로그램이 충돌이 일어나더라도 (당신은 존재하지 않은 로긴을 위하여 지체되는 엔트리를 가지는 utmp 데이터 베이스를 수없이 보아오지 않았는가?) 작동한다는 것이다.

동기 디바이스의 열기 이벤트는 디바이스가 접근권한을 설정하는데 사용될 수 있다. 디바이스 드라이브의 open() 메소드가 호출되기 전에, 데몬은 외부 프로그램이나 스크립트를 실행시킴으로써 처음으로 열기시도를 확인할 것이다. 이것은 그에 대한 접근 자체가 UID와 GID 대신에 다른 시스템의 상태에 기초하여 결정되기 때문에 접근 제어 리스트를 사용하는 것보다 훨씬 더 유연하다.

Inode 탐색 이벤트는 모듈의 자동로드 요청을 인증하는데 사용될 수 있다. kmod를 직접적으로 사용하는 대신에, 이 이벤트는 모듈 스스로가 적재되기 전에 임의적으로 인증을 구현할수 있는 devfsd 에 보내진다.

Inode 탐색 이벤트는 존재하지 않는 디바이스에 대하여 심볼릭 링크로 devfs 를 위치시키기 위해 복구하지 않고도 임시로 네임스페이스를 구성하는데 역시 쓰일수 있다.

2.11. 이론적인 디바이스 탐색

모듈이 올라와 있던 아니던 시스템에 있는 모든(SCSI, IDE 및 다른 타입들) CD-ROM 디바이스를 찾기 위한(cdparanoia와 같은) 어플리케이션을 고려해보자. 이론적으로 어플리케이션은 해당 모듈이 적재되어 있는지 확인하기 위해서는 디바이스 노드를 (SCSI CD-ROM의 경우 /dev/sr0) 열어야만 한다. 표준 디바이스 네이밍 스키마 (최근에 레드햇은 이것을 다르게 구현한 것을 확인했다) 를 따르는 모든 리눅스 배포판은 이러한 작업을 필요로 한다. Devfs는 그러한 네이밍 문제를 해결한다.

그와 같은 어플리케이션은 또 시스템에 실제 사용가능한 디바이스를 찾기를 원할 수도 있다. 현존하는 시스템으로 이같은 일은 /dev 디렉토리를 읽고, 그 디바이스가 존재하는지 안하는지를 결정하기 위해서는 각 /dev/sr* 디바이스들을 open 해 봐야 한다. 큰 /dev 를 가지는 시스템에서, 그 시스템이 특히 많은 수의 /dev/sr* 노드를 가지고 있다면, 이것은 비효율적인 작업이다. scsidev와 같은 해결책은 /dev/sr* 엔트리의 수를 줄여줄수 있다 (그러나 역시 불필요한 디렉토리 탐색을 하게 된다).

Devfs를 사용하면, 그 어플리케이션은 /dev/sr 디렉토리 (필요하다면 모듈의 자동적재를 유도해내는)를 읽을수 있고, /dev/sr를 읽기 위하여 계속 진행된다. 오직 사용가능한 디바이스들만이 엔트리를 가지므로, 불필요한 디렉토리 탐색이나, 디바이스의 오프닝은 필요없다.