ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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의 classdevice 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 )에 대응할 수 있는 함수들의 포인터를 정의한 구조체 포인터
    • 반환값
      • 성공시 : 등록된 문자 장치의 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);
Designed by Tistory.
-->