리눅스 디바이스 모델 & Character 드라이버

sungho·2024년 12월 31일

디바이스 드라이버

목록 보기
6/12

리눅스 커널은 방대한 하드웨어 장치를 효율적으로 관리하고 제어하기 위해 정교한 디바이스 모델을 사용합니다. 시스템에 존재하는 모든 장치를 계층적으로 구성하고 관리하는 추상화된 모델입니다. 장치 검색, 전원 관리, 장치 간의 관계 정의 등을 효율적으로 처리하여 커널의 복잡성을 줄이고 코드의 재사용성을 높입니다.

1. 리눅스 디바이스 모델

개념

  • Device
    • 시스템에 존재하는 모든 물리적 또는 가상 장치
  • Driver
    • 특정 장치를 제어하는 소프트웨어 모듈
  • Bus
    • 장치와 드라이버를 연결하는 통신 채널
  • Class
    • 유사한 기능을 수행하는 장치들을 그룹화
  • Framework
    • 특정 장치 유형에 대한 공통 인터페이스와 기능을 제공하는 추상화 계층
  • Platform Device
    • SoC(System on Chip)에 내장된 장치
    • 일반적으로 메모리 주소나 IRQ와 같은 시스템 자원을 할당
  • Platform Driver
    • platform_device를 제어하는 드라이버
  • Device Tree
    • ARM 기반 시스템에서 사용되는 하드웨어 구성 기술 방식
    • 하드웨어의 구성을 텍스트 파일 형태로 기술하여 커널에 전달
    • 커널은 부팅 시점에 시스템의 하드웨어 구성을 동적으로 파악

장점

  • 모듈화
    • 장치 드라이버 개발을 단순화하고 코드 중복을 줄임
  • 확장성
    • 새로운 장치와 버스를 쉽게 추가
  • 일관성
    • 모든 장치에 대한 일관된 인터페이스
  • 전원 관리
    • 시스템 전체의 전력 소비를 효율적으로 관리

디바이스 모델과 디바이스 트리

  • ARM 기반 시스템에서는 디바이스 트리를 사용하여 하드웨어 구성
    • 디바이스 트리는 플랫폼 장치, I2C 장치 등 시스템의 전반적인 하드웨어 구성
    • 플랫폼 드라이버는 디바이스 트리에 기술된 platform_device와 바인딩되어 장치를 제어
    • 커널은 부팅 시점에 시스템의 하드웨어 구성을 동적으로 파악과 필요한 드라이버를 로드하여 장치를 초기화

2. sysfs

  • sysfs는 리눅스 디바이스 모델을 사용자 공간에 노출하는 가상 파일 시스템
  • /sys 디렉터리 아래에 계층적으로 구성(장치, 드라이버, 버스, 클래스)

디렉터리 구조

  • /sys/bus
    • 시스템에 존재하는 모든 버스(I2C, USB, SPI 등)에 대한 정보를 제공
  • /sys/devices
    • system
      • CPU, 클럭 등 시스템 관련 장치 정보
    • virtual
      • 메모리 기반의 가상 장치 정보
    • platform
      • 플랫폼에 특화된 장치정보
  • /sys/class
    • 장치의 기능별 분류(input, net, block)에 따라 그룹화된 정보를 제공
  • /sys/kernel
    • 커널의 일반적인 파라미터 및 디버깅 정보
  • /sys/module
    • 적재된 모듈의 정보

udev와의 연관성

  • udev는 sysfs의 정보를 기반으로 장치 파일을 생성, 삭제하고, 장치에 대한 접근 권한을 설정
  • USB 장치를 연결하면 udev는 sysfs에서 해당 장치의 정보를 읽어 /dev/sdX와 같은 장치 파일을 생성

ex)

  • /sys/class/leds/ 디렉터리에서 시스템 LED를 제어
    # 예: LED 켜기
    echo 1 > /sys/class/leds/<led_name>/brightness
    
    # 예: LED 끄기
    echo 0 > /sys/class/leds/<led_name>/brightness
  • /sys/bus/usb/devices/ 디렉터리에서 USB 장치의 정보를 확인
    # 예: USB 장치 목록 확인
    ls /sys/bus/usb/devices/
    
    # 예: 특정 USB 장치의 정보 확인
    cat /sys/bus/usb/devices/1-1/manufacturer
  • /sys/devices/system/cpu/cpu0/cpufreq/ 디렉터리에서 CPU의 클럭 정보를 확인하고 변경
    # 예: 현재 CPU 클럭 확인
    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
    
    # 예: CPU 클럭 조절 정책 확인
    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
  • ls -l /sys 명령의 출력 결과를 통해 sysfs의 구조를 직관적으로 파악
