rorosi 2022. 11. 25. 16:00
728x90

ioctl

ioctl() 함수란 하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 함수이다. read(), write()를 이용해서 데이터를 읽고 쓰는 등의 기능은 가능하지만 하드웨어를 제어하거나 상태 정보를 확인하려면 ioctl()를 이용해야 한다.

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

첫 번째 인자는 open 한 디바이스 드라이버의 fd 값이다

두 번째 인자는 디바이스에게 전달할 명령이다. 이 명령에 따라서 디바이스를 컨트롤할 수 있다.

 

에러시 -1을 반환, 다른 시스템 호출과 같이 성공하면 0보다 크거나 같은 값 리턴

 

https://rorsi.tistory.com/66

 

read(), write()

https://rorsi.tistory.com/65 open(), close() https://rorsi.tistory.com/61 위 포스트에 이어서 디바이스 드라이버를 만들었다면 이제 그 드라이버를 이용해 open(), close()를 구현해 보고자 한다. driver.c #include #include

rorsi.tistory.com

 

이전 read, write에 이어서 ioctl() 함수를 구현해 보고자 한다.

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"
#define CALL_PRINT 1

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);
long device_ioctl(struct file *filp, unsigned int cmd, unsigned long data);

static struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write,
	.unlocked_ioctl = device_ioctl
};

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;
}

long device_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
	switch (cmd) {
		case CALL_PRINT:
			printk(KERN_INFO "[%s] CALL PRINT!", __func__);
			break;
		default:
			break;
	}

        return 0;
}

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");

위에 설명한 거와 같이 ioctl() 함수와 연결할 수 있게 file operation에 해당 코드를 적어준다.

static struct file_operations fops = {
	.open = device_open,
	.release = device_release,
	.read = device_read,
	.write = device_write,
	.unlocked_ioctl = device_ioctl
};

그런데 ioctl이라는 함수는 어디로 가고 왜 unlocked_ioctl, compat_ioctl일까? 우선 unlocked_ioctl부터 보자. 왜 unlocked라는 말이 붙었을까? 옛날 (2.6 이전)에는 ioctl을 호출할 때, BKL을 사용했다. BKL은, 커널 전체에 걸리는 락으로, 동시에 여러 개의 프로세스가 커널 모드에 진입하지 못하도록 막는 락이다. 듣기만 해도 엄청나게 비효율적이란 걸 알 수 있다. 그래서 2.6.39를 기준으로 완전히 제거되었다. 이제는 ioctl을 호출할 때 BKL이 아니라, 디바이스 드라이버가 스스로 락을 관리해야 한다. 따라서 이름이 unlocked_ioctl로 변경되었다. compat_ioctl은 32비트와의 호환성을 위한 함수이다.

 

ioctl.c

#include <stdio.h>  // printf()
#include <unistd.h> // close(), read(), write()
#include <sys/fcntl.h>  // open()
#include <sys/ioctl.h>  // ioctl()

#define CALL_PRINT 1

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);

  if(ioctl(fd, CALL_PRINT, NULL))
  {
    printf("failed ioctl\n");
  }
  else
  {
    printf("success ioctl\n");
  }

  close(fd);
}

디바이스 드라이버를 등록 한 후 해당 코드를 작성한 뒤 실행시켜준다.

정상적으로 ioctl()이 작동한 것을 확인할 수 있다.

 

이제 dmesg 명령어를 통해 커널 메시지를 확인해보면 정상적으로 ioctl을 통해 출력된 것을 볼 수 있다.

728x90