• Linux之ioctl20160705


    ioctl(fdAcodec, ACODEC_GET_ADCL_VOL, &vol_ctrl)//从内核驱动中获取或者设置数据
    //内核驱动中也使用ACODEC_GET_ADCL_VOL进行case,因为这个cmd中的魔数和基数都是系统函数IOWR定义的,内核驱动就会知道。
    #define ACODEC_GET_ADCL_VOL //ACODEC_GET_ADCL_VOL这个CMD等同于_IOWR(... ),使用该函数定义CMD _IOWR(IOC_TYPE_ACODEC, IOC_NR_GET_ADCL_VOL, ACODEC_VOL_CTRL)
    _IOWR(IOC_TYPE_ACODEC, IOC_NR_GET_DACL_VOL, ACODEC_VOL_CTRL)//IOC_TYPE_ACODEC 魔数 IOC_NR_GET_DACL_VOL基数 ...
    //ACODEC_VOL_CTRL变量型,使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型
    int ioctl(int fd, int cmd, void *data)
    _IO (魔数, 基数);
    _IOR (魔数, 基数, 变量型)
    _IOW (魔数, 基数, 变量型)
    _IOWR (魔数, 基数,变量型 )
    设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理
    基数用于区别各种命令。通常,从 0开始递增
    变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型

    ioctl与内核交换数据
    1. 前言

    使用ioctl系统调用是用户空间向内核交换数据的常用方法之一,从ioctl这个名称上看,本意是针对I/O设备进 行的控制操作,但实际并不限制是真正的I/O设备,可以是任何一个内核设备即可。

    2. 基本过程

    在内核空间中ioctl是很多内核操作结构的一个成员函数,如文件操作结构struct file_operations(include/linux/fs.h)、协议操作结构struct proto_ops(include/linux/net.h)等、tty操作结构struct tty_driver(include/linux/tty_driver.h)等,而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备, 如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上调用ioctl(2)来向内核 交换数据。

    3. ioctl(2)

    ioctl(2)函数的基本使用格式为:
    int ioctl(int fd, int cmd, void *data)
    第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET 是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始 位置指针,
    cmd命令参数是个32位整数,分为四部分:
    dir(2b) size(14b) type(8b) nr(8b)
    详 细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type, nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义
    本文cmd定义为:
    #define NEWCHAR_IOC_MAGIC 'M'
    #define NEWCHAR_SET _IO(NEWCHAR_IOC_MAGIC, 0)
    #define NEWCHAR_GET _IO(NEWCHAR_IOC_MAGIC, 1)
    #define NEWCHAR_IOC_MAXNR 1

    要定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行 ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可, 不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。

    4. 内核设备

    为进行ioctl操作最通常是使用字符设备来进行,当然定义其他类型的设备也可以。在用户空间,可使用mknod命令建立一个 字符类型设备文件,假设该设备的主设备号为123,次设备号为0:
    mknode /dev/newchar c 123 0
    如果是编程的 话,可以用mknode(2)函数来建立设备文件。

    建立设备文件后再将该设备的内核模块文件插入内核,就可以使用open(2)打开 /dev/newchar文件,然后调用ioctl(2)来传递数据,最后用close(2)关闭设备。而如果内核中还没有插入该设备的模 块,open(2)时就会失败。

    由于内核内存空间和用户内存空间不同,要将内核数据拷贝到用户空间,要使用专用拷贝函数 copy_to_user();要将用户空间数据拷贝到内核,要使用copy_from_user()。
    要最简单实现以上功能,内核模块只需要实 现设备的open, ioctl和release三个函数即可,
    下面介绍程序片断:
    static int newchar_ioctl(struct inode *inode, struct file *filep,
    unsigned int cmd, unsigned long arg);
    static int newchar_open(struct inode *inode, struct file *filep);
    static int newchar_release(struct inode *inode, struct file *filep);
    // 定义文件操作结构,结构中其他元素为空
    struct file_operations newchar_fops =
    {
    owner: THIS_MODULE,
    ioctl: newchar_ioctl,
    open: newchar_open,
    release: newchar_release,
    };
    // 定义要传输的数据块结构
    struct newchar{
    int a;
    int b;
    };
    #define MAJOR_DEV_NUM 123
    #define DEVICE_NAME "newchar"

    打开设备,非常简单,就是增加 模块计数器,防止在打开设备的情况下删除模块,
    当然想搞得复杂的话可进行各种限制检查,如只允许指定的用户打开等:
    static int newchar_open(struct inode *inode, struct file *filep)
    {
    MOD_INC_USE_COUNT;
    return 0;
    }

    关闭设备,也很简单,减模块计数器:
    static int newchar_release(struct inode *inode, struct file *filep)
    {
    MOD_DEC_USE_COUNT;
    return 0;
    }

    进行ioctl调用的基本处理函数
    static int newchar_ioctl(struct inode *inode, struct file *filep,
    unsigned int cmd, unsigned long arg)
    {
    int ret;
    // 首先检查cmd是否合法
    if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
    if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
    // 错误情况下的缺省返回值
    ret = EINVAL;
    switch(cmd)
    {
    case KNEWCHAR_SET:
    // 设置操作,将数据从用户空间拷贝到内核空间
    {
    struct newchar nc;
    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
    ret = do_set_newchar(&nc);
    }
    break;
    case KNEWCHAR_GET:
    // GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部
    // 数据后重新写回缓冲区
    // 当然也可以根据具体情况什么也不传入直接向内核获取数据
    {
    struct newchar nc;
    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
    ret = do_get_newchar(&nc);
    if(ret == 0){
    if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
    return -EFAULT;
    }
    }
    break;
    }
    return ret;
    }
    模块初始化函数,登记字符设备
    static int __init _init(void)
    {
    int result;
    // 登记该字符设备,这是2.4以前的基本方法,到2.6后有了些变化,
    // 是使用MKDEV和cdev_init()来进行,本文还是按老方法
    result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME, &newchar_fops);
    if (result < 0) {
    printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar ");
    return result;
    }
    return 0;
    }
    模块退出函数,登出字符设备
    static void __exit _cleanup(void)
    {
    int result;
    result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);
    if (result < 0)
    printk(__FUNCTION__ ": failed unregister character device for /dev/newchar ");
    return;
    }
    module_init(_init);
    module_exit(_cleanup);

    5. 结论

    用ioctl()在用户空间和内核空间传递数据是最常用方法之一,比较简单方便,而且可以在同一个ioctl中对不同的命令传送不 同的数据结构,本文只是为描述方便而在不同命令中使用了相同的数据结构。


    cmd
    在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值。cmd除了可区别数字外,还包含有助于处理的几种相应信息。 cmd的大小为 32位,共分 4 个域:
    bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
    bit29~bit15 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
    bit20~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
    bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
    像 命令码中的 “区分读写区” 里的值可能是 _IOC_NONE (0值)表示无数据传输,_IOC_READ (读), _IOC_WRITE (写) , _IOC_READ|_IOC_WRITE (双向)。
    内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助生成上面的 cmd 。下面分析 _IO() 的实现,其它的类似:< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

    在 asm-generic/ioctl.h 里可以看到 _IO() 的定义:

    #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

    再看 _IOC() 的定义:

    #define _IOC(dir,type,nr,size)
    (((dir) << _IOC_DIRSHIFT) |
    ((type) << _IOC_TYPESHIFT) |
    ((nr) << _IOC_NRSHIFT) |
    ((size) << _IOC_SIZESHIFT))

    可见,_IO() 的最后结果由 _IOC() 中的 4 个参数移位组合而成。
    再看 _IOC_DIRSHIT 的定义:

    #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)

    _IOC_SIZESHIFT 的定义:

    #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)

    _IOC_TYPESHIF 的定义:

    #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)

    _IOC_NRSHIFT 的定义:

    #define _IOC_NRSHIFT 0

    _IOC_NRBITS 的定义:

    #define _IOC_NRBITS 8

    _IOC_TYPEBITS 的定义:

    #define _IOC_TYPEBITS 8

    由上面的定义,往上推得到:

    引 用

    _IOC_TYPESHIFT = 8

    _IOC_SIZESHIFT = 16

    _IOC_DIRSHIFT = 30

    所以,(dir) << _IOC_DIRSHIFT) 表是 dir 往左移 30 位,即移到 bit31~bit30 两位上,得到方向(读写)的属性;
    (size) << _IOC_SIZESHIFT) 位左移 16 位得到“数据大小”区;
    (type) << _IOC_TYPESHIFT) 左 移 8位得到"魔数区" ;
    (nr) << _IOC_NRSHIFT) 左移 0 位( bit7~bit0) 。
    这样,就得到了 _IO() 的宏值。

    这几个宏的使用格式为:
    ?_IO (魔数, 基数);
    ?_IOR (魔数, 基数, 变量型)
    ?_IOW (魔数, 基数, 变量型)
    ?_IOWR (魔数, 基数,变量型 )

    魔数 (magic number)
    魔数范围为 0~255 。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动 程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也是可以使用其他设备驱动程序已用过的魔数。
    基(序列号)数
    基数用于区别各种命令。通常,从 0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时 使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。设备驱动程序想要从命令中获取该基数,就使用下面的宏:
    _IOC_NR (cmd)
    通常,switch 中的 case 值使用的是命令的本身。
    变量型
    变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型,原因是在使用宏创建命令,已经包含了 sizeof() 编译命令。比如 _IOR() 宏的定义是:

    引用

    #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

    而 _IOC_TYPECHECK() 的定义正是:

    引用

    #define _IOC_TYPECHECK(t) (sizeof(t))

    设备驱动程序想要从传送的命令获取相应的值,就要使用下列宏函数:
    _IOC_SIZE(cmd)

    _IO 宏

    该宏函数没有可传送的变量,只是用于传送命令。例如如下约定:

    引用

    #define TEST_DRV_RESET _IO ('Q', 0)

    此时,省略由应用程序传送的 arg 变量或者代入 0 。在应用程序中使用该宏时,比如:

    ioctl (dev, TEST_DEV_RESET, 0) 或者 ioctl (dev, TEST_DRV_RESET) 。
    这是因为变量的有效因素是可变因素。只作为命令使用时,没有必要判 断出设备上数据的输出或输入。因此,设备驱动程序没有必要执行设备文件大开选项的相关处理。

    _IOR 宏
    该函数用 于创建从设备读取数据的命令,例如可如下约定:

    引用

    #define TEST_DEV_READ _IRQ('Q', 1, int)

    这说明应用程序从设备读取数据的大小为 int 。下面宏用于判断传送到设备驱动程序的 cmd 命令的读写状态:
    _IOC_DIR (cmd)
    运行该宏时,返回值的类型 如下:
    ?_IOC_NONE : 无属性
    ?_IOC_READ : 可读属性
    ?_IOC_WRITE : 可写属性
    ?_IOC_READ | _IOC_WRITE : 可读,可写属性

    使用该命令时,应用程序的 ioctl() 的 arg 变量值指定设备驱动程序上读取数据时的缓存(结构体)地址。
    _IOW 宏
    用于创建设 备上写入数据的命令,其余内容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入数据时的缓存(结构体)地址。
    _IOWR 宏
    用于创建设备上读写数据的命令。其余内 容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入或读取数据时的缓存 (结构体) 地址。
    _IOR() , _IOW(), IORW() 的定义:
    #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)))

  • 相关阅读:
    蛙蛙推荐:五分钟搞定网站前端性能优化
    蛙蛙推荐:AngularJS学习笔记
    蛙蛙推荐:如何实时监控MySql状态
    这6种思维,学会了你就打败了95%文案!zz
    10分钟,解决卖点没创意的难题zz
    总感觉自己工作沟通想问题时没有逻辑,这可怎么办?| 极简逻辑指南
    「零秒思考」是个神话,不过这款笔记术你值得拥有zz
    关于提高沟通能力的书单 | 章鱼书单zz
    日常沟通的 3 种模式zz
    关于提高沟通能力的书单zz
  • 原文地址:https://www.cnblogs.com/yuweifeng/p/5644528.html
Copyright © 2020-2023  润新知