임베디드 스터디 - 시스템 콜과 디바이스 드라이버 접근
임베디드 스터디 - 시스템 콜과 디바이스 드라이버 접근
file_operations 구조체
- 리눅스에서 사용자 공간은 어떤 디바이스든 동일하게
open()→read()→write()로 접근함 - 디바이스마다 실제 구현이 다르기 때문에, 커널은 vtable 방식으로 드라이버별 함수를 연결함
- C++의 vtable과 동일한 개념
- 이 함수 포인터 집합을
file_operations구조체라고 함
1
2
3
4
5
6
7
8
struct file_operations {
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*ioctl) (struct file *, unsigned int, unsigned long);
loff_t (*llseek) (struct file *, loff_t, int);
int (*release)(struct inode *, struct file *);
};
- 드라이버 개발자는 이 구조체에 자신의 함수를 채워 넣고 커널에 등록함
- 사용자가
read()를 호출하면 커널이 해당 디바이스의 함수 포인터를 찾아 실행함
주요 멤버 함수
open / read / write / release
open: 디바이스 파일을 열 때 호출read: 디바이스에서 데이터를 읽을 때 호출write: 디바이스로 데이터를 쓸 때 호출release: 디바이스 파일을 닫을 때 호출 (close()에 대응)
ioctl
read/write로 처리하기 애매한 제어 명령을 전달할 때 사용함- 데이터 입출력이 아닌 디바이스 설정·제어가 목적
- 보레이트(baud rate) 변경, LED 밝기 설정, 모터 속도 제어 등
1
2
ioctl(fd, UART_SET_BAUDRATE, 115200);
ioctl(fd, LED_SET_BRIGHTNESS, 80);
- 커맨드 번호(두 번째 인자)로 어떤 제어를 수행할지 구분함
llseek
lseek()시스템 콜이 커널로 들어오면file_operations.llseek이 호출됨- 드라이버가 내부 파일의 오프셋 위치를 갱신하고, 이후
read()는 그 위치부터 데이터를 읽음
1
2
3
4
5
6
7
lseek(fd, 1024, SEEK_SET)
↓
file_operations.llseek 호출
↓
드라이버가 내부 오프셋을 1024로 갱신
↓
다음 read() → 1024번지부터 읽기
ll은long long의 약자 — 대용량 스토리지의 큰 오프셋 값을 처리하기 위해 64비트 타입 사용
register_chrdev() / unregister_chrdev()
file_operations구조체를 커널 드라이버 테이블에 등록/해제하는 함수
1
2
3
4
5
6
7
8
9
10
static int __init my_driver_init(void) {
register_chrdev(4, "tty", &my_fops);
// │ │ │
// Major번호 이름 file_operations 구조체
return 0;
}
static void __exit my_driver_exit(void) {
unregister_chrdev(4, "tty");
}
register_chrdev():module_init()안에서 호출 — 모듈 로드 시 드라이버 테이블에 등록unregister_chrdev():module_exit()안에서 호출 — 모듈 제거 시 드라이버 테이블에서 해제- 해제하지 않으면 모듈 코드는 사라졌는데 테이블에 함수 포인터가 남아 댕글링 포인터 문제 발생, 커널 패닉으로 이어진다.
전체 흐름
1
2
3
4
5
6
7
8
9
10
11
12
13
14
드라이버 개발자
↓
file_operations 구조체에 함수 포인터 등록
↓
module_init() → register_chrdev()로 커널 드라이버 테이블에 등록
↓
사용자 공간: open("/dev/ttyS0") → 시스템 콜
↓
커널: Major number로 드라이버 테이블 조회
→ file_operations.open 호출
↓
사용자 공간: read() → 시스템 콜
↓
커널: file_operations.read 호출 → UART 레지스터 접근 → 데이터 반환
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.