read(), write()
Linux/Device Driver

read(), write()

https://rorsi.tistory.com/65

이전 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