ioctl函数实用性总结(适用初学者)

1  ioctl为什么会出现

        (1)虽然在文件操作结构体"struct file_operations"中有很多对应的设备操作函数,但是有些命令是实在找不到对应的操作函数。如CD-ROM的驱动,想要一个弹出光驱的操作,这种操作并不是所有的字符设备都需要的,所以文件操作结构体也不会有对应的函数操作。

        (2)大部分驱动除了需要具备读写设备的能力之外还需要具备对硬件控制的能力,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情

        (3)编写应用的程序猿要做的事只需要通过驱动开发者在驱动相关的.H文件中提供的命令码,命令码用文字进行了说明,因为有些驱动是加密的,里面包含了ioctl的操作,不会给你看源码,但是会提供H文件里面包含了该驱动所有命令码的解释与操作结果说明,这样一来你只要理解命令码即可,应用程序员可快速开发上手

        总结:出于这样的原因,ioctl就有它的用处了————一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作步骤,将他们综合起来完成一个任务,而不是单一的某个write/read操作,通过应用层传入的命令来调用相应的操作。


2  ioctl函数原型

    2.1  在应用程序中,使用ioctl系统调用来控制设备,原型如下

  1. int ioctl(int fd,unsigned long cmd,...);  
  2. /* 
  3. fd:文件描述符 
  4. cmd:控制命令 
  5. ...:可选参数:插入*argp,具体内容依赖于cmd 
  6. */ 

    2.2  在驱动程序中,使用ioctl用来操作硬件,原型如下

  1. int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);  
  2. /* 
  3. inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。 
  4. cmd 由用户空间直接不经修改的传递给驱动程序 
  5. arg 可选。 
  6. */          

        总结:当我们把驱动加载到内核的时候,驱动会调用内核的注册函数,将驱动的file_operations注册,这个时候,我们通过应用层打开该驱动的设备文件fd,调用ioctl时,会自动挂接到指定驱动中的ioctl函数,完成指定的硬件操作。

3  ioctl函数几个重要参数

    3.1  cmd为什么会变得如此复杂

(1)所谓的复杂命令无非也是一个32bit的整数,比如用一个最简单的方式定义一个命令也是可以的  
                                      #define TEST_CLEAR 155
         如上但内核开发人员发现这样有点不对劲。如果有两个不同的设备,但它们的ioctl的cmd却一样的
                                       #define TEST_OPEN 155 

        当程序代码量,功能,模块太大时,哪天应用开发者没有注意,在操作某一个功能时,本来要打开A设备,操作A硬件,不小心写错了代码打开了B设备,并且调用ioctl,这样就完蛋了。因为这个文件里面同样有cmd对应实现,你就无故的影响了B驱动的正常使用,为了防止这样的事情发生,内核对cmd又有了新的定义,规定了cmd都应该不一样。

使用复杂cmd后代码:

                        #define TEST_MAGIC 'x' //定义幻数
                        #define TEST_MAX_NR 3 //定义命令的最大序数

                        #define TEST_CLEAR _IO(TEST_MAGIC, 1)
                        #define TEST_OFFSET _IO(TEST_MAGIC, 2)
                        #define TEST_KBUF _IO(TEST_MAGIC, 3) 

   

   3.2 cmd的组成

         前言:  在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

            在Linux核心中是这样定义一个命令码的:

            | 设备类型 | 序 列 号 | 方 向  | 数据 尺寸 |

            |  8    bit   |  8 bit     | 2 bit  |  8~14 bit |

    1) 幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有一个文档给出一些推荐的或者已经被使用的幻数。
    2)序数:用这个数来给自己的命令编号,占8bit(_IOC_NRBITS),我的程序从1开始排序。

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

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

        总结:这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

        如下:

  1. //nr为序号,命令最大个数,datatype为数据类型,如int  
  2. _IO(type, nr ) //没有参数的命令  
  3. _IOR(type, nr, datatype) //从驱动中读数据  
  4. _IOW(type, nr, datatype) //写数据到驱动  
  5. _IOWR(type,nr, datatype) //双向传送  
  6.   
  7.   定义命令例子:  
  8. #define MEM_IOC_MAGIC 'm' //定义类型  
  9. #define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)  
  10. #define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)  

    cmd最后达到的目的:既然这么费劲定义了命令,当然要检验命令是否有效,所有我们在驱动中就可以添加验证代码,判断命令是否为该驱动的命令

                 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL;
                 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL;

3.3 ioctl中的arg之传参

    前言:  应用层的ioctl的第三个参数是"...",这个跟printf的"..."可不一样,printf中是意味这你可以传任意个数的参数,而ioctl最多也只能传一个,"..."的意思是让内核不要检查这个参数的类型。也就是说,从用户层可以传入任何参数,只要你传入的个数是1.

一般会有两种的传参方法:

    3.3.1  整数,那可是省力又省心,直接使用就可以了。

        如下代码:作为运算参数 
                应用层   ioctl(fd, TEST_OFFSET, 10);
                驱动层    switch(cmd){
                                        case TEST_OFFSET: 
                                        filp->f_pos += (int)arg;        
                                }

    3.3.2  指针,通过指针的就传什么类型都可以了,当然用起来就比较烦。

        (1)考虑到参数不可能永远只是一个正数这么简单,如果要传多一点的东西,譬如是结构体,那就得用上指针了。

        (2)一讲到从应用程序传来的指针,就得想起我邪恶的传入了非法指针的例子。所以,驱动程序中任何与应用层打交道的指针,都得先检验指针的安全性。

      代码如下:因为指针是从用户程序传来,所以必须检查安全性

       驱动代码              if(copy_from_user(&val, (struct ioctl_data *)arg, sizeof(struct ioctl_data))){
                                    ret = - EFAULT;
                                    goto RET;

                                    }


       应用代码              struct ioctl_data my_data= {

                                   .size = 10,
                                   .buf = "123456789"
                                    };

                                    ioctl(fd, TEST_KBUF, &my_data);






猜你喜欢

转载自blog.csdn.net/qq_40334837/article/details/79976511