일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- vm_area_struct
- memory
- strex
- kmalloc
- multiqueue
- kafka
- 카프카
- allocator
- blk-mq
- Android
- slowpath
- lruvec
- Apache
- page
- vmalloc
- Kernel
- commit
- buddy_system
- proc
- pmap
- fastpath
- spinlock
- NDK
- slab
- slub
- Linux
- devicedriver
- BLOCK
- mm_struct
- Network
- Today
- Total
Art of Pr0gr4m
[Linux Kernel 5] Character Device Driver 본문
Character Device Driver는 유저 프로세스로부터 직접 (버퍼 캐시 등을 사용하지 않고) 데이터를 읽고 쓰는 디바이스 드라이버다.
이 외에 블록 디바이스, 네트워크 디바이스 드라이버는 추후 포스팅 예정
리눅스는 VFS를 통해 디바이스를 파일로 다루고 있다
/dev 아래 있는 디바이스 파일(노드)들은 디바이스와 커널 및 응용프로그램간 인터페이스를 제공한다
디바이스의 major number는 디바이스들을 구분하기 위해 사용하며
minor number는 동일한 디바이스가 여러 개 있을 때, 이들을 구분하기 위해 사용한다
1. struct file_operations
struct file_oeprations 구조체는 드라이버와 유저 프로세스간의 인터페이스로,
함수 포인터로 선언되어있는 멤버들을 구현해 사용한다
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
이 중 주로 open(), release(), read(), write() 함수를 구현해 다음과 같이 등록한다
static struct file_operations vd_fops = {
.read = virtual_device_read,
.write = virtual_device_write,
.open = virtual_device_open,
.release = virtual_device_release
};
2. 디바이스 드라이버 작성
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
#define DEVICE_NAME "chardev"
#define BUF_LEN 1024
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM = 2;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
static int chardev_open(struct inode *, struct file *);
static int chardev_release(struct inode *, struct file *);
static ssize_t chardev_read(struct file *, char *, size_t, loff_t *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);
struct file_operations chardev_fops = {
.open = chardev_open,
.release = chardev_release,
.read = chardev_read,
.write = chardev_write,
};
struct data {
unsigned char buffer[BUF_LEN];
};
int __init init_chardev(void)
{
int alloc_ret = 0;
int cdev_err = 0;
int minor;
dev_t dev;
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DEVICE_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
// get the major number value in dev
chardev_major = MAJOR(dev);
dev = MKDEV(chardev_major, MINOR_BASE);
// initialize a cdev structure
cdev_init(&chardev_cdev, &chardev_fops);
chardev_cdev.owner = THIS_MODULE;
// add a char device to the system
cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", cdev_err);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
chardev_class = class_create(THIS_MODULE, "chardev");
if (IS_ERR(chardev_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&chardev_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(chardev_class, NULL, MKDEV(chardev_major, minor), NULL, "chardev%d", minor);
}
return 0;
}
void __exit exit_chardev(void)
{
int minor;
dev_t dev = MKDEV(chardev_major, MINOR_BASE);
for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(chardev_class, MKDEV(chardev_major, minor));
}
class_destroy(chardev_class);
cdev_del(&chardev_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
}
static int chardev_open(struct inode *inode, struct file *file)
{
struct data *p = kmalloc(sizeof(struct data), GFP_KERNEL);
if (p == NULL) {
printk(KERN_ERR "kmalloc - Null");
return -ENOMEM;
}
file->private_data = p;
return 0;
}
static int chardev_release(struct inode *inode, struct file *file)
{
if (file->private_data) {
kfree(file->private_data);
file->private_data = NULL;
}
return 0;
}
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct data *p = filp->private_data;
printk("Before calling the copy_from_user() function : [%p/%s]\n", p->buffer, p->buffer);
if (copy_from_user(p->buffer, buf, count) != 0) {
return -EFAULT;
}
printk("After calling the copy_from_user() function : [%p/%s]\n", p->buffer, p->buffer);
return count;
}
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct data *p = filp->private_data;
if(count > BUF_LEN) {
count = BUF_LEN;
}
if (copy_to_user(buf, p->buffer, count) != 0) {
return -EFAULT;
}
return count;
}
module_init(init_chardev);
module_exit(exit_chardev);
3. init_chardev
init_chardev는 디바이스 드라이버를 등록할때 초기화를 위한 엔트리이다
문자 디바이스의 major number와 minor number가 고정되어 있다면 register_chrdev_region 함수로 자원을 할당받고
동적으로 number와 자원을 할당받으려면 alloc_chrdev_region 함수를 사용한다
디바이스 번호는 dev_t 타입 변수에 저장하며, MKDEV 매크로를 이용하여 디바이스 넘버를 지정할 수 있다
또한 dev_t 변수에서 major number와 minor number는 MAJOR와 MINOR 매크로로 파싱할 수 있다
문자 디바이스 드라이버는 struct char_device_struct 구조체로 관리되며,
외부에는 인터페이스 역할을 하는 struct cdev 구조체가 공개된다
#define CHRDEV_MAJOR_HASH_SIZE 255
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_init 함수는 다음과 같이 문자 디바이스를 커널에 등록하기 위한 초기화를 수행한다
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
보는 것처럼 struct file_operations 구조체를 등록한다
문자 디바이스는 char_device_struct 구조체에서 볼 수 있는 것처럼 연결 리스트로 관리하는데,
cdev_add는 해당 리스트에 디바이스를 추가하며 cdev_del은 디바이스를 제거한다
cdev_get() 함수는 디바이스의 사용 횟수를 1 증가시키고, cdev_put()은 사용 횟수를 1 감소시킨다
class_create() 함수는 시스템에 생성할 디바이스의 클래스를 생성하며
device_create() 함수는 실제로 시스템에 디바이스 노드를 생성한다
4. exit_chardev
exit_chardev는 디바이스 드라이버가 제거될 때 실행되는 엔트리 함수다
초기화 함수에서 할당한 자원들을 역순으로 해제한다
device_destroy 함수는 디바이스 노드를 제거한다
class_destroy 함수는 디바이스 클래스를 제거한다
cdev_del은 문자 디바이스 관리 리스트에서 디바이스를 제거한다
unregister_chrdev_region 함수는 할당된 장치 번호 자원을 반납한다
5. open & release
유저 프로세스에서 해당 디바이스를 open / close 할 때 호출되는 함수이다
해당 예제에서는 내부적으로 사용하기 위한 데이터(버퍼)를 할당 및 해제하며, 파일 구조체에 등록 및 해제한다
6. read & write
유저 프로세스에서 read / write 함수를 이용하여 해당 디바이스에 읽고 쓸 때 호출되는 함수이다
copy_to_user 함수와 copy_from_user 함수를 이용하여 유저 데이터와 커널 데이터를 복사 한다
7. 테스트 코드
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define BASIC
#define BUF_LEN 1024
#define TEXT_LEN 5
int main()
{
static char buf[1024];
int fd;
if ((fd = open("/dev/chardev0", O_RDWR)) < 0) {
perror("open error");
}
if (write(fd, "hello", TEXT_LEN) < 0) {
perror("write error");
}
if (read(fd, buf, TEXT_LEN) < 0) {
perror("read error");
} else {
printf("%s\n", buf);
}
if (close(fd) != 0) {
perror("close error");
}
return 0;
}
해당 디바이스 드라이버를 열고 쓰고 읽고 닫아서 테스트한다
8. 빌드
obj-m += chardev.o
TARGETS = chardev_app
KDIR := /lib/modules/$(shell uname -r)/build
default: ${TARGETS}
$(MAKE) -C $(KDIR) M=$(PWD) modules
CC := gcc
%.c%:
${CC} -o $@ $^
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -f ${TARGETS}
위 Makefile을 이용하여 빌드한다
9. 실행 결과
hello 문자열을 정상적으로 write한 후 read한 걸 볼 수 있다.
참고로 위와 같이 디바이스 노드를 생성하면 600 권한으로 되어있는 것을 볼 수 있다.
이 상태에선 일반 권한의 유저 프로세스는 해당 디바이스를 사용할 수 없다.
위와 같이 chmod로 권한을 추가해주던가, 생성 시 자동으로 권한을 설정할 수 있게
/etc/udev/rules.d/80-chardev.rules 파일에 'KERNEL == "chardev[0-9]*",GROUP="root",MODE="0666"' 를 추가해준다
'IT > Linux Kernel' 카테고리의 다른 글
[Linux Kernel 5] proc & seq_file (0) | 2020.05.02 |
---|---|
[Linux Kernel 5] Crypto Device Driver (0) | 2020.04.30 |
[Linux Kernel 5] Character Device Driver IOCTL (0) | 2020.04.30 |
[Linux Kernel 5] Module Programming Basic (0) | 2020.04.25 |
[Linux Kernel 5] system call 추가하기 (0) | 2020.04.24 |