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

ex)
# 예: LED 켜기
echo 1 > /sys/class/leds/<led_name>/brightness
# 예: LED 끄기
echo 0 > /sys/class/leds/<led_name>/brightness# 예: USB 장치 목록 확인
ls /sys/bus/usb/devices/
# 예: 특정 USB 장치의 정보 확인
cat /sys/bus/usb/devices/1-1/manufacturer# 예: 현재 CPU 클럭 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
# 예: CPU 클럭 조절 정책 확인
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor$ 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
#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 Camera Framework
V4L2 예시
# 예: 카메라 센서와 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 -d /dev/video0 --all
# 예: 카메라 해상도 설정
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080
// 사용자 공간
#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;
}#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;
}
#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");`
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 권한 필요
리눅스 디바이스 모델, sysfs, Character Driver, 그리고 V4L2 프레임워크에 대해 자세히 살펴보았습니다. kdt_character.c 코드를 통해 Character Driver의 개발 과정을 이해하고 사용자 공간과 커널 공간 간의 데이터 통신 방법을 익혔습니다. ioctl 시스템 콜, 모듈 파라미터, 사용자 공간 프로그램 등을 다루어 Character Driver의 활용도를 높였습니다.