$ ls -l /sys
total 0
drwxr-xr-x  2 root root 0 Dec 27 10:00 block
drwxr-xr-x 18 root root 0 Dec 27 10:00 bus
drwxr-xr-x 64 root root 0 Dec 27 10:00 class
drwxr-xr-x  4 root root 0 Dec 27 10:00 dev
drwxr-xr-x 14 root root 0 Dec 27 10:00 devices
drwxr-xr-x  5 root root 0 Dec 27 10:00 firmware
drwxr-xr-x  9 root root 0 Dec 27 10:00 fs
drwxr-xr-x  2 root root 0 Dec 27 10:19 hypervisor
drwxr-xr-x 13 root root 0 Dec 27 10:00 kernel
drwxr-xr-x 53 root root 0 Dec 27 10:00 module
drwxr-xr-x  2 root root 0 Dec 27 10:19 power

3. 프레임워크

  • 프레임워크는 특정 유형의 장치에 대한 공통 인터페이스와 기능을 제공하는 추상화 계층
  • Framebuffer
    • 그래픽 출력 장치(LCD, HDMI 등)를 위한 프레임워크
    • /dev/fb* 장치 파일을 통해 사용자 공간 애플리케이션이 화면에 직접 그림 그리기 가능
    • 간단한 텍스트 출력
      #include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <linux/fb.h>int main() {
          int fd = open("/dev/fb0", O_RDWR);
          if (fd == -1) {
              perror("Error opening framebuffer device");
              exit(1);
          }
      
          struct fb_var_screeninfo vinfo;
          if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
              perror("Error getting variable screen info");
              exit(1);
          }
      
          // framebuffer 메모리 매핑
          char *fbp = (char *)mmap(0, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8,
                                   PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      
          if (fbp == MAP_FAILED) {
              perror("Error mapping framebuffer memory");
              exit(1);
          }
      
          // 예: 화면 중앙에 텍스트 출력 (간략화된 예시)
          int x = vinfo.xres / 2;
          int y = vinfo.yres / 2;
          char *text = "Hello, Framebuffer!";
          for(int i = 0; text[i] != '\0'; i++){
              // 텍스트를 화면에 쓰는 로직 (간략화)
              // 이 부분은 폰트 렌더링, 문자 위치 계산 등을 포함해야 함.
              // fb_fix_screeninfo 구조체를 통해 얻은 정보 활용 가능.
          }
          
          
          munmap(fbp, vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8);
          close(fd);
          return 0;
      }
  • V4L2 (Video for Linux Two)
    • 카메라, 캡처 카드와 같은 비디오 장치를 위한 프레임워크
    • /dev/video* 장치 파일을 통해 비디오 스트림을 캡처하고 인코딩/디코딩, 이미지 처리 등을 수행
    • media-ctl
      • V4L2 장치의 설정을 변경하고 미디어 파이프라인을 구성
      • ex)
        • media-ctl -d /dev/media0 -l '"msm_csiphy0":1->"msm_csid0":0[1]'
    • v4l2-ctl
      • 카메라 설정을 확인하고 변경
      • ex)
        • v4l2-ctl -d /dev/video0 --all
  • ALSA(Advanced Linux Sound Architecture)
    • 사운드 카드, 오디오 코덱과 같은 오디오 장치를 위한 프레임워크
  • Input
    • 키보드, 마우스, 터치스크린과 같은 입력 장치를 위한 프레임워크
    • /dev/input/* 장치 파일을 통해 입력 이벤트를 수신
  • regulator
    • 전압 조정기를 제어하기 위한 프레임워크
    • sysfs를 통해 전압 조정기의 상태를 확인하고 설정을 변경
    • ex)
      • /sys/class/regulator/regulator/
  • clock
    • 클럭 소스를 관리하고 제어하기 위한 프레임워크
    • sysfs를 통해 클럭의 주파수를 확인하고 설정을 변경
    • ex)
      • /sys/class/clk/clk/

V4L2 Camera Framework

  • V4L2 Core
    • V4L2 프레임워크의 핵심 모듈
      • 사용자 애플리케이션과 드라이버 간의 인터페이스를 제공
    • ioctl 시스템 콜을 통해 V4L2 장치를 제어
  • Camera Sensor X driver
    • 특정 카메라 센서 모델을 제어하는 드라이버
  • GPIO framework
    • GPIO 핀을 제어하기 위한 프레임워크
  • I2C framework
    • I2C 버스를 통해 카메라 센서와 통신하기 위한 프레임워크
  • DCMI/CCP(Camera Parallel Interface)
    • 카메라 센서로부터 병렬 데이터를 수신하는 하드웨어 인터페이스
  • media-ctl
    • 사용자 공간 유틸리티로 V4L2 장치의 설정을 변경하고 미디어 파이프라인을 구성
    • media-ctl은 ioctl을 사용하여 드라이버를 제어
    • V4L2 장치의 연결 관계를 설정하여 데이터가 흐르는 경로를 지정
  • v4l2-ctl
    • 사용자 공간 유틸리티로 V4L2 장치의 설정을 확인하고 변경하는 데 사용

V4L2 예시

  • media-ctl 유틸리티를 사용하여 카메라 센서의 해상도, 프레임 속도 등을 설정하고 센서와 ISP(Image Signal Processor) 간의 연결을 구성
# 예: 카메라 센서와 ISP 간의 연결 설정
media-ctl -d /dev/media0 -l '"msm_csiphy0":1->"msm_csid0":0[1]'
media-ctl -d /dev/media0 -l '"msm_csid0":1->"msm_ispif0":0[1]'
media-ctl -d /dev/media0 -l '"msm_ispif0":1->"msm_ispw0":0[1]'
media-ctl -d /dev/media0 -l '"msm_ispw0":1->"msm_csid1":0[1]'
media-ctl -d /dev/media0 -l '"msm_csid1":1->"msm_vfe0_pix":0[1]'
media-ctl -d /dev/media0 -l '"msm_vfe0_pix":1->"jpeg_encoder":0[1]'
  • v4l2-ctl 유틸리티를 사용하여 카메라 설정을 확인하고 변경
    # 예: 카메라 설정 확인
    v4l2-ctl -d /dev/video0 --all
    
    # 예: 카메라 해상도 설정
    v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080
  • libv4l 라이브러리를 사용하여 C/C++ 애플리케이션에서 V4L2 장치를 제어

4. Character Driver

  • Character Driver는 사용자 공간 애플리케이션과 하드웨어 장치 간의 문자(byte) 스트림 기반 통신을 위한 인터페이스

구조체

  • struct file_operations
    • 장치 파일에 대한 시스템 콜(open, close, read, write, ioctl 등)을 처리하는 함수 포인터를 정의
  • struct cdev
    • Character 디바이스를 나타내는 구조체
  • struct class
    • sysfs에 장치를 등록
  • struct device
    • sysfs에 장치를 등록

함수

  • register_chrdev_region() / alloc_chrdev_region()
    • 장치 번호(major, minor)를 할당
    • register_chrdev_region()는 고정된 장치 번호를 할당
    • alloc_chrdev_region()는 동적으로 장치 번호를 할당
  • cdev_init()
    • struct cdev 구조체를 초기화하고 struct file_operations를 연결
  • cdev_add()
    • Character 디바이스를 커널에 등록
  • cdev_del()
    • Character 디바이스를 커널에서 제거
  • class_create()
    • 장치 클래스를 생성
  • device_create()
    • 장치를 생성하고 sysfs에 등록
  • copy_to_user()
    • 커널 공간의 데이터를 사용자 공간으로 복사
    • 복사에 실패한 바이트 수를 반환
    • 반환값이 0이면 복사 성공을 의미
    • 반환값이 0이 아니면 복사 실패를 의미하며 일반적으로 -EFAULT를 반환
  • copy_from_user()
    • 사용자 공간의 데이터를 커널 공간으로 복사
    • 복사에 실패한 바이트 수를 반환
    • 반환값이 0이면 복사 성공을 의미
    • 반환값이 0이 아니면 복사 실패를 의미하며 일반적으로 -EFAULT를 반환
  • ioctl()
    • 장치별 고유한 명령을 처리하는 데 사용되는 시스템 콜
    • cmd 인자를 통해 명령을 구분하고 arg 인자를 통해 명령에 필요한 데이터를 전달
    • 사용자 공간 애플리케이션에서 ioctl() 함수를 호출하여 Character Driver의 ioctl 함수 포인터가 가리키는 함수를 실행
    • ex)
      // 사용자 공간
      #include <sys/ioctl.h>#define MY_IOCTL_CMD _IOW('k', 1, int)
      
      int main() {
          int fd = open("/dev/my_device", O_RDWR);
          int data = 123;
          ioctl(fd, MY_IOCTL_CMD, &data);
          close(fd);
      }
      
      // 커널 공간 (Character Driver)
      static long my_driver_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
          switch (cmd) {
              case MY_IOCTL_CMD:
                  int data;
                  copy_from_user(&data, (int __user *)arg, sizeof(int));
                  printk(KERN_INFO "Received data: %d\n", data);
                  break;
              default:
                  return -ENOTTY; // 적절하지 않은 ioctl
          }
          return 0;
      }

Character Driver 개발 과정

  1. 장치 번호 할당
    • register_chrdev_region() 또는 alloc_chrdev_region() 함수를 사용하여 장치 번호를 할당
  2. file_operations 구조체 정의
    • 장치 파일에 대한 시스템 콜을 처리하는 함수 포인터를 정의
  3. cdev 구조체 초기화 및 등록
    • cdev_init() 함수를 사용하여 cdev 구조체를 초기화하고 file_operations를 연결한 후 cdev_add() 함수를 사용하여 커널에 등록
  4. sysfs에 장치 등록(선택 사항)
    • class_create()와 device_create() 함수를 사용하여 장치 클래스와 장치를 생성하고 sysfs에 등록
    • 사용자 공간에서 udev가 장치 파일을 자동으로 생성하고 sysfs를 통해 장치 정보를 확인
  5. read, write 함수 구현
    • copy_to_user()와 copy_from_user() 함수를 사용하여 사용자 공간과 커널 공간 간에 데이터를 복사
  6. ioctl 함수 구현(선택 사항)
    • 장치별 고유한 명령을 처리하기 위해 ioctl 함수를 구현

유저 공간 프로그램

#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main() {
    int fd = open("/dev/kdt_char_driver", O_RDWR);
    if (fd < 0) {
        perror("Failed to open device file");
        return -1;
    }

    char write_buf[1024] = "Hello from user space!";
    char read_buf[1024];

    // 장치에 데이터 쓰기
    ssize_t bytes_written = write(fd, write_buf, strlen(write_buf));
    if (bytes_written < 0) {
        perror("Failed to write to device");
        close(fd);
        return -1;
    }
    printf("Wrote %ld bytes to device\n", bytes_written);

    // 장치로부터 데이터 읽기
    ssize_t bytes_read = read(fd, read_buf, sizeof(read_buf));
    if (bytes_read < 0) {
        perror("Failed to read from device");
        close(fd);
        return -1;
    }
    printf("Read %ld bytes from device: %s\n", bytes_read, read_buf);

    close(fd);
    return 0;
}
  • open(), read(), write() 함수를 사용하여 /dev/kdt_char_driver 장치 파일에 데이터를 쓰고 읽기

5. 코드분석

#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/device.h>#define BUF_SIZE 1024

static char kernel_read_buffer[BUF_SIZE] = "Hello from kernel space!"; // read 버퍼 초기화
static char kernel_write_buffer[BUF_SIZE];
static dev_t kdt_dev;
static struct class *kdt_class;
static struct cdev kdt_cdev;

#define DRIVER_NAME "kdt_char_driver"#define DRIVER_CLASS "kdt_char_class"static ssize_t kdt_driver_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
    size_t to_copy = len < BUF_SIZE ? len : BUF_SIZE; // 복사할 크기 계산
    
    // copy_to_user() 함수의 반환값을 0과 비교하여 복사 성공 여부 확인
    if (copy_to_user(buf, kernel_read_buffer, to_copy)) {
        pr_err("read: copy_to_user failed\n");
        return -EFAULT; // 에러 발생 시 -EFAULT 반환
    }
    pr_info("read: read %zu bytes\n", to_copy); // 복사한 바이트 수 출력
    return to_copy; // 복사한 바이트 수 반환
}

static ssize_t kdt_driver_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
    size_t to_copy = len < BUF_SIZE ? len : BUF_SIZE; // 복사할 크기 계산
    
    // copy_from_user() 함수의 반환값을 0과 비교하여 복사 성공 여부 확인
    if (copy_from_user(kernel_write_buffer, buf, to_copy)) {
        pr_err("write: copy_from_user failed\n");
        return -EFAULT; // 에러 발생 시 -EFAULT 반환
    }
    pr_info("write: wrote %zu bytes\n", to_copy); // 복사한 바이트 수 출력
    return to_copy; // 복사한 바이트 수 반환
}

static int kdt_driver_open(struct inode *inode, struct file *file)
{
    pr_info("open\n");
    return 0;
}

static int kdt_driver_close(struct inode *inode, struct file *file)
{
    pr_info("close\n");
    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = kdt_driver_open,
    .release = kdt_driver_close,
    .read = kdt_driver_read,
    .write = kdt_driver_write
};

static int __init kdt_module_init(void)
{
    /* 여기서 노드를 할당 받는다. */
    if (alloc_chrdev_region(&kdt_dev, 0, 1, DRIVER_NAME) < 0) {
        pr_err("Device Nr. could not be allocated!\n");
        return -1;
    }
    pr_info("Allocated Major = %d & Minor = %d \n", MAJOR(kdt_dev), MINOR(kdt_dev));

    /* device class 생성 */
    if ((kdt_class = class_create(THIS_MODULE, DRIVER_CLASS)) == NULL) {
        pr_err("Device class can not be created!\n");
        goto ClassError;
    }

    /* device file 생성 */
    if (device_create(kdt_class, NULL, kdt_dev, NULL, DRIVER_NAME) == NULL) {
        pr_err("Can not create device file!\n");
        goto FileError;
    }

    /* character device 초기화 */
    cdev_init(&kdt_cdev, &fops);

    /* 커널에 등록 */
    if (cdev_add(&kdt_cdev, kdt_dev, 1) == -1) {
        pr_err("Registering of device to kernel failed!\n");
        goto AddError;
    }
    pr_info("kdt_character driver loaded\n");
    return 0;

AddError:
    device_destroy(kdt_class, kdt_dev);
FileError:
    class_destroy(kdt_class);
ClassError:
    unregister_chrdev_region(kdt_dev, 1);
    return -1;
}

static void __exit kdt_module_exit(void)
{
    cdev_del(&kdt_cdev);
    device_destroy(kdt_class, kdt_dev);
    class_destroy(kdt_class);
    unregister_chrdev_region(kdt_dev, 1);
    pr_info("kdt_character driver unloaded\n");
}

module_init(kdt_module_init);
module_exit(kdt_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("KDT <kdt_com>");
MODULE_DESCRIPTION("kdt character");
MODULE_VERSION("1.0.0");

// 모듈 파라미터 예시
static int my_param = 10;
module_param(my_param, int, 0);
MODULE_PARM_DESC(my_param, "An example integer parameter");`
  • kernel_read_buffer, kernel_write_buffer
    • 커널 공간에 할당된 버퍼로 사용자 공간과의 데이터 교환에 사용
    • kernel_read_buffer는 초기화 문자열
  • kdt_driver_read()
    • 사용자 공간에서 read() 시스템 콜을 호출했을 때 실행되는 함수
      • copy_to_user() 함수를 사용하여 kernel_read_buffer의 내용을 사용자 공간 버퍼로 복사
      • copy_to_user() 함수의 반환 값을 확인하여 복사 성공 여부를 확인하고 에러 발생 시 -EFAULT를 반환
      • 복사한 바이트 수는 len과 BUF_SIZE 중 작은 값으로 계산
  • kdt_driver_write()
    • 사용자 공간에서 write() 시스템 콜을 호출했을 때 실행되는 함수
      • copy_from_user() 함수를 사용하여 사용자 공간 버퍼의 내용을 kernel_write_buffer로 복사
      • copy_from_user() 함수의 반환 값을 확인하여 복사 성공 여부를 확인하고 에러 발생 시 -EFAULT를 반환
      • 복사한 바이트 수는 len과 BUF_SIZE 중 작은 값으로 계산
  • kdt_driver_open(), kdt_driver_close()
    • open()과 close() 시스템 콜을 처리하는 함수
  • fops
    • file_operations 구조체로 위에서 정의한 함수들을 가리키는 포인터
  • kdt_module_init()
    • 모듈 초기화 함수
      • alloc_chrdev_region() 함수로 장치 번호를 할당
      • class_create() 함수로 장치 클래스를 생성
      • device_create() 함수로 장치를 생성하고 sysfs에 등록
      • cdev_init() 함수로 cdev 구조체를 초기화하고 fops를 연결
      • cdev_add() 함수로 Character 디바이스를 커널에 등록
  • kdt_module_exit(): 모듈 종료 함수입니다. 등록된 장치와 클래스를 해제하고 장치 번호를 반환
  • 모듈 파라미터
    • my_param은 모듈 파라미터 예시로 insmod 명령어로 모듈을 로드할 때 값을 지정
    • ex)
      • sudo insmod kdt_character.ko my_param=20

Makefile 분석

obj- := kdt_character.o

export BUILDROOT=/home/kesl/grepp/src/buildroot
export ARCH=arm64
export CROSS_COMPILE=$(BUILDROOT)/output/host/bin/aarch64-buildroot-linux-gnu-

KERNELDIR ?= $(BUILDROOT)/output/build/linux-custom

all default: modules
install: modules_install

modules modules_install help clean:
	$(MAKE) -C $(KERNELDIR) M=$(shell pwd) $@

.PHONY: nfs
nfs:
	sudo cp kdt_character.ko ~/nfs # sudo 권한 필요
  • obj- := kdt_character.o
    • kdt_character.c를 컴파일하여 kdt_character.o 오브젝트 파일을 설정
  • BUILDROOT, ARCH, CROSS_COMPILE
    • 크로스 컴파일을 위한 환경 변수를 설정
  • KERNELDIR
    • 리눅스 커널 소스 디렉터리를 지정
  • modules, modules_install, help, clean
    • make 유틸리티의 타겟을 정의
  • (MAKE) -C $(KERNELDIR) M=(shell pwd) $@
    • 커널 소스 디렉터리에서 현재 디렉터리를 모듈 빌드 디렉터리로 지정하고 make를 실행
  • nfs
    • 컴파일된 모듈(kdt_character.ko)을 NFS 공유 디렉터리에 복사
    • sudo 권한이 필요

빌드

  1. 위의 Makefile과 kdt_character.c를 같은 디렉터리에 저장
  2. Makefile에서 BUILDROOT, KERNELDIR 변수를 자신의 환경에 맞게 수정
  3. 터미널에서 make 명령을 실행하여 모듈을 빌드
  4. sudo insmod kdt_character.ko 명령으로 모듈을 로드
  5. dmesg | tail 명령으로 커널 로그를 확인하여 모듈이 정상적으로 로드되었는지 확인
    • "kdt_character driver loaded" 메시지가 출력
  6. /dev/kdt_char_driver 장치 파일이 생성되었는지 확인
  7. echo "hello" > /dev/kdt_char_driver 명령으로 장치 파일에 데이터 쓰기
  8. cat /dev/kdt_char_driver 명령으로 장치 파일에서 데이터를 읽기
    • "Hello from kernel space!"가 출력
  9. sudo rmmod kdt_character 명령으로 모듈을 언로드

6. 결론

리눅스 디바이스 모델, sysfs, Character Driver, 그리고 V4L2 프레임워크에 대해 자세히 살펴보았습니다.  kdt_character.c 코드를 통해 Character Driver의 개발 과정을 이해하고 사용자 공간과 커널 공간 간의 데이터 통신 방법을 익혔습니다. ioctl 시스템 콜, 모듈 파라미터, 사용자 공간 프로그램 등을 다루어 Character Driver의 활용도를 높였습니다.

0개의 댓글