-
[Linux Kernel] Device Driver 적재, Device File 생성프로그래밍/리눅스 시스템 2024. 4. 29. 00:35
커널모듈 형태(.ko)의 Device Driver 제작
module_init(함수); // 커널모듈 적재시 호출되는 함수 지정 매크로
module_init (함수)
- 기능 : 현재 커널 모듈을 등록(insmod)할 때 호출되는 함수를 지정하는 매크로
- 입력값 : 커널 모듈을 등록할 때 호출할 함수
- 필요 라이브러리 : <linux/module.h>
module_exit(함수);// 커널모듈 제거시 호출되는 함수 지정 매크로
module_exit (함수)
- 기능 : 현재 커널 모듈을 제거(rmmod)할 때 호출되는 함수를 지정하는 매크로
- 입력값 : 커널 모듈을 제거할 때 호출할 함수
- 필요 라이브러리 : <linux/module.h>
커널 모듈 빌드를 위한 Makefile
#디바이스 드라이버를 KERNEL 빌드한 헤더의 경로로 가서 build 한 뒤에, build 결과물만 현재 디렉토리로 갖고 오도록 구성된 Makefile KERNEL_HEADERS=/lib/modules/$(shell uname -r)/build obj-m += hi.o PWD := $(CURDIR) driver: make -C $(KERNEL_HEADERS) M=$(PWD) modules clean: make -C $(KERNEL_HEADERS) M=$(PWD) clean
- KERNEL_HEADERS : 커널헤더 경로 지정
- 커널모듈을 커널에서 build해야하기 때문에 경로를 지정해준다.
- 이때, 현재 커널 버전을 알기 위해 $(shell uname -r) 변수를 사용한다.
- obj-m += hi.o
- 커널 모듈로 빌드할 소스 파일을 지정한다.
- make -C [빌드 디렉토리경로] M=[빌드된 파일을 저장할 경로] module
- -C로 지정된 디렉토리로 이동하여 커널 module을 빌드하고, 빌드된 파일을 M=위치에 저장한다.
module 정보 보기 명령어
$ modinfo [모듈이름.ko]
적재된 커널 module 확인 명령어 (grep을 이용하여 원하는 module만 확인 가능)
$ lsmod | grep [모듈이름(확장자X)]
Device Driver 전체 동작
전체 구조
- App : User Space 에 있는 app 은 Kernel Space의 직접 접근이 불가하므로 Device Driver 에 연결된 Device File에 syscall 을 보내 장치를 제어한다.
- Device File : 장치를 제어하기 위해 /dev/ 에 등록되는 파일로, Device Node라고도 한다. Device Driver와 연결되어 있다.
- Device Driver : 커널 모듈 형식으로 제작되었으며, kernel의 도움을 받아 장치에 접근한다.
Device File 생성
방법1 : 쉘창에서 mknod 유틸리티로 Device File을 생성
$ sudo mknod [/dev/파일명] [파일종류] [majorN] [minorN]
🚨하지만, 해당 방법으로 생성된 device file은 reboot시 삭제된다.
방법2 : device_create() 함수를 이용하여 device driver가 커널에 insmod될 때 생성해주기
⭐ device_create()함수를 이용해 device file을 생성해주기 위해서는,
device deriver의 class와 device deriver의 식별자(dev_t)가 필요하다!!! ⭐
[코드]
더보기static int __init deviceFile_init(void) { //register_chrdev() 의 첫번째 매개변수는 major num, 0으로 지정하면, 커널이 동적으로 할당한다. //return 값이 major num 이다. NOD_MAJOR = register_chrdev(0, NOD_NAME, &fops); if( NOD_MAJOR < 0 ){ pr_alert("Register File\n"); return NOD_MAJOR; } pr_info("Insmod Module\n"); //MKDEV() 를 이용해 장치 구분을 위한 번호 생성 및 할당 dev = MKDEV(NOD_MAJOR, 0); //rpi-6.6 버전 device file를 관리해주는 class 생성 cls = class_create(NOD_NAME); //rpi-6.1y 버전 device file를 관리해주는 class 생성 //cls = class_create(THIS_OWNER, NOD_NAME); //device file 생성 device_create(cls, NULL, dev, NULL, NOD_NAME); pr_info("Major number %d\n", NOD_MAJOR); pr_info("Device file : /dev/%s\n", NOD_NAME); return 0; } static void __exit deviceFile_exit(void) { //device file 해제 device_destroy(cls, dev); class_destroy(cls); unregister_chrdev(NOD_MAJOR, NOD_NAME); pr_info("Unload Module\n"); } module_init(deviceFile_init); module_exit(deviceFile_exit);
file_operations 구조체 : syscall에 대응하는 device driver의 동작함수 정의
struct file_operations fops
- 멤버변수
- system call : 대응하는 device driver의 함수 포인터 형식으로 작성
- .owner 멤버는 사용자 애플리케이션이 직접 호출하는 것이 아니라, 커널 내부적으로 모듈을 식별하는 데 사용되는 커널함수로, 보통 .owner = THIS_MODULE로 설정해준다.
ex)
static struct file_operations fops = { .owner = THIS_MODULE, .open = deviceFile_open, .release = deviceFile_release, };
register_chrdev() : 캐릭터 device driver 등록하기
int register_chrdev(unsinged int major, const char *name, const struct file_operations *fops)
- 기능 : 커널이 관리하는 chrdev의 배열에 캐릭터 디바이스 드라이버를 등록하는 API
- 입력값
- unsigned int major : 장치 파일의 major 번호
=> 0으로 할당하면, 커널이 동적으로 major num을 할당해줌. - const char *name : char device file 장치 파일 이름
- const struct file_operations *fops : 요청된 작업( system call )에 대응할 수 있는 함수들의 포인터를 정의한 구조체 포인터
- unsigned int major : 장치 파일의 major 번호
- 반환값
- 성공시 : 등록된 문자 장치의 major number를 반환
- 실패시 : 음수값 반환
device_create() : Device File 생성
struct device *device_create
(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);- 기능 : device file 생성 및 sysfs 파일 시스템에 해당 device file 추가
- 입력값
- *class : device가 속하는 클래스를 나타내는 class 포인터 (class_create() 함수의 return값)
- *parent : 생성할 device의 부모 device 구조체 포인터. (일반적으로 NULL로 설정)
- devt : 생성할 device의 번호 (MKDEV() 의 return 값)
- drvdata : 디바이스 드라이버에서 사용할 데이터에 대한 포인터입니다. (일반적으로 NULL로 설정)
- fmt : 생성할 device file의 이름
▶MKDEV() : device driver 장치 구분을 위한 번호 생성 및 할당
dev_t MKDEV(int major_num, int minor_num)
- 기능 : major와 minor number를 받아서 이를 조합하여 장치 구분을 위한 dev_t 타입의 디바이스 번호를 생성
=> 디바이스 번호를 통해 디바이스 장치를 생성/삭제한다. (device driver의 식별자로 사용된다.) - 입력값
- major: 디바이스의 major number
- minor: 디바이스의 minor number
- 반환값 : major와 minor number를 조합한 dev_t 형식의 디바이스 번호
▶ class_create() : device class를 생성
struct class* class_create(const char *name)
- 기능 : 디바이스 드라이버가 사용하는 클래스를 생성
=> 여러 디바이스를 그룹화하고 관리하는 데 사용된다. - 입력값 : 생성할 클래스의 이름을 나타내는 문자열 포인터 (device file의 이름)
- 반환값
- 성공시 : 생성된 클래스를 나타내는 struct class의 포인터
- 실패시 : ERR_PTR() 매크로를 사용하여 오류를 나타내는 포인터를 반환
device_destroy() : device file 제거
▶ device_destroy() : device file제거
void device_destroy(struct class *class, dev_t devt)
- 기능 : 특정 클래스에서 생성된 특정 디바이스를 제거하고 관련 리소스를 정리 및 반환
- 입력값
- class : 제거할 디바이스를 생성한 클래스에 대한 포인터
- devt : 제거할 디바이스의 장치 식별번호
▶ class_destroy() : device class 삭제
void class_destroy(struct class *class)
- 기능 : 디바이스 드라이버가 사용하는 클래스를 제거
- 입력값 : 제거 할 클래스의 포인터
예제코드로 복습하기
//mknod 유틸리티 사용하지 않고, device_create(), class_create() 를 사용해 device File 을 생성한 디바이스 드라이버 샘플 코드 //device.h 를 이용해, 장치파일을 생성, 관리한다. //read용 api 를 추가 생성하여 fops에 등록하여, cat 명령어를 이용해 메시지를 출력한다. #include <linux/module.h> #include <linux/printk.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/device.h> //device file 관리용 header #define NOD_NAME "deviceFile" MODULE_LICENSE("GPL"); static int NOD_MAJOR; //device file 의 major num 를 담을 변수 static struct class *cls; //device file 생성을 위한 class static dev_t dev; //device 장치 구분을 위한 번호를 담아줄 변수 static int deviceFile_open(struct inode *inode, struct file *filp){ pr_info("Open Device\n"); return 0; } static int deviceFile_release(struct inode *inode, struct file *filp){ pr_info("Close Device\n"); return 0; } static struct file_operations fops = { .owner = THIS_MODULE, .open = deviceFile_open, .release = deviceFile_release, }; static int __init deviceFile_init(void) { //register_chrdev() 의 첫번째 매개변수는 major num, 0으로 지정하면, 커널이 동적으로 할당한다. //return 값이 major num 이다. NOD_MAJOR = register_chrdev(0, NOD_NAME, &fops); if( NOD_MAJOR < 0 ){ pr_alert("Register File\n"); return NOD_MAJOR; } pr_info("Insmod Module\n"); //MKDEV() 를 이용해 장치 구분을 위한 번호 생성 및 할당 dev = MKDEV(NOD_MAJOR, 0); //rpi-6.6 버전 device file를 관리해주는 class 생성 cls = class_create(NOD_NAME); //rpi-6.1y 버전 device file를 관리해주는 class 생성 //cls = class_create(THIS_OWNER, NOD_NAME); //device file 생성 device_create(cls, NULL, dev, NULL, NOD_NAME); pr_info("Major number %d\n", NOD_MAJOR); pr_info("Device file : /dev/%s\n", NOD_NAME); return 0; } static void __exit deviceFile_exit(void) { //device file 해제 device_destroy(cls, dev); class_destroy(cls); unregister_chrdev(NOD_MAJOR, NOD_NAME); pr_info("Unload Module\n"); } module_init(deviceFile_init); module_exit(deviceFile_exit);
'프로그래밍 > 리눅스 시스템' 카테고리의 다른 글
[Linux Kernel] Device Driver : Timer (0) 2024.04.29 [Linux Kernel] Device Driver : proc file system (0) 2024.04.29 [Linux Kernel] Device File : Read, write (with ioctl) (0) 2024.04.29 [Linux] 멀티 Process, Signal, WDT (0) 2024.04.15 [Linux] 파일 입출력 함수 (0) 2024.04.14