ioctl()
Linux/Device Driver

ioctl()

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을 통해 출력된 것을 볼 수 있다.

'Linux > Device Driver' 카테고리의 다른 글

read(), write()  (0) 2022.11.25
open(), close()  (0) 2022.11.25
디바이스 드라이버  (0) 2022.11.20