Art of Pr0gr4m

[Linux Kernel 5] Character Device Driver IOCTL 본문

IT/Linux Kernel

[Linux Kernel 5] Character Device Driver IOCTL

pr0gr4m 2020. 4. 30. 05:16

 

ioctl은 스트림 디바이스를 다루기 위한 확장이었다

현재는 기능이 더욱 확장되어 vfs상의 파일(하드웨어, 파일 등)을 제어하기 위한 오퍼레이션이 되었다

 

ioctl에 대한 더 자세한 정보는 다음 링크를 참고하며, 매뉴얼은 다음 링크를 참고한다

 

1. IOCTL 구조

 

ioctl의 인자로 전달되는 request는 커널 내부의 cmd로 전달되며, 기타 인자들은 arg로 전달된다

시스템 호출 절차를 간략화하면 ioctl() -> sys_ioctl() -> dev_ioctl() 이 된다

 

디바이스 드라이버에서 작성하는 ioctl은 대략 다음과 같다

 

static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
		case 1:
			break;
 
		case 2:
			break;
	}
	return 0;
}
 
struct file_operations chardev_fops = {
	.unlocked_ioctl = chardev_ioctl,
};

 

 

2. IOCTL 관련 매크로

 

ioctl의 cmd는 다음과 같은 32bit로 구성되어있다

 

[ 2 (TYPE) ] [ 14 (Data Size) ] [ 8 (Magic Number) ] [ 8 (Identified Number) ]

 

해당 비트들은 아래와 같이 선언되어있다

 

#define _IOC_NRBITS	8           // 구분 번호
#define _IOC_TYPEBITS	8           // 매직 넘버
#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS	14          // 데이터 사이즈
#endif
#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS	2            // 속성 유형
#endif

 

해당 cmd 명령을 만드는 매크로와 명령을 해석하는 매크로는 다음과 같이 선언되어 있다

 

#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \
	 ((type) << _IOC_TYPESHIFT) | \
	 ((nr)   << _IOC_NRSHIFT) | \
	 ((size) << _IOC_SIZESHIFT))
 
#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif
 
/*
 * Used to create numbers.
 *
 * NOTE: _IOW means userland is writing and kernel is reading. _IOR
 * means userland is reading and kernel is writing.
 */
#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size)	_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
 
/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr)		(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)		(((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)		(((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)		(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

 

macro desc
_IO(type, nr) type과 nr만 전달하는 ioctl에 사용
_IOR(type, nr, size) 디바이스 드라이버에서 데이터를 읽는 ioctl에 사용
_IOW(type, nr, size) 디바이스 드라이버에서 데이터를 쓰는 ioctl에 사용
_IORW(type, nr, size) 디바이스 드라이버에서 데이터를 읽고 쓰는 ioctl에 사용
_IOC_DIR(nr) 읽기/쓰기 속성 필드 파싱
_IOC_TYPE(nr) type 필드 값 파싱
_IOC_NR(nr) nr 필드 값 파싱
_IOC_SIZE(nr) size 필드 값 파싱

 

실제 사용 예는 다음과 같다

 

#define IOCTL_READ		_IOR(IOCTL_MAGIC, 0, struct ioctl_info)
#define IOCTL_WRITE		_IOW(IOCTL_MAGIC, 1, struct ioctl_info)
#define IOCTL_STATUS		_IO(IOCTL_MAGIC, 2)
#define IOCTL_RW		_IOWR(IOCTL_MAGIC, 3, struct ioctl_info)
 
 
if (_IOC_TYPE(cmd) != IOCTL_MAGIC) return -EINVAL;

 

 

3. ioctl 디바이스 드라이버 예제 소스

 

ioctl.h

#ifndef __IOCTL_H__
#define __IOCTL_H__
 
#include <linux/ioctl.h>
 
struct ioctl_info {
	unsigned long size;
	char buf[128];
};
 
#define IOCTL_MAGIC		'G'
#define IOCTL_READ		_IOR(IOCTL_MAGIC, 0, struct ioctl_info)
#define IOCTL_WRITE		_IOW(IOCTL_MAGIC, 1, struct ioctl_info)
#define IOCTL_STATUS	_IO(IOCTL_MAGIC, 2)
#define IOCTL_RW		_IOWR(IOCTL_MAGIC, 3, struct ioctl_info)
#define IOCTL_NR		4
 
#endif

 

ioctl.c

#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/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/current.h>
#include "ioctl.h"
 
MODULE_LICENSE("GPL");
 
#define DEVICE_NAME		"chardev"
 
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM = 1;
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 *);
static long chardev_ioctl(struct file *, unsigned int, unsigned long);
 
struct file_operations chardev_fops = {
	.open = chardev_open,
	.release = chardev_release,
	.read = chardev_read,
	.write = chardev_write,
	.unlocked_ioctl = chardev_ioctl,
};
 
static int __init chardev_init(void)
{
	int alloc_ret = 0, cdev_err = 0, minor = 0;
	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;
	}
 
	device_create(chardev_class, NULL, MKDEV(chardev_major, minor),
			NULL, "chardev%d", minor);
 
	return 0;
}
 
static void __exit chardev_exit(void)
{
	int minor = 0;
	dev_t dev = MKDEV(chardev_major, MINOR_BASE);
 
	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)
{
	printk("chardev_open() has been called\n");
	return 0;
}
 
static int chardev_release(struct inode *inode, struct file *file)
{
	printk("chardev_release() has been called\n");
	return 0;
}
 
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("chardev_read() has been called\n");
	return count;
}
 
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("chardev_write() has been called\n");
	return count;
}
 
static struct ioctl_info info;
 
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	printk("chardev_ioctl() has been called\n");
 
	switch (cmd) {
		case IOCTL_READ:
			if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
				return -EFAULT;
			}
			break;
 
		case IOCTL_WRITE:
			if (copy_from_user(&info, (void __user *)arg, sizeof(info))) {
				return -EFAULT;
			}
			break;
 
		case IOCTL_STATUS:
			printk("info : [%ld/%s]\n", info.size, info.buf);
			break;
 
		case IOCTL_RW:
			if (copy_from_user(&info, (void __user *)arg, sizeof(info))) {
				return -EFAULT;
			}
 
			sprintf(info.buf, "%s %s", info.buf, "hello");
			info.size += strlen(" hello");
 
			if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
				return -EFAULT;
			}
			break;
 
		default:
			printk(KERN_WARNING "unsupported command %d\n", cmd);
			return -EFAULT;
	}
 
	return 0;
}
 
module_init(chardev_init);
module_exit(chardev_exit);

 

chardev_ioctl 함수에서 ioctl 오퍼레이션 동작을 정의한다

cmd가 IOCTL_READ일 땐 디바이스 드라이버 내의 데이터를 유저 프로세스로 복사해준다

IOCTL_WRITE에선 유저 프로세스에서 인자로 전달된 데이터를 디바이스 드라이버 내의 데이터로 복사한다

IOCTL_STATUS에선 디바이스 드라이버의 데이터 상태를 출력한다

IOCTL_RW에선 유저 프로세스의 데이터를 디바이스 드라이버 내의 데이터로 복사 한 후,

문자열을 추가하여 다시 유저 프로세스에게 복사해준다

 

이 외의 내용과 빌드 과정은 이 전 포스트와 같으므로 설명을 생략한다

 

 

 

4. 테스트 코드

 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "ioctl.h"
 
int main(int argc, char *argv[])
{
	int fd;
	struct ioctl_info set_info, get_info;
 
	set_info.size = 100;
	strncpy(set_info.buf, "pr0gr4m.0xF", 11);
 
	if ((fd = open("/dev/chardev0", O_RDWR)) < 0)
		perror("open error");
 
	if (ioctl(fd, IOCTL_WRITE, &set_info) < 0)
		perror("IOCTL_WRITE error");
 
	if (ioctl(fd, IOCTL_READ, &get_info) < 0)
		perror("IOCTL_READ error");
 
	printf("[1] get_info : [%ld/%s]\n", get_info.size, get_info.buf);
 
	if (ioctl(fd, IOCTL_STATUS) < 0)
		perror("IOCTL_STATUS error");
 
	if (ioctl(fd, IOCTL_RW, &get_info) < 0)
		perror("IOCTL_RW error");
 
	printf("[2] get_info : [%ld/%s]\n", get_info.size, get_info.buf);
 
	if (close(fd) != 0)
		perror("close error");
 
	return 0;
}

 

ioctl 함수 동작을 테스트한다

 

 

 

5. 실행 결과

 

ioctl 실행 결과
dmesg 내용

 

테스트 결과 info에 원하는 대로 데이터가 작성된 것을 볼 수 있다