ioctl이란 하드웨어의 제어와 상태 정보를 얻기 위해 제공되는 operation이다. read(), write() 함수만으로는 해결되지 않는 제어에 사용된다.

#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...);
  • fd : open() 함수에서 얻은 file descriptor
  • cmd : device에 전달할 명령어

cmd

cmd는 응용 프로그램이 device driver에게 요구한 처리를 구별하기 위한 구별 값이다. 비트 구성은 다음과 같다.

image

  • 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

[2] http://blog.naver.com/PostView.nhn?blogId=jyh3211&logNo=40131890810&redirect=Dlog&widgetTypeCall=true

[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

+ Recent posts