이전 open(), close()에 이어서 이번에는 read(), write()를 구현해 보고자 한다.
driver.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/cdev.h>
#define DEVICE_NAME "driver"
static dev_t device_dev;
static struct class *device_class;
static struct cdev device_cdev;
static char *buffer = NULL;
int device_open(struct inode *inode, struct file *filp);
int device_release(struct inode *inode, struct file *filp);
ssize_t device_read(struct file *filp, char *buf, size_t count, loff_t *f_ops);
ssize_t device_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops);
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write
};
int device_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "device open\n");
return 0;
}
int device_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "device close\n");
return 0;
}
ssize_t device_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)
{
int msg;
if(buffer == NULL)
return -1;
msg = copy_to_user(buf, buffer, count);
printk("[%s] count = %ld, msg = %u\n", __func__, count, msg);
return count - msg;
}
ssize_t device_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
int msg;
if(buffer != NULL)
kfree(buffer);
if((buffer = kmalloc(count + 1, GFP_KERNEL)) == NULL)
return -ENOMEM;
msg = copy_from_user(buffer, buf, count);
printk("[%s] count = %ld, msg = %u\n", __func__, count, msg);
return count - msg;
}
int __init device_init(void)
{
if(alloc_chrdev_region(&device_dev, 0, 1, DEVICE_NAME))
{
printk(KERN_ALERT "[&s] alloc_chrdev_region failed\n", __func__);
return -1;
}
cdev_init(&device_cdev, &fops);
if(cdev_add(&device_cdev, device_dev, 1))
{
printk(KERN_ALERT "[%s] cdev_add failed\n", __func__);
unregister_chrdev_region(device_dev, 1);
}
if((device_class = class_create(THIS_MODULE, DEVICE_NAME)) == NULL)
{
printk(KERN_ALERT "[%s] class_add failed\n", __func__);
unregister_chrdev_region(device_dev, 1);
}
if(device_create(device_class, NULL, device_dev, NULL, DEVICE_NAME) == NULL)
{
printk(KERN_ALERT "[%s] device_create failed\n", __func__);
class_destroy(device_class);
}
printk(KERN_INFO "[%s] successfully created device: Major = %d, Minor = %d\n",
__func__, MAJOR(device_dev), MINOR(device_dev));
return 0;
}
void __exit device_exit(void)
{
device_destroy(device_class, device_dev);
class_destroy(device_class);
cdev_del(&device_cdev);
unregister_chrdev_region(device_dev, 1);
printk("KERN_INFO [%s] successfully unregistered.\n", __func__);
}
module_init(device_init);
module_exit(device_exit);
MODULE_AUTHOR("Test");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("character device driver");
이전과 마찬가지로 read 함수와 write 함수와 연결할 수 있게 file operation에 해당 코드를 적어준다.
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write
};
read, write을 진행할 시 메모리 할당을 하기 위해 kmalloc/kfree를 사용한다. 할당 속도가 빠르고 간단해 디바이스 드라이버에서 가장 많이 사용된다.
사용법은 malloc(), free() 함수와 유사하다.
kmalloc은 size 말고도 flags라는 인자를 받는데, 이 flags는 메모리를 어떻게 할당받을 건지를 정하는 플래그이다.
GFP_KERNEL : 동적 메모리 할당이 항상 성공하도록 요구
GFP_ATOMIC : 커널에 할당 가능한 메모리가 있으면 무조건 할당, 없으면 즉시 NULL반환 프로세스가 잠드는 경우는 없지만 할당에 실패할 경우를 대비해 예외처리 필수
GFP_DMA : 연속된 물리 메모리를 할당받을 때 사용
그 이외에도 copy_to_user, copy_from_user 함수를 사용했는데 write를 하면 디바이스 드라이버에 메모리를 할당해서 저장해두다가, read를 하면 저장해둔 메모리로부터 유저 공간으로 복사한다. 유저 공간의 권한 체크를 위해 copy_to_user, copy_from_user 함수를 사용한다.
int copy_to_user(void __user* to, const void* from, unsigned long n)
커널 스페이스의 주소 from 을 base로 데이터 n바이트를 유저 스페이스에 있는 주소 to에 copy 한다.
[return]
복사 실패한 byte의 수.
성공 시 : 0
int copy_from_user(void* to, const void __user* from, unsigned long n)
유저 스페이스에 있는 주소 from을 base로 데이터 n바이트를 커널 스페이스의 주소 to에 copy 한다.
[return]
복사 실패한 byte의 수.
성공 시 : 0
read_write.c
#include <stdio.h> // printf()
#include <unistd.h> // close(), read(), write()
#include <sys/fcntl.h> // open()
int main(void) {
int fd;
char buf[1000];
int read_ret, write_ret;
fd = open("/dev/driver", O_RDWR);
if (fd < 0)
{
printf("failed opening device\n");
return 0;
}
else
{
printf("succeess opening device\n");
}
write_ret = write(fd, "hello", 5);
read_ret = read(fd, buf, 5);
printf("fd = %d, ret write = %d, ret read = %d\n", fd, write_ret, read_ret);
printf("content = %s\n", buf);
close(fd);
}
디바이스 드라이버를 등록한 후 해당 코드를 작성한 뒤 실행시켜준다.
정상적으로 read(), wrtie()가 작동한 것을 확인할 수 있다.
이제 dmesg 명령어를 통해 커널 메시지를 확인해보면 정상적으로 write 되고 read 된 것을 확인 할 수 있다.
'Linux > Device Driver' 카테고리의 다른 글
open(), close() (0) | 2022.11.25 |
---|---|
ioctl() (0) | 2022.11.25 |
디바이스 드라이버 (0) | 2022.11.20 |