do{학습}while

캐릭터 디바이스 드라이버 생성 본문

임베디드 프로그래밍

캐릭터 디바이스 드라이버 생성

하이오야이 2025. 6. 6. 12:53

간단한 캐릭터 디바이스 드라이버 만들기 (문자열 출력 예제)

이번 글에서는 리눅스 커널에서 캐릭터 디바이스 드라이버를 직접 작성하여, 사용자 공간에서 cat /dev/mychardev 명령어로 커널에서 정의한 문자열을 출력하는 방법을 다룹니다.


1. 디바이스 드라이버 코드 (mychardev.c)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "mychardev"
static int major;

static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) {
    char *msg = "Hello from kernel!\n";
    int msg_len = strlen(msg);

    if (*off >= msg_len)
        return 0;

    if (len > msg_len - *off)
        len = msg_len - *off;

    if (copy_to_user(buf, msg + *off, len))
        return -EFAULT;

    *off += len;
    return len;
}

static int my_open(struct inode *i, struct file *f) {
    printk(KERN_INFO "mychardev: device opened\n");
    return 0;
}

static int my_release(struct inode *i, struct file *f) {
    printk(KERN_INFO "mychardev: device closed\n");
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = my_read,
    .open = my_open,
    .release = my_release,
};

static int __init my_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);
    if (major < 0) {
        printk(KERN_ALERT "Failed to register character device\n");
        return major;
    }
    printk(KERN_INFO "mychardev loaded with major number %d\n", major);
    return 0;
}

static void __exit my_exit(void) {
    unregister_chrdev(major, DEVICE_NAME);
    printk(KERN_INFO "mychardev unloaded\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

2. Makefile 작성

#Makefile 위치 /drivers/Makefile
obj-y += mychardev.o # built-in 드라이버

3. 빌드 및 테스트

$ make linux-rebuild
$ make linux-install

# rebuild된 커널과 함께 qemu 실행

$ dmesg | grep mychardev #mychardev loaded with major number <major 번호>

로그에 major number가 출력되면 성공적으로 등록된 것입니다.


4. 디바이스 노드 생성

$ sudo mknod /dev/mychardev c <major> 0
$ sudo chmod 666 /dev/mychardev

<major>에는 dmesg에 출력된 숫자를 넣습니다.

 

mknod를 명령어를 실행하게 된다면 디바이스 노드를 생성하게 되는데 

/dev 디렉토리 안에 mychardev 이름을 가진 문자형 디바이스 파일(c)을 만들고 메이저 번호 <major>와 마이너 번호 0을 할당하겠다는 의미입니다.

 

메이저 번호(Major Number)마이너 번호(Minor Number)는 리눅스 커널이 디바이스 드라이버와 실제 디바이스 파일을 연결하는 핵심 정보

 

  • 메이저 번호커널이 어떤 디바이스 드라이버에 작업을 전달해야 할지를 식별하기 위한 번호입니다.
    → 하나의 드라이버에 하나의 메이저 번호가 할당됩니다.
  • 마이너 번호같은 드라이버가 관리하는 여러 디바이스 인스턴스를 구분하기 위한 번호입니다.
    → 하나의 드라이버(=같은 메이저 번호) 아래에 여러 개의 디바이스 파일이 있을 수 있고, 이들을 마이너 번호로 식별합니다.

메이저 번호는 드라이버 초기화 단계에서 register_chrdev() 함수를 통해 반환값 형태로 제공 받을 수 있게 됩니다

 


5. 결과 확인

$ echo hello > /dev/mychardev
mychardev: opened
mychardev: closed
# write 기능 정의 없어 동작 X 

$ cat /dev/mychardev
mychardev: opened
mychardev: closed
# read 기능 정의 없어 동작 X

커널에서 mychardev 문자형 디바이스 파일을 열거나(open), 파일에 문자 쓰기(write), 작업을 다 끝나고 파일을 빠져나오는(release) 시점에 드라이버에서 정의한 함수들이 실행되게 됩니다.


참고

  • 문자열을 출력할 때 read() 함수는 사용자 공간으로 데이터를 복사하는 역할을 합니다.
  • copy_to_user()를 통해 사용자 공간에 안전하게 데이터를 전달해야 합니다.
  • offset(*off) 값을 사용하여 한 번 읽은 이후 다시 읽을 때는 0을 반환하도록 구현해야 합니다.