ioctl이란 하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 operation이다. read(), write() 함수만으로는 해결되지 않는 제어에 사용된다.
#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...);
fd
: open() 함수에서 얻은 file descriptorcmd
: device에 전달할 명령어
cmd
cmd는 응용 프로그램이 device driver에게 요구한 처리를 구별하기 위한 구별 값이다. 비트 구성은 다음과 같다.
number
: 8 bits, 명령을 구분하는 명령어의 순차번호이다.type
: 8 bits, 다른 device driver의 ioctl 명령과 구분하기 위한 magic number이다.size
: 14bits, 매개변수 arg를 통해 전달되는 메모리의 크기direction
: 2bits, 자료를 전송할 때 전송 방향을 가리킨다. 예를 들어,_IOC_READ
는 device에서 자료를 읽는다는 뜻이다.
Macro Function
cmd는 보통 macro 함수를 이용하여 구성한다.
_IO(int type, int number); // type, number 값만 전달.
_IOR(int type, int number, data_type); // device driver에서 data를 read.
_IOW(int type, int number, data_type); // device driver에서 data를 write.
_IORW(int type, int number ,data_type); // device driver에서 data를 read/write.
type
: 위에서 말한type
과 동일.number
: 위에서 말한number
와 동일.data_type
: 변수형
예를 들어 cmd를 구성해보자.
struct ioctl_info{
unsigned long size;
unsigned int buf[128];
};
#DEFINE IOCTL_MAGIC 'G' // 보통 magic number로 영문자 A~Z or a~z를 사용한다.
#DEFINE SET_DATA _IOW(IOCTL_MAGIC, 2, ioctl_info)
#DEFINE GET_DATA _IOR(IOCTL_MAGIC, 3, ioctl_info)
SET_DATA
의 경우, 데이터의 입력, 출력, 쓰기가 가능한 매크로이며 GET_DATA
는 데이터의 입력, 출력, 읽기가 가능한 매크로이다.
전달된 매크로의 필드 값을 쉽게 추출하기 위한 macro 함수도 있다.
_IOC_NR(); // number
_IOC_TYPE(); // type
_IOC_SIZE(); // size
_IOC_DIR(); // direction
Make device driver for using ioctl
#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/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include "chardev.h"
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_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 s_chardev_fops = {
.open = chardev_open,
.release = chardev_release,
.read = chardev_read,
.write = chardev_write,
.unlocked_ioctl = chardev_ioctl,
};
static int chardev_init(void)
{
int alloc_ret = 0;
int cdev_err = 0;
int minor = 0;
dev_t dev;
printk("The chardev_init() function has been called.");
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_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, &s_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", alloc_ret);
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 chardev_exit(void)
{
int minor = 0;
dev_t dev = MKDEV(chardev_major, MINOR_BASE);
printk("The chardev_exit() function has been called.");
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("The chardev_open() function has been called.");
return 0;
}
static int chardev_release(struct inode *inode, struct file *file)
{
printk("The chardev_close() function has been called.");
return 0;
}
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("The chardev_write() function has been called.");
return count;
}
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("The chardev_read() function has been called.");
return count;
}
static struct ioctl_info info;
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("The chardev_ioctl() function has been called.");
switch (cmd) {
case SET_DATA:
printk("SET_DATA\n");
if (copy_from_user(&info, (void __user *)arg, sizeof(info))) {
return -EFAULT;
}
printk("info.size : %ld, info.buf : %s",info.size, info.buf);
break;
case GET_DATA:
printk("GET_DATA\n");
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.h
#ifndef CHAR_DEV_H_
#define CHAR_DEV_H_
#include <linux/ioctl.h>
struct ioctl_info{
unsigned long size;
char buf[128];
};
#define IOCTL_MAGIC 'G'
#define SET_DATA _IOW(IOCTL_MAGIC, 2 ,struct ioctl_info)
#define GET_DATA _IOR(IOCTL_MAGIC, 3 ,struct ioctl_info)
#endif
obj-m := chardev.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
이전에 만들었던 chardev에 ioctl 함수를 추가하였다.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "chardev.h"
int main()
{
int fd;
struct ioctl_info set_info;
struct ioctl_info get_info;
set_info.size = 100;
strncpy(set_info.buf,"lazenca.0x0",11);
if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
printf("Cannot open /dev/chardev0. Try again later.\n");
}
if (ioctl(fd, SET_DATA, &set_info) < 0){
printf("Error : SET_DATA.\n");
}
if (ioctl(fd, GET_DATA, &get_info) < 0){
printf("Error : SET_DATA.\n");
}
printf("get_info.size : %ld, get_info.buf : %s\n", get_info.size, get_info.buf);
if (close(fd) != 0){
printf("Cannot close.\n");
}
return 0;
}
테스트 프로그램이다. ioctl 함수를 이용하여 사용자 공간에 저장된 data를 kernel 영역에 복사하고, 다시 kernel 영역의 data를 사용자 공간으로 복사한다.
baek@ubuntu:~/Kernel/ioctl$ dmesg | tail
[ 291.879318] The chardev_init() function has been called.
[ 348.609851] The chardev_open() function has been called.
[ 348.609857] The chardev_ioctl() function has been called.
[ 348.609857] SET_DATA
[ 348.609859] info.size : 100, info.buf : lazenca.0x0
[ 348.609860] The chardev_ioctl() function has been called.
[ 348.609861] GET_DATA
data들이 정상적으로 복사되는 것을 알 수 있다.
참고문헌
[1] https://www.lazenca.net/pages/viewpage.action?pageId=23789739
[3] http://blog.naver.com/luis8282?Redirect=Log&logNo=20086600288
'Linux Kernel' 카테고리의 다른 글
Device Driver - Character (0) | 2020.04.04 |
---|---|
Kernel Module (0) | 2020.04.04 |