리눅스 커널
모듈의 장점 : 컴퓨터를 리부트할 필요없이 바로 장치를 사용할 수 있다.
디바이스 종류 :
- char : io가 char(1 byte) 단위로 이루어짐, HID
- block : 대량의 바이트를 전송, storage(HDD, USBmem etc)
- network : 네트워크 장치, NIC
- ...
char 디바이스 특징 : 간헐적 데이터 발생, 바이트 데이터 발생
캐릭터 디바이스 등록 : 디바이스를 등록하면 장치 배열에 저장, 이후 커널이 관리(struct cdev)
Major 번호 : 장치의 종류를 구분하는 식별 번호, 드라이버를 찾아올 수 있게 하는 번호
Minor 번호 : 동종 장치의 개별적 장치를 지정하는 고유 식별 번호, 문자 장치와 블록 장치는 독립적으로 사용
cat /proc/devices
- /proc : 커널이 open 해놓은 번호
- /sys : 장치(드라이버)가 open 해놓은 번호
장치 번호를 지정하는 법
cat /proc/devices를 사용하여 현황 파악
장치에 예약된 번호를 사용하거나
비어 있는 번호를 사용하거나(<- 255)(~ 512)
register_chrdev_region() 사용하여 직접 지정
할당하는 방법
자동으로 할당, 비어있는 번호를 자동으로 할당 받음
alloc_chrdev_region()
해제하는 방법
unregister_chrdev_region() 사용하여 커널로부터 등록 해제
make에 앞서서 header 파일 필요(/lib/modules/6.1.21-v8+/ - raspberrypi-kernel-headers)
커널에 장치를 등록하는 방법
1 | 주 번호(major number), 부 번호(minor number) 결정 |
2 | file_operations 구조체 정의 (read, write, open, release 등 함수 포인터) |
3 | cdev 구조체 할당 및 초기화 |
4 | **cdev_add()**로 캐릭터 디바이스를 커널에 등록 |
5 | (선택) **class_create() 및 device_create()**로 /dev 아래에 디바이스 노드 자동 생성 |
register_chrdev_region() | 특정 주/부 번호 범위를 예약 |
alloc_chrdev_region() | 주 번호를 자동 할당 |
struct cdev | 캐릭터 디바이스 커널 객체 (file_operations와 연결됨) |
cdev_init() | cdev 구조체 초기화 |
cdev_add() | 커널에 cdev 등록 |
struct file_operations | open, read, write, release 같은 함수 포인터들을 담은 구조체 |
class_create() | udev 연동을 위한 class 객체 생성 (sysfs 및 /dev 자동 노드 생성용) |
device_create() | sysfs 및 /dev 아래 디바이스 노드 생성 |
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NAME "mychardev"
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
// file_operations 함수
static int my_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "Device opened\n");
return 0;
}
static int my_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "Device closed\n");
return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
printk(KERN_INFO "Read called\n");
return 0;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
printk(KERN_INFO "Write called\n");
return count;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
};
// 모듈 초기화
static int __init my_init(void) {
int ret;
// 주 번호 자동 할당
ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT "Failed to allocate major number\n");
return ret;
}
// cdev 초기화 및 등록
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_cdev, dev_num, 1);
if (ret < 0) {
unregister_chrdev_region(dev_num, 1);
printk(KERN_ALERT "Failed to add cdev\n");
return ret;
}
// /dev에 장치 노드 생성 (udev 연동)
my_class = class_create(THIS_MODULE, DEVICE_NAME);
device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
printk(KERN_INFO "Device registered: major=%d minor=%d\n", MAJOR(dev_num), MINOR(dev_num));
return 0;
}
// 모듈 종료
static void __exit my_exit(void) {
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "Device unregistered\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YSCHOI");
MODULE_DESCRIPTION("Simple Character Device Example");
mknod /dev/name type major minor <= dev 폴더 안에서 기기 등록(app > kernel)
insmod filename.ko <= module 등록하기 (/proc/devices, kernel > module)
어플리케이션(사용자 공간)에서 드라이버 사용하기(위의 예시에 이어서)
① 커널 모듈 로드 | insmod 또는 modprobe로 mychardev.ko를 커널에 올림 |
② 장치 파일 생성 | /dev/mychardev 같은 장치 노드를 mknod로 생성하여 사용자 공간과 커널 공간 연결 |
③ 어플리케이션에서 접근 | C 프로그램에서 open("/dev/mychardev"), read(), write(), ioctl() 등을 사용해 커널 모듈의 file_operations 인터페이스와 통신 |
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/dev/mychardev", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
char buf[100];
ssize_t n = read(fd, buf, sizeof(buf));
if (n >= 0) {
printf("Read %ld bytes: %.*s\n", n, (int)n, buf);
} else {
perror("read");
}
const char *msg = "hello driver";
n = write(fd, msg, strlen(msg));
if (n >= 0) {
printf("Wrote %ld bytes\n", n);
} else {
perror("write");
}
close(fd);
return 0;
}
어플리케이션 → 시스템 콜 | open(), read(), write() |
커널 → VFS 계층 | Virtual File System 계층에서 요청 라우팅 |
VFS → 드라이버 file_operations | 해당 장치의 file_operations 구조체로 연결 |
드라이버 내부 구현 | 실제 하드웨어 제어, 내부 버퍼, 메모리 처리 등 수행 |
insmod와 mknod의 시점 차이로 어플리케이션이 모듈을 못 찾아올 수 있다 => 해결법 : insmod가 실행할 때 mknod도 실행하도록 구성
device_create(), device_destroy() 등 함수 이용
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NAME "mychardev"
static int major;
static struct class *mychardev_class;
static struct cdev my_cdev;
static int mychardev_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "mychardev: device opened\n");
return 0;
}
static int mychardev_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "mychardev: device closed\n");
return 0;
}
static ssize_t mychardev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
printk(KERN_INFO "mychardev: read called\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mychardev_open,
.release = mychardev_release,
.read = mychardev_read,
};
static int __init mychardev_init(void) {
dev_t dev;
int result;
// 메이저 번호 자동 할당
result = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (result < 0) {
printk(KERN_WARNING "mychardev: can't get major number\n");
return result;
}
major = MAJOR(dev);
// cdev 등록
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
result = cdev_add(&my_cdev, dev, 1);
if (result) {
printk(KERN_NOTICE "mychardev: Error %d adding cdev\n", result);
unregister_chrdev_region(dev, 1);
return result;
}
// /dev/mychardev 생성
mychardev_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(mychardev_class)) {
printk(KERN_ERR "mychardev: failed to create class\n");
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
return PTR_ERR(mychardev_class);
}
device_create(mychardev_class, NULL, dev, NULL, DEVICE_NAME);
printk(KERN_INFO "mychardev: module loaded, major=%d\n", major);
return 0;
}
static void __exit mychardev_exit(void) {
dev_t dev = MKDEV(major, 0);
device_destroy(mychardev_class, dev);
class_destroy(mychardev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "mychardev: module unloaded\n");
}
module_init(mychardev_init);
module_exit(mychardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YSCHOI");
MODULE_DESCRIPTION("Simple Character Device with Auto Node Creation");
드라이버에서 GPIO 다루기 => 데이터 시트 값을 읽으며 구현
'VEDA 복습 > 리눅스, 리눅스 프로그래밍' 카테고리의 다른 글
VEDA 53일차 - 임베디드 파일시스템, 디바이스 드라이버 (1) | 2025.06.02 |
---|---|
VEDA 50일차 - 리눅스 드라이버 (1) | 2025.05.28 |
VEDA 48일차 - 리눅스 드라이버 (0) | 2025.05.27 |
VEDA 44일차 - 하드웨어 제어 + TCP/IP 서버 -클라이언트 프로그래밍 (0) | 2025.05.20 |
VEDA 43일차 - 하드웨어 제어 + TCP/IP 서버 -클라이언트 프로그래밍 (0) | 2025.05.19 |