임베디드 시스템 소프트웨어 과목의 강의영상과 강의자료를 바탕으로 작성한 학습용 게시글입니다.
Linux Device Drivers
Device Driver
Network Device Driver
- 네트워크 프로토콜 스택과 상호 작용
- 소켓 인터페이스
- ex) 이더넷 드라이버
Block Device Driver
- 랜덤 엑세스 장치
- open, close, read, write 시스템 콜
- 파일에 불특정한 위치로 포인터를 옮김
- ex) 스토리지 드라이버
Character Device Driver
- sequential access device (순차적 엑세스 장치) : 바이트 스트림으로 엑세스
- block device driver와 동일한 인터페이스(open, close, read, write)
- 랜덤 access 불가
- 어떤 데이터를 바이트 스트림으로 아웃풋 디바이스에 주거나 인풋 디바이스에서 바이트 스트림을 받기 가능
- ex) 센서 드라이버
Applications' Point of View
- 파일은 스토리지에 저장되어 있는 정보 집합체가 아닌 디바이스들에 대한 abstraction
- 파일로 응용에게 전달
- 응용은 file Operations 통해 접근 가능
- 어떤 io디바이스 자체를 파일로 abstration했다고 이해해야 함
Driver Programming
registering and Initializing Drivers
Network devices
- net_device, net_device_ops 구조체 초기화
- register_netdev()로 등록
Block devices
- getdisk, block_device_operations 구조체 초기화
- add_disk()로 등록
Character device
- cdev, file_operations 구조체 초기화
- cdev_add()로 등록
File Operations
- cdev 구조체와 file_operations 구조체를 초기화해주는 것이 중요하다.
- Character Device Driver는 커널 모듈로서 만들어진다.
- 응용(App)이 file operation을 system call하고, virtual file system을 거쳐서 device driver에 실제 구현한 file operation들이 호출되면서 HW Device에 접근하게 된다.
- cdev 구조체는 character device의 전체 정보를 포함하고 있는 가장 상위에 존재하는 구조체
- ops라는 포인터는 file_operations를 가리키고 있다.
- file_operations는 open, read, write 등의 function pointer를 저장하고 있는 구조체
cdev 구조체
: 하나의 캐릭터 디바이스를 표현하기 위해 최상위에 존재하는 구조체
- include/linux/cdev.h 에 정의되어 있다.
- ops
- 해당 character device 안에 있는 함수들의 실제 구현을 포함
- my_cdev->ops =&my_ops; 또는 cdev_init(my_cdev, &my_ops); 로 초기화
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
|
file_operations 구조체
- include/linux/fs.h에 정의
- read, write 등의 함수 포인터 집합
- 구현할 필요가 없는 것은 null 포인터로 두면 된다.
- 모든 함수를 정의하고 초기화할 필요는 없다.
struct file_operations {
struct module *owner;
…
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
…
int (*unlocked_ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
…
int (*open) (struct inode *, struct file *);
…
};
|
open()
: 해당 파일에 대한 descripter 넘겨준다.
- null값이라면, 항상 성공했다는 반환값을 주고 있다.
release()
: close했을 때 불려지는 함수를 연결해준다.
read()
: 디바이스로부터 데이터를 읽어온다.
- 성공 : 몇바이트 읽어왔는지 양수값을 리턴한다.
- 실패 : 오류값 -EINVAL (음수)을 리턴한다.
write()
: 디바이스에 데이터를 보내기 위한 함수이다.
- 성공 : 몇바이트 읽어왔는지 양수값을 리턴한다.
- 실패 : 오류값 -EINVAL (음수)을 리턴한다.
ioctl()
: io control의 약자
- 어떤 구현을 할 것인지 정해진 것이 없다.
- 임의의 디바이스에 대해 특화된 기능이 필요할 수도 있음을 미리 알고 구현하는 것은 어렵다.
- 꽤 많은 파일 오퍼레이션이 정의되어있지만 미리 정의되어있지 않은 경우도 있다.
- 기능이 하나가 아니라 여러개가 있을 수 있다.
- flad/cmd (unsigned int)
- 인자값으로는 falg/cmd를 전달받도록 되어 있다.
( 번호에 맞는 각각의 기능을 수행해야 한다.)
- 인자값으로는 falg/cmd를 전달받도록 되어 있다.
- parameter (unsigned long)
- 파라미터가 필요없을 수도 있고, 변수일수도 있고, 함수나 구조체일 수도 있다.
- 인자값이 하나라면 직접 전달한다.
- 여러개 또는 구조체 전달시에는 메모리 공간의 주소번지를 전달한다.
- cmd 시 유의할 점
- 커맨드 정의 시 기존에 사용하던 것과 같은 것 쓰면 문제가 발생할 수 있다.
- 많은 경우 임의의 번호를 사용헤도 되지만, 기본적인 룰이 존재한다.
- Documentation/ioctl/ioctl-number.txt 에서 사용하고 있지 않은 magic number를 사용해야 한다.
- 보통 switch 형태로 구현된다.
- poll()
- 서버 프로그래밍에서, 여러 파일 디스크립터들을 select, poll, epoll에게 넘겨주었을 때, read / write가 준비된 것들을 리턴해 준다.
- 비동기 통신 잘되도록 하기 위한 것이다.
registration
- 함수 구현
- 함수 포인터와 연결
- cdev의 ops와 연결
cdev_add()
: 커널에게 이 커널 모듈이 하나의 캐릭터 디바이스 드라이버라고 이야기해주는 함수, 캐릭터 디바이스 드라이버를 운영체제에 등록해주는 과정
- 커널 함수, export할 수 있기 때문에 커널 모듈에서 호출 가능
- struct cdev *dev
- 캐릭터 디바이스의 모든 정보를 포함하고 있는 최상위 구조체
- ops가 가리키는 것이 file operations 구조체
- dev_t num
- 해당 캐릭터 디바이스의 고유넘버로서 id같은 것
- major number와 minor number로 이루어져 있음
- unsigned int count
- 몇개까지의 디바이스 드라이버가 있냐를 명시
Major and Minor Numbers
Major Number
: 특정 디바이스를 제어하는 드라이브의 고유 번호
- 제품마다 정해져있지는 않고 사용하지 않는 것을 그때그때 할당한다.
- 사용하지 않는 번호를 동적 할당하도록 한다.
- alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
- 사용하지 않는 major number를 얻기 위한 커널함수
- dev : 결과값
- baseminor : 요청된 minor number의 시작주소, 일반적으로 0
- count : 요청된 minor number의 수
- name : 연결된 디바이스 또는 드라이버의 이름 , 고유 이름
Minor Number
: 여러 드라이버에 의해 제어되고 있는 드라이버가 여러개 있을 때 그들 사이에서 구분하기 위한 고유번호
- 동종의 디바이스 구분 가능
ls-l/dev
- 담당하고 있는 드라이버의 major, minor nummber 정보들은 inode에 들어가있다. (read 함수)
- file 구조체가 가리키고 있는 f_inode에 접근할 수 있고, 그 inode에 major, minor number 정보들이 들어있다. (write 함수)
- imajor() , MAJOR()
- inode를 인자값으로 받아서 major number를 리턴
- iminor() , MINOR()
- inode를 인자값으로 받아서 minor number를 리턴
Initialization of Device Drivers
- insmod를 하면 init 함수가 호출되면서 관련된 초기화 과정을 거치게 된다.
- 대부분의 초기화 과정은 init 함수에 들어가있다.
- cdev & file_operations 초기화
- major & minor number 얻어옴
- cdev_add 커널 함수 호출하여 cdev 구조체를 등록해주어야 함
Device Files
응용 프로그램과 연결되는 고리가 무엇인가
Devicd Files
: 시스템 내 존재하는 io 장치들을 추상화한 것
- 응용 프로그램에서 io 장치 제어시 input/output 요구할 때 file io system call 호출해서 디바이스 제어 가능
- 디바이스 파일들은 /dev 디렉토리에 존재
- 디바이스 파일은 디바이스 노드 (device node)라고 불리기도 한다.
- mknod
- : 디바이스 파일 생성 명령어
- 디바이스 파일을 만들 때에는 항상 major number 명시해주어야 한다.
- alloc_chrdev_region()
- : major number 할당
- /proc/devices
- : major number 할당 확인 가능
- major number는 그때그때 사용하지 않는 번호를 동적으로 할당한다.
- 디바이스 드라이버를 받을 때마다 major number가 달라질 수 있다.
- 매번 major number를 확인해주어야 한다.
- 자동으로 감지해서 mknod하는 방법이 일반적이다.
#!/bin/sh
MODULE=“mymod”
major=$(awk “\$2==\“$MODULE\” {print \$1}” /proc/devices)
mknod /dev/${MODULE}0 c $major 0
|
- MODULE
- : 모듈 이름
- awk
- : 특정 파일을 컬럼별로 구분하도록 했을 때 원하는 것을 빼오는 명령어
- mknod
- 디바이스 파일 만들기위한 명령어
- c : 캐릭터 디바이스
- 루트 권한 있어야 함
Put Things Together
- 커널 모듈 안 cdev 구조체 초기화
(ops라는 포인터는 file operation 구조체 가리킨다. )
(file_operation은 open, read, write 등의 함수의 실제 구현 부분 포함) - major number 얻기 위해 alloc_chrdev_region 호출, 커널에서는 사용되지 않고 있는 major number를 리턴해준다.
- /proc/devices 안에 major number에 대한 정보 저장
- 커널에서 cdev_add 호출하며, 초기화했던 캐릭터 디바이스 드라이버에 대한 정보를 등록
(major number와 연결)
(운영체제 안에 캐릭터 디바이스 드라이버가 제대로 등록된 것) - /proc/devices에서 이름으로 major number를 얻어와 해당 major number로 mknod
- device file 생성
- device file open
- open system call 은 file descriptor 리턴
- 해당 file desc로 read/write 호출 시 file descriptor 통해 inode 얻고, inode 기반으로 major number 얻고, major number 기반으로 해당 device driver를 찾고, cdev 구조체 접근하고, cdev 안 file ops 구조체 접근
-> 실제로 구현했던 file operation의 바디 부분에 해당하는 함수를 초훌
- 디바이스 파일과 디바이스 드라이버 연결고리는 Major number와 Minor number이다.
Parameter Passing
모듈 변수의 값을 지정할 수 있는 방법
- 모듈을 커널에 삽입하는 명령어 : insmod, modprobe
- modprobe는 dependenct를 갖고 있는 커널 모듈을 삽입해 줌
- ex) insmod mymod count =10
- count
- : mymod 안에 선언되어 있는 전역변수
- 초기값 명시 -> compile 타임에 정해지지x, insmod할 때 정해진다.
- 컴파일 타임에 정해지는 것 보다 더 flexible
- count
module_param()
: 모듈 매개변수 정의
static int count = 0;
module_param(count, int, S_IRUGO);
|
- insmod할 때, 초기값을 명시해 줄 수 있는 것들을 선언해주는 것
- 함수 밖에서 선언한다.
- Arguments
- Name of the variable (변수 이름)
- type (타입)
- Permission (퍼미션)
- S_IRUGO : insmod시 값을 설정할 수 있지만, 이후 커널 모듈 내부에서는 변경불가
* 상수처럼 값을 바꾸지 않고 쓰는 것이 일반적이다. - S_IRUGO|S_IWUSR : 커널 모듈 내에서 변경 가능
- S_IRUGO : insmod시 값을 설정할 수 있지만, 이후 커널 모듈 내부에서는 변경불가
Kernel Dependency
커널 버전에 대한 의존성을 커널 모듈에서 어떻게 처리할 것인가
- 커널 버전이 바뀌면서 자료구조/함수가 바뀌어서 우리의 device driver도 바뀌어야 되는 경우 있다.
(그렇지 않으면 최신 버전에서는 동작하지 않을 수도 있다.) - LINUX_VERSION_CODE
- : 커널 버전을 양의 정수로 표시해둔 것
- KERNEL_VERSION(major, minor, release)
- : 커널 버전 번호를 받아서 정수값으로 표시해주는 함수
'LECTURE > [2023-1] 임베디드시스템소프트웨어' 카테고리의 다른 글
[임베디드시스템소프트웨어] 05. General Purpose I/O (0) | 2023.04.19 |
---|---|
[임베디드시스템소프트웨어] 04. Blocking I/O (0) | 2023.04.12 |
[임베디드시스템소프트웨어] 03. Basic Kernel Functions (0) | 2023.04.11 |
[임베디드시스템소프트웨어] 01. Loadable Kernel Modules (적재 가능 커널 모듈) (0) | 2023.04.09 |