高级字符驱动程序ioctl

高级字符驱动程序ioctl

Linux内核一般会自动地过滤到一些不合法的cmd定义,比如你自己定义的 2

1.ioctl命令格式

幻数 序数 数据传输方向 数据大小
8 bit 8 bit 2 bit 14bit

内核中两个文档有说明ioctl-decoding.txtioctl-number.txt

1.1 幻数

是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有文档ioctl-number.txt给出一些推荐的或者已经被使用的幻数 。

1.2 序数

用这个数来给自己的命令编号,占8bit(_IOC_NRBITS)。

1.3 数据传输方向

占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。

  • _IOC_NONE:值为0,无数据传输。
  • _IOC_READ:值为1,从设备驱动读取数据。
  • _IOC_WRITE:值为2,往设备驱动写入数据。
  • _IOC_READ|_IOC_WRITE:双向数据传输。

1.4 数据大小

与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)

2.定义命令

内核提供了一些宏来帮助定义命令

//nr为序号,type为数据类型,如int
#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))

//获取类型大小
#define _IOC_TYPECHECK(t) (sizeof(t))   
//没有参数的命令
#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))

定义命令demo

#define MY_IOC_MAGIC    'm' //定义类型
#define MY_IOCSET       _IOW(MY_IOC_MAGIC, 0, int)
#define MY_IOCGQSET     _IOR(MY_IOC_MAGIC, 1, int)

3.提取命令中的信息

#define _IOC_NRBITS 8
#define _IOC_TYPEBITS   8

#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS  14
#endif

#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS   2
#endif

#define _IOC_NRMASK     ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK   ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK   ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK    ((1 << _IOC_DIRBITS)-1)

#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT+_IOC_SIZEBITS)

//从命令中提取方向
#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)

4.应用实例

4.1 驱动程序

ioctl_test.h

#ifndef IOCTL_TEST_H_ 
#define IOCTL_TEST_H_ 
//定义幻数 
#define MY_IOC_MAGIC '%' 

//数据清零 
#define MY_IOC_CLEAR_DATA   _IO(MY_IOC_MAGIC, 0) 
//读取数据
#define MY_IOC_GET_DATA     _IOR(MY_IOC_MAGIC, 1, int) 
//查询数据
#define MY_IOC_QUERY_DATA   _IO(MY_IOC_MAGIC, 2) 
//设置数据(通过指针) 
#define MY_IOC_SET_DATA     _IOW(MY_IOC_MAGIC, 3, int) 
//设置数据(通过直接引用参数值) 
#define MY_IOC_TELL_DATA    _IO(MY_IOC_MAGIC, 4) 

#endif

ioctl_test.c

#include <linux/module.h> 
#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/fs.h> 
#include <linux/errno.h> 
#include <linux/cdev.h> 
#include <asm/uaccess.h> 
#include <linux/device.h> 
#include "ioctl_test.h" 

//设备私有数据 
struct ioctl_dev { 
    int data; 
    struct cdev cdev; 
} dev; 

//最大IOCTL命令号 
#define MY_IOC_MAXNR 4 

//默认自动分配主设备号 
#define IOCTL_DEV_MAJOR 0 

static int ioctl_major = IOCTL_DEV_MAJOR; 

//加载模块传递参数
module_param(ioctl_major, int, S_IRUGO); 

struct class *ioctl_class; 
struct cdev cdev; 

long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 
{ 
    int err = 0, retval = 0; 

    //判断命令幻数是否匹配 
    if (_IOC_TYPE(cmd) != MY_IOC_MAGIC) 
        return -ENOTTY; 

    //判断命令序号是否非法 
    if (_IOC_NR(cmd) > MY_IOC_MAXNR) 
        return -ENOTTY; 

    //判断空间是否可访问 
    /* VERIFY_WRITE 是 VERIFY_READ 超集 */ 
    if (_IOC_DIR(cmd) & _IOC_READ) 
        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE) 
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); 
    if (err) 
        return -EFAULT; 

    switch (cmd) 
    { 
    case MY_IOC_CLEAR_DATA://数据清零 
        dev.data = 0; 
        printk("MY_IOC_CLEAR_DATA data: 0\n"); 
        break; 
    case MY_IOC_GET_DATA://读取数据
        retval = __put_user(dev.data, (int __user *)arg); 
        printk("MY_IOC_GET_DATA data: %d\n", dev.data); 
        break; 
    case MY_IOC_QUERY_DATA://查询数据
        printk("MY_IOC_QUERY_DATA data: %d\n", dev.data); 
        retval = dev.data; 
        break; 
    case MY_IOC_SET_DATA://设置数据(通过指针)
        retval = __get_user(dev.data, (int __user *)arg); 
        printk("MY_IOC_SET_DATA data: %d\n", dev.data); 
        break; 
    case MY_IOC_TELL_DATA://设置数据(通过直接引用参数值) 
        dev.data = arg; 
        printk("MY_IOC_TELL_DATA data: %ld\n", arg); 
        break; 
    default: 
        retval = -EINVAL; 
        break; 
    } 

    return retval; 
} 
static const struct file_operations my_fops = { 
    .owner = THIS_MODULE, 
    .unlocked_ioctl = my_ioctl,//linux 2.6.36内核之后unlocked_ioctl取代ioctl 
}; 

static int my_init(void) 
{ 
    //设备号 
    dev_t devno = MKDEV(ioctl_major, 0); 
    int result; 

    if (ioctl_major)
    {
        //静态分配设备号 
        result = register_chrdev_region(devno, 1, "ioctl"); 
    }
    else 
    {
        //动态分配设备号 
        result = alloc_chrdev_region(&devno, 0, 1, "ioctl"); 
        ioctl_major = MAJOR(devno); 
    } 
    if (result < 0) 
        return result; 

    //用于udev/mdev自动创建节点 
    ioctl_class = class_create(THIS_MODULE, "ioctl"); 
    device_create(ioctl_class, NULL, devno, NULL, "ioctl"); 

    //静态添加cdev 
    cdev_init(&cdev, &my_fops); 
    cdev.owner = THIS_MODULE; 
    cdev_add(&cdev, devno, 1); 
    printk("=====my_init success=====\n"); return 0; 
} 

static void my_exit(void) 
{ 
    cdev_del(&cdev); 
    device_destroy(ioctl_class, MKDEV(ioctl_major, 0)); 
    class_destroy(ioctl_class); 
    unregister_chrdev_region(MKDEV(ioctl_major, 0), 1); 
    printk("=====my_exit success=====\n"); 
} 

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL"); 

4.2 测试程序

#include <stdio.h> 
#include <fcntl.h> 
#include <sys/ioctl.h> 
#include "ioctl_test.h"

int main(void) 
{ 
    int fd; 
    int data;

    //打开设备 
    fd = open("/dev/ioctl", O_RDWR); 
    if (fd == -1) 
    { 
        printf("open ioctl device failed!\n"); 
        return -1; 
    } 

    //数据清零 
    ioctl(fd, MY_IOC_CLEAR_DATA); 

    //查询数据
    data = ioctl(fd, MY_IOC_QUERY_DATA); 
    printf("app data %d\n", data); 

    //设置数据(通过直接引用参数值) 
    data = 100; 
    ioctl(fd, MY_IOC_TELL_DATA, data); 

    //读取数据
    ioctl(fd, MY_IOC_GET_DATA, &data); 

    //设置数据
    data = 122; 
    ioctl(fd, MY_IOC_SET_DATA, &data); 
    return 0; 
}

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/81239274