일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- kmalloc
- vm_area_struct
- slab
- slowpath
- blk-mq
- pmap
- proc
- multiqueue
- page
- Kernel
- slub
- Linux
- NDK
- kafka
- spinlock
- commit
- Android
- strex
- devicedriver
- allocator
- buddy_system
- lruvec
- Apache
- mm_struct
- memory
- fastpath
- Network
- BLOCK
- vmalloc
- 카프카
- Today
- Total
Art of Pr0gr4m
[Linux Kernel 5] Character Device Driver IOCTL 본문
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. 실행 결과
테스트 결과 info에 원하는 대로 데이터가 작성된 것을 볼 수 있다
'IT > Linux Kernel' 카테고리의 다른 글
[Linux Kernel 5] proc & seq_file (0) | 2020.05.02 |
---|---|
[Linux Kernel 5] Crypto Device Driver (0) | 2020.04.30 |
[Linux Kernel 5] Character Device Driver (3) | 2020.04.30 |
[Linux Kernel 5] Module Programming Basic (0) | 2020.04.25 |
[Linux Kernel 5] system call 추가하기 (0) | 2020.04.24 |