ioctl(Input/Output control)
ioctl은 하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 오퍼레이션이다.
read(), write()는 데이터의 읽고, 쓰기는 가능하지만
하드웨어의 제어 및 상태 정보까지는 확인할 수 없다.
SPI 통신 속도 설정를 하거나, I2C 슬레이브 어드레스 설정,등의 작업은 read, write만으로는 할 수 없다.
CD-ROM 디바이스 드라이버는 실제 장치에서 디스크를 꺼내도록 지시할 수 있는 ioctl 요청 코드를 제공한다.
위키피디아의 내용을 좀 더 읽어보면 이렇다.
시스템 호출은 표준 커널 기능에 접근하기 위한 편리한 설계이지만, 비표준 하드웨어 주변 장치에 접근하기에는 적합하지 않을 때가 있습니다. 대부분의 하드웨어 주변 장치(또는 장치)는 커널 내에서만 직접적으로 접근할 수 있습니다. 그러나 사용자 코드는 장치와 직접 통신해야 할 수 있습니다. 예를 들어, 관리자는 이더넷 인터페이스의 미디어 유형을 구성할 수 있습니다. 현대 운영 체제는 다양한 장치를 지원하며 이 중 많은 장치가 다양한 기능을 제공합니다. 이러한 기능 중 일부는 커널 디자이너가 예상하지 못한 것일 수 있으며, 이로 인해 커널이 장치 사용을 위한 시스템 호출을 제공하는 것이 어려울 수 있습니다.
이 문제를 해결하기 위해 커널은 확장 가능하도록 설계되었으며, 커널 공간에서 실행되는 디바이스 드라이버라는 추가 모듈을 허용할 수 있습니다. ioctl 인터페이스는 사용자 공간이 디바이스 드라이버와 통신할 수 있는 단일 시스템 호출입니다. 디바이스 드라이버에 대한 요청은 일반적으로 디바이스 핸들과 요청 번호를 기반으로 이 ioctl 시스템 호출에 대한 벡터로 처리됩니다. 기본 커널은 디바이스가 지원하는 기능에 대해 사용자 공간이 아무것도 알지 못해도 디바이스 드라이버에 액세스할 수 있도록 하며, 불필요하게 큰 시스템 호출 컬렉션 없이도 가능합니다.
ioctl 함수의 원형은 다음과 같다.
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
인자 설명은 다음과 같다.
d - open함수에서 얻은 파일 디스크립터(fd)
request - 디바이스에 전달한 명령
이 외에도 개발자의 설정에 따라 추가적인 인자를 생성할 수 있다.
Using macros to generate ioctl command numbers
Linux 헤더 파일 "/usr/include/asm/ioctl.h"에는
ioctl의 커맨드 번호를 작성하는데 사용해야 하는 매크로가 정의되어 있다.
다음과 같은 커맨드 매크로 형태를 사용할 수 있다.
Macro | Description |
_IO(int type, int number) | type, number 값만 전달하는 단순한 ioctl에 사용 |
_IOR(int type, int number, data_type) | 디바이스 드라이버에서 데이터를 읽는 ioctl에 사용 |
_IORW(int type, int number, data_type) | 디바이스 드라이버에서 데이터를 읽고 쓰는 ioctl에 사용 |
_IOW(int type, int number, data_type) | 디바이스 드라이버에서 데이터를 쓰는 ioctl에 사용 |
매크로의 인자 값을 다음과 같은 형태로 구성되어 있다.
Argument | Description |
type | - 장치 드라이버에 고유하게 선택된 8 비트 정수 - fd가 겹치는 다른 드라이버와 충돌하지 않도록 선택해야 함 |
number | - 8 비트 정수 - 드라이버 내에서 드라이버가 서비스하는 서로 다른 종류의 ioctl 명령마다 고유 번호를 선택해야 함 |
data_type | - 클라이언트와 드라이버간에 교환되는 바이트 수를 계산하는 데 사용되는 유형 |
다음과 같이 커맨드 매크로를 정의할 수 있다.
"SET_DATA" - 데이터의 입력, 출력, 쓰기가 가능한 매크로
"GET_DATA" - 데이터의 입력, 출력, 읽기가 가능한 매크로
struct ioctl_info{
unsigned long size;
unsigned int buf[128];
};
#define IOCTL_MAGIC 'G'
#define SET_DATA _IOW(IOCTL_MAGIC, 2 , ioctl_info )
#define GET_DATA _IOR(IOCTL_MAGIC, 3 , ioctl_info )
Example
chardev_ioctl()
사용자 공간(User space)에서 해당 디바이스를 열어서 ioctl()함수를 호출하면
chardev_ioctl() 함수가 호출되며 다음 기능을 처리한다.
- cmd의 값이 "SET_DATA" 일 경우 copy_from_user() 함수를 이용하여 사용자 공간으로 부터 전달받은 데이터를 info 구조체 변수에 복사한다.
- cmd의 값이 "GET_DATA" 일 경우 copy_to_user() 함수를 이용하여 Kernel 영역에 저장된 info 구조체 변수 데이터를 사용자 공간으로 복사한다.
chardev.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/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);
매크로 정의는 다음과 같다.
SET_DATA은 _IOW(입력, 출력, 쓰기)로 설정하고 인자값의 형태는 "struct ioctl_info"로 설정
SET_DATA은 _IOR(입력, 출력, 읽기)로 설정하고 인자값의 형태는 "struct ioctl_info"로 설정
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
Makefile
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
테스트 프로그램은 다음과 같이 동작한다.
- open() 함수를 이용하여 "/dev/chardev0" 파일을 열어 fd값을 얻음
- ioctl() 함수를 이용하여 사용자 공간에 저장된 데이터를 커널 영역에 데이터를 복사하기 위해 fd, "SET_DATA", 커널에 저장할 데이터를 인자 값(&set_info)으로 전달
- ioctl() 함수를 이용하여 커널 영역에 저장된 데이터를 사용가 공간으로 복사하기 위해 fd, "GET_DATA", 사용자 공간에에 데이터를 저장할 변수를 인자 값(&get_info)으로 전달
- printf() 함수를 이용하여 사용자 공간에 복사된 데이터를 출력
- close() 함수를 이용하여 fd를 닫음
test.c
#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;
}
Make & Run
해당 모듈을 빌드하고 테스트 프로그램을 실행하면
다음과 같이 전달된 데이터들이 정상적으로 복사되는 것을 확인할 수 있다.
https://www.lazenca.net/pages/viewpage.action?pageId=23789739
https://en.wikipedia.org/wiki/Ioctl
'background > linux kernel' 카테고리의 다른 글
Linux Kernel Debugging (with QEMU) (0) | 2024.04.02 |
---|---|
[Lazenca][Development of Kernel Module] 04.Creating a kernel module to privilege escalation (0) | 2024.03.29 |
[Lazenca][Development of Kernel Module] 02.Character Device Drivers (0) | 2024.03.28 |
[Linux Kernel] Device Driver (0) | 2024.03.28 |
[Lazenca][Development of Kernel Module] 01.Hello world! (0) | 2024.03.28 |