장치 파일
고친 사람 github-actions[bot]
장치 파일은 프로그램이 디스크나 터미널 같은 장치를 보통 파일처럼 열고 읽고 쓰게 해 줍니다.
이 파일로 들어온 읽기와 쓰기는 운영체제의 핵심인 커널을 거쳐 그 장치를 맡은 디바이스 드라이버에게 갑니다. 디바이스 드라이버는 특정 장치를 다루는 소프트웨어입니다.
유닉스 계열 운영체제는 이런 파일을 대개 /dev 디렉터리 아래에 둡니다.
쉽고 빠른 이해
장치 파일은 장치로 이어지는 파일 이름입니다. 셸에서 명령 > /dev/null 로 출력을 버려 봤다면 이미 장치 파일에 쓴 것입니다.
장치 파일이 없으면 프로그램은 파일을 다루는 방법과 장치를 다루는 방법을 둘 다 익혀야 합니다. 장치 파일이 있으면 파일을 다루던 방법 하나로 장치까지 다룹니다. 파일 이름을 받는 프로그램에 장치 이름을 넘겨도 됩니다. 누가 그 장치를 쓸 수 있는지도 파일 권한으로 정합니다.
- 장치 파일에는 내용 대신 「어느 드라이버의 몇 번째 장치인가」를 가리키는 번호가 적혀 있습니다
- 프로그램이 그 파일을 열면 커널이 번호를 보고 드라이버를 고릅니다
- 그 뒤의 읽기와 쓰기는 그 드라이버가 받아 처리합니다
대가도 있습니다. 읽기와 쓰기로 나타낼 수 없는 조작은 장치마다 뜻이 다른 명령으로 보내야 합니다. 네트워크 카드처럼 장치 파일이 아예 없는 장치도 있습니다.
상세
회사에는 부서마다 메일 주소가 있습니다. 경리부에 보내든 인사부에 보내든 쓰는 법은 똑같이 메일 쓰기입니다. 받은 메일을 어떻게 처리할지는 그 부서가 압니다.
장치 파일은 장치를 가리키는 파일입니다. 프로그램은 이 파일을 보통 파일처럼 읽고 씁니다. 다른 점은 하나입니다. 읽기와 쓰기 요청이 디스크의 데이터 대신 연결된 장치를 움직입니다.
요청을 받는 쪽은 커널입니다. 커널은 운영체제 가운데 하드웨어를 직접 다루는 핵심 프로그램입니다. 커널은 장치 파일로 온 요청을 그 장치를 맡은 디바이스 드라이버에게 넘깁니다. 이 글에서는 줄여서 드라이버라고 부릅니다. 앞의 비유로 치면 장치 파일은 부서의 메일 주소입니다. 드라이버는 그 메일을 받아 처리하는 부서입니다.
가장 흔히 만나는 장치 파일은 /dev/null 입니다. 여기에 쓴 내용은 버려집니다.
셸에서 명령 > /dev/null 이라고 치면 명령의 출력이 이 파일로 갑니다. > 는 명령의 출력을 화면 대신 파일로 보내는 셸 기능인 리다이렉션입니다. 받는 파일이 /dev/null 이라 출력은 사라집니다.
/dev/null 뒤에는 하드웨어가 없습니다. 장치 파일이 가리키는 장치는 주변기기일 수 있습니다. 프로그램에게 주변기기처럼 보이는 무언가일 수도 있습니다.
하드웨어 없이 드라이버가 소프트웨어로 장치 노릇을 하는 것을 의사 장치라고 부릅니다.
장치 파일을 부르는 이름은 여럿입니다. 유닉스가 처음 붙인 이름은 특수 파일(special file)입니다. 장치 특수 파일이나 장치 노드라고도 부릅니다.
장치 파일은 문자 특수 파일과 블록 특수 파일 두 종류로 나뉩니다. 흔히 문자 장치·블록 장치라고 줄여 부릅니다. 둘의 차이는 아래 「갈래」 절이 다룹니다.
장치 파일에 적힌 정보
장치 파일은 내용 바이트를 담지 않습니다. 이 파일이 어느 장치를 가리키는지 알려 주는 정보만 담습니다.
이 정보는 아이노드에 적힙니다. 파일 시스템은 디스크 위에 파일과 디렉터리를 담는 방식입니다. 아이노드는 파일 시스템이 파일마다 하나씩 두는 정보 묶음입니다. 보통 파일의 아이노드에는 소유자와 권한, 크기, 데이터가 놓인 디스크 주소 같은 것이 들어갑니다. 장치 파일의 아이노드에는 데이터 주소 대신 장치를 가리키는 번호가 들어갑니다.
| 정보 | 뜻 |
|---|---|
| 종류 | 장치 파일인가, 그렇다면 문자 장치인가 블록 장치인가 |
| 주 번호 | 어느 드라이버가 이 장치를 맡나 |
| 부 번호 | 그 드라이버가 맡은 장치 가운데 어느 것인가 |
주 번호(major number)는 전통적으로 드라이버를 고르는 번호입니다. 부 번호(minor number)는 드라이버가 자기가 맡은 장치들을 서로 가르는 번호입니다. 컨트롤러 하나에 디스크 드라이브가 여럿 달려 있으면 드라이브마다 부 번호가 다릅니다. 주 번호와 부 번호를 묶어 장치 번호라고 부릅니다.
두 번호는 1974년의 유닉스에도 있었습니다. 그때는 아이노드의 두 바이트에 적었습니다. 앞 바이트를 device type, 뒤 바이트를 subdevice number 라고 불렀습니다.
프로그램은 파일 상태를 묻는 stat 호출로 이 정보를 읽습니다. 돌려받는 구조체의 st_mode 칸에는 파일 종류가 담깁니다.
문자 장치인지는 S_ISCHR, 블록 장치인지는 S_ISBLK 매크로로 가립니다.
장치 번호는 st_rdev 칸에 담깁니다. 이 칸은 XSI(X/Open System Interfaces)라는 확장 규격을 따르는 시스템에만 요구됩니다.
보통 파일의 크기를 담는 st_size 칸은 장치 파일에서 뜻이 정해져 있지 않습니다.
요청이 드라이버에 닿는 길
이 소절은 프로그램이 문자 장치의 장치 파일을 열고 한 번 읽기까지를 따라갑니다. 블록 장치는 커널과 드라이버 사이의 길이 이와 다릅니다. 그 차이는 「갈래」 절이 다룹니다.
프로그램은 시스템 콜로 커널에 일을 맡깁니다. 시스템 콜은 프로그램이 커널의 기능을 부르는 입구입니다. 파일을 열 때는 open, 읽을 때는 read 를 부릅니다.
열기가 끝나면 커널은 파일 디스크립터를 돌려줍니다. 파일 디스크립터는 프로세스가 연 파일을 가리키는 작은 정수입니다. 이후의 읽기와 쓰기에서 프로그램은 어느 파일을 다루는지 이 정수로 알립니다.
커널은 열려는 파일의 아이노드를 봅니다. 장치 파일이라는 표시가 있으면 주 번호로 그 장치의 입출력을 맡을 드라이버를 고릅니다. 문자 장치의 드라이버는 대개 열기·닫기·읽기·쓰기를 처리하는 함수를 갖춥니다.
sequenceDiagram
participant 프로그램
participant 커널
participant 드라이버
participant 장치
프로그램->>커널: open · 장치 파일 경로
Note over 커널: 아이노드에서 종류와 주 번호를 읽는다
커널->>드라이버: 주 번호로 고른 드라이버의 열기 함수
커널-->>프로그램: 파일 디스크립터
프로그램->>커널: read · 파일 디스크립터
커널->>드라이버: 드라이버의 읽기 함수
드라이버->>장치: 장치가 알아듣는 절차
장치-->>드라이버: 데이터
드라이버-->>커널: 데이터
커널-->>프로그램: 데이터
그림의 read 는 open 이 돌려준 파일 디스크립터를 들고 옵니다. 커널은 그 정수에 묶인 드라이버의 읽기 함수를 부릅니다. 그 드라이버가 장치를 움직입니다.
프로그램 쪽에서 보면 보통 파일을 읽을 때와 부른 시스템 콜이 같습니다. 커널이 요청을 넘기는 곳만 달라집니다.
파일 인터페이스로 모자란 조작
읽기와 쓰기로 나타낼 수 없는 조작이 있습니다. 장치의 동작 방식을 바꾸는 설정이 그렇습니다. 터미널은 동작 특성 여러 가지를 이런 조작으로 바꿀 수 있습니다.
이런 조작은 ioctl 이라는 시스템 콜로 보냅니다. ioctl 은 입출력 제어(input/output control)의 줄임입니다.
ioctl 은 장치 파일을 연 파일 디스크립터와 요청 번호를 받습니다. 요청 번호의 뜻은 장치에 따라 다릅니다.
ioctl 을 부르는 코드는 상대 장치가 무엇인지 알아야 요청 번호를 고를 수 있습니다. 파일 인터페이스가 숨기던 장치 사이의 차이가 이 호출에서 다시 드러납니다.
장치 파일을 만드는 권한
장치 파일은 mknod 호출로 만듭니다. 종류와 장치 번호를 주면 그 번호를 가리키는 장치 파일이 생깁니다.
장치 파일을 만들려면 특권이 필요합니다. 특권은 root 같은 관리자에게만 있는 권한입니다. 특권 없는 프로세스가 장치 파일을 만들려 하면 권한 오류로 실패합니다.
장치를 쓸 수 있는지는 장치 파일에 붙은 권한 비트가 정합니다. 아무나 장치 파일을 만들 수 있다면, 디스크와 같은 번호로 새 장치 파일을 만들고 자기에게 읽기 권한을 주는 길이 열립니다.
마운트는 다른 저장 장치의 파일 시스템을 디렉터리 트리의 한 디렉터리에 붙이는 일입니다. 붙인 파일 시스템 안에는 다른 기계에서 만든 장치 파일이 들어 있을 수 있습니다. 그 장치 파일은 이 기계의 특권 검사를 거치지 않고 만들어졌습니다.
마운트 옵션 nodev 를 주면 그 파일 시스템 안의 장치 파일을 장치로 해석하지 않습니다.
리눅스의 mount 명령은 일반 사용자에게 마운트를 허락하는 옵션(user·owner·group)을 주면, 따로 끄지 않는 한 nodev 를 함께 켭니다.
mknod 는 FIFO(First-In First-Out)도 만듭니다. FIFO 는 먼저 쓴 데이터가 먼저 읽히는 파일입니다. 장치를 가리키지 않습니다. FIFO 는 특권 없이도 만들 수 있습니다.
장치 파일을 만들 때 mknod 가 어떻게 동작하는지는 운영체제마다 다릅니다. 여러 운영체제에서 똑같이 동작하는 쓰임은 FIFO 를 만드는 것 하나입니다.
배경
장치를 파일과 다른 방법으로 다루면 프로그램은 두 방법을 다 익혀야 합니다. 파일 이름을 인자로 받는 프로그램은 장치를 상대하지 못합니다. 장치를 누가 쓸 수 있는지도 파일과 다른 규칙으로 정해야 합니다.
1974년의 유닉스는 파일을 보통 디스크 파일·디렉터리·특수 파일 세 종류로 나눴습니다. 지원하는 입출력 장치마다 특수 파일을 하나 이상 붙였습니다.
특수 파일은 보통 디스크 파일과 똑같이 읽고 씁니다. 다만 읽기·쓰기 요청이 연결된 장치를 움직입니다.
종이테이프에 구멍을 뚫으려면 /dev/ppt 라는 파일에 쓰면 됐습니다. 통신 회선·디스크·테이프 드라이브·물리 메모리에도 각각 특수 파일이 있었습니다.
이렇게 해서 얻은 것은 셋입니다.
| 얻은 것 | 무엇이 쉬워졌나 |
|---|---|
| 파일 입출력과 장치 입출력이 최대한 비슷하다 | 프로그램이 한 가지 방법으로 둘을 다룬다 |
| 파일 이름과 장치 이름의 문법과 뜻이 같다 | 파일 이름을 받는 프로그램에 장치 이름을 넘길 수 있다 |
| 특수 파일도 보통 파일과 같은 보호 방식을 받는다 | 쓰고 있는 디스크나 메모리를 아무나 건드리지 못하게 권한으로 막는다 |
예시
리눅스의 장치 번호표
리눅스 커널 문서에는 장치 번호 배정표가 있습니다. 어느 주 번호·부 번호가 어느 장치 파일인지를 적은 공식 목록입니다. 아래는 그 일부입니다.
1 char Memory devices
3 = /dev/null Null device
5 = /dev/zero Null byte source
8 = /dev/random Nondeterministic random number gen.
9 = /dev/urandom Faster, less secure random number gen.
1 block RAM disk
0 = /dev/ram0 First RAM disk
8 block SCSI disk devices (0-15)
0 = /dev/sda First SCSI disk whole disk
16 = /dev/sdb Second SCSI disk whole disk
주 번호 1 의 문자 장치는 메모리 장치 묶음입니다. /dev/null 은 부 번호 3, /dev/zero 는 부 번호 5 입니다.
같은 주 번호 1 이 블록 장치 쪽에서는 램 디스크입니다. 주 번호는 종류와 함께 읽어야 장치가 정해집니다.
주 번호 8 의 블록 장치는 SCSI(Small Computer System Interface) 디스크입니다. 첫째 디스크 /dev/sda 가 부 번호 0, 둘째 /dev/sdb 가 16 입니다.
부 번호 0 과 16 은 각각 디스크 전체를 가리킵니다. 디스크마다 부 번호가 16 씩 건너뜁니다. 이 묶음은 디스크 하나에 파티션을 15 개까지 둡니다. 파티션은 디스크 하나를 나눈 구역입니다.
지금의 리눅스는 장치 번호를 그때그때 배정하는 방식을 폭넓게 지원합니다. 직렬 장치와 부팅 장치 쪽에는 고정 번호를 쓰는 예외가 아직 남아 있습니다.
ls -l 이 보이는 번호
ls -l 로 장치 파일을 보면 크기가 나올 칸에 주 번호와 부 번호가 쉼표로 나뉘어 나옵니다. 맨 앞 글자는 종류입니다. c 는 문자 장치입니다. b 는 블록 장치입니다.
아래는 흔한 리눅스 시스템의 출력입니다.
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1
crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw--w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
null 과 zero 는 주 번호가 1 로 같습니다. 같은 드라이버가 맡는다는 뜻입니다. 가상 콘솔 tty1 과 직렬 단말 ttyS1 은 주 번호 4 의 드라이버가 맡습니다. vcs1 과 vcsa1 은 주 번호 7 의 드라이버가 맡습니다.
부 번호는 커널이 그 드라이버가 맡은 장치 가운데 어느 것인지 가리는 데 씁니다. 커널은 부 번호에 대해 그 이상은 거의 모릅니다.
주 번호 하나에 드라이버 하나라는 짝은 전통입니다. 지금의 리눅스 커널은 여러 드라이버가 주 번호 하나를 나눠 쓰게도 합니다. 그래도 흔히 보는 장치는 대부분 이 짝을 따릅니다.
권한 칸도 보입니다. null 과 zero 는 crw-rw-rw- 라 누구나 읽고 씁니다. tty1 은 crw------- 라 소유자인 root 만 읽고 씁니다.
리눅스와 FreeBSD 의 /dev 관리
/dev 아래의 장치 파일을 누가 만들고 관리하는지는 운영체제마다 다릅니다.
리눅스 커널은 devtmpfs 기능을 켜 두면 부팅 초기에 devtmpfs 라는 파일 시스템을 메모리 위에 만듭니다. /dev 에 붙일 파일 시스템입니다. 커널은 장치 번호를 받아 등록된 모든 장치에 대해 이 안에 장치 파일을 둡니다. 이름과 권한은 기본값으로 붙입니다.
그 위에서는 대개 udev 라는 프로그램이 돕니다. udev 는 장치가 붙거나 떨어지거나 상태가 바뀔 때마다 커널이 보내는 장치 이벤트를 받습니다. 그리고 규칙에 맞춰 장치 파일의 권한을 매깁니다. 알아보기 쉬운 심볼릭 링크를 더할 수도 있습니다.
커널은 대개 장치를 발견한 순서대로 이름을 붙입니다. 그래서 어느 장치가 어느 이름을 받을지 미리 알기 어렵습니다. udev 는 장치 파일의 이름을 바꾸지 못합니다. 대신 장치의 속성으로 지은 링크를 더합니다. 그 링크는 같은 장치를 늘 같은 이름으로 가리킵니다.
FreeBSD 에서는 devfs 라는 파일 시스템이 이 일을 맡습니다. devfs 는 보통 /dev 에 붙습니다. 커널이 아는 장치들이 devfs 를 거쳐 /dev 아래의 파일 이름으로 보입니다.
갈래
장치 파일의 두 종류는 장치를 프로그램에 어떤 모습으로 보이느냐로 갈립니다. 문자 장치는 바이트가 흘러가는 통로로 보입니다. 블록 장치는 대개 장치의 하드웨어 성질을 감춘 저장 공간으로 보입니다. 블록 장치에 커널이 캐시를 붙이는지도 시스템마다 갈립니다.
문자 장치
문자 장치는 데이터를 바이트의 흐름으로 주고받습니다. 터미널과 직렬 포트가 대표입니다. /dev/null 처럼 특별한 성질을 가진 파일도 문자 장치입니다.
보통 파일은 앞뒤로 위치를 옮겨 가며 읽을 수 있습니다. 대부분의 문자 장치는 들어온 차례대로만 읽는 통로입니다. 영상 프레임을 잡아 오는 프레임 그래버처럼 앞뒤로 옮겨 다닐 수 있는 문자 장치도 있습니다.
블록 장치
블록 장치는 대개 장치의 하드웨어 성질을 감추고 장치에 접근하게 합니다. 디스크가 대표입니다. 대부분의 유닉스에서 블록 장치는 블록 하나 이상을 한 번에 옮기는 입출력만 받습니다. 블록은 대개 512 바이트이거나 그보다 큰 2 의 거듭제곱 크기입니다.
리눅스에서 블록 장치는 파일 시스템을 올릴 수 있는 장치입니다. 리눅스는 블록 장치도 문자 장치처럼 아무 바이트 수로 읽고 쓰게 합니다. 그래서 두 종류는 커널 안에서 데이터를 다루는 방식만 다릅니다. 그 차이 때문에 블록 장치의 드라이버가 커널과 맞닿는 방식은 문자 장치의 드라이버와 전혀 다릅니다. 사용자에게는 그 차이가 드러나지 않습니다.
캐시를 붙인 블록 장치
캐시는 디스크에 오가는 데이터를 커널이 메모리에 잠시 담아 두는 곳입니다. FreeBSD 가 아닌 다른 유닉스 시스템 가운데에는 커널이 캐시를 붙인 디스크 장치를 두는 것이 있습니다. 그 장치를 블록 장치라고 부릅니다.
이 캐시는 쓰기의 순서를 바꿉니다. 그러면 응용 프로그램은 어느 순간 디스크에 무엇이 적혔는지 알 수 없습니다. 파일 시스템이나 데이터베이스는 장애 뒤에 디스크 위 구조를 예측대로 복구하지 못합니다. 쓰기는 늦게 일어날 수도 있습니다. 그러면 커널은 어느 쓰기가 오류를 냈는지 응용 프로그램에 알려 줄 수 없습니다.
sequenceDiagram
participant 응용 as 응용 프로그램
participant 캐시
participant 디스크
응용->>캐시: 쓰기 A
응용->>캐시: 쓰기 B
Note over 캐시: 디스크에 늦게 내려보낼 수 있다
캐시->>디스크: B 를 먼저 내려보낸다
Note over 응용,디스크: 이 순간 디스크에는 B 만 있다
캐시->>디스크: A 를 내려보낸다
디스크-->>캐시: 쓰기 오류
Note over 응용,캐시: 어느 쓰기가 오류를 냈는지 응용 프로그램에 알릴 길이 없다
그래서 디스크에 직접 접근하는 응용 프로그램은 거의 모두 캐시 없는 문자 장치를 쓰도록 공들여 지정합니다. 이 문자 장치를 raw 장치라고도 부릅니다.
FreeBSD 는 이 블록 장치를 없앴습니다. 디스크나 파티션 하나를 뜻이 다른 두 장치로 겹쳐 두는 구현이 커널 코드를 크게 복잡하게 했기 때문입니다. FreeBSD 는 디스크 입출력 기반을 새로 다듬으면서 캐시를 붙인 디스크 장치를 걷어 냈습니다. 그래서 FreeBSD 에는 블록 장치라는 종류가 없습니다. FreeBSD 의 드라이버는 대략 문자 장치와 네트워크 장치 두 갈래로 나뉩니다.
경계
네트워크 카드도 장치 파일로 다루나. 아닙니다.
네트워크 카드는 장치입니다. 하지만 /dev 아래에 장치 파일이 없습니다.
커널은 네트워크 카드를 네트워크 인터페이스로 다룹니다. 네트워크 인터페이스는 다른 컴퓨터와 데이터를 주고받는 장치입니다. 대개는 네트워크 카드 같은 하드웨어입니다.
네트워크 인터페이스는 eth0 같은 고유한 이름을 받습니다. 그런데 그 이름에 해당하는 파일 시스템 항목이 없습니다.
네트워크 인터페이스는 바이트 흐름으로 다루는 장치가 아닙니다. 그래서 파일 시스템의 항목으로 옮기기 쉽지 않습니다. 커널도 네트워크 드라이버에게는 읽기·쓰기 함수 대신 패킷을 보내고 받는 함수를 부릅니다.
프로그램은 대개 open 대신 socket 시스템 콜로 네트워크 인터페이스를 쓰기 시작합니다. 어느 네트워크 드라이버를 거칠지는 커널이 안에서 정합니다.
소켓은 프로세스끼리 통신하는 끝점입니다. 소켓도 파일 종류의 하나입니다. 다만 문자 특수 파일이나 블록 특수 파일과는 다른 종류입니다. 파일 종류를 가리는 매크로도 S_ISSOCK 으로 따로 있습니다.
관련 항목
장치 파일 요청이 거치는 커널 부품
커널 · 디바이스 드라이버 · 커널 모듈 · 가상 파일 시스템 · 파일 디스크립터
장치 파일을 다루는 시스템 콜
시스템 콜 · open · read · write · close · ioctl · mmap · stat
장치 파일에 적히는 정보
주 번호 · 부 번호 · 아이노드 · 권한
장치 파일의 하위 종류
문자 장치 · 블록 장치 · 의사 장치
장치 파일로 열리는 장치
터미널 · 의사 터미널 · 직렬 포트 · 가상 콘솔 · 디스크 · 파티션 · SCSI · 램 디스크 · 프레임 그래버
장치 파일을 만들고 관리하는 수단
mknod · udev · devfs · devtmpfs · 마운트 · nodev · ls
장치 파일을 정의하는 표준
POSIX · XSI · Single UNIX Specification
장치 파일에 입출력을 잇는 셸 기능
셸 · 리다이렉션 · 표준 입력 · 표준 출력 · 표준 오류
장치 파일과 나란한 다른 파일 종류
FIFO · 이름 있는 파이프 · 파이프 · 심볼릭 링크 · 디렉터리
장치 파일이 속하는 상위 분류
파일 · 파일 시스템 · 유닉스 · 모든 것은 파일이다
장치 파일 대신 장치를 드러내는 수단
소켓 · 네트워크 인터페이스 · sysfs · procfs
장치 파일을 쓰는 운영체제
다른 이름: device file · device special file · 장치 특수 파일 · 디바이스 파일 · device node · 장치 노드 · special file · 특수 파일