ioctl函数详解(参数详解,驱动unlocked_ioctl使用、命令码如何封装)

@ioctl函数详解

一、ioctl函数的原型

在用户空间的函数原型

#include 
int ioctl(int d, int request, ...);    //io的控制,设备的控制
 /***第一个参数d是打开的文件描述符***/
 /***The  second  argument is a device-dependent request code,An ioctl() request 
     has encoded in it whether the argument is an in parameter or out paramter, and 
     the size of the argument argp in bytes.  Macros and defines used in specifying 
      an ioctl() request are located in the file 
     就是说第二个参数已经被编码,这个int(32位)的数编码中有一部分说明了输入输出,还有部分说
     明了第三个参数的大小字节,这个封装成了一个宏在定义了。***/
/***The third argument is an untyped pointer to memory,第三个参数可变参数,可写可不写,
     要写就是一块地址***/

在内核空间的函数原型,定义在了struct file_operations这个操作方法结构中。

struct file_operations {
     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//这个才是ioctl的接口
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  //为了兼容性保留的老的接口
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);                                       
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    ……
  };

二、ioctl函数的第二个参数详解,如何编码

1、asm-generic/ioctl.h中命令码的分析

我们可以根据man手册确认到在内核中有一个定义的文件,可以在linux内核找到这个文件就是include/asm-generic/ioctl.h:
打开这个文件以后我们可以根据注释找到这么一段话:

* ioctl command encoding: 32 bits total, command in lower 16 bits,
* size of the parameter structure in the lower 14 bits of the
* upper 16 bits.
   /*意思就是这个32位命令码,低16位(0bit——15bit)包含了命令,从16位开始(16bit——29bit)的14位
   描述了参数的大小*/
* Encoding the size of the parameter structure in the ioctl request
* is useful for catching programs compiled with old versions
* and to avoid overwriting user space outside the user buffer area.
* The highest 2 bits are reserved for indicating the ``access mode''.  
  /*意思是最高的两位为传输位表明了(读/写)*/

2、内核提供帮助手册Documentation底下有各种使用说明,有一个编码的说明文档Documentation/ioctl/ioctl-decoding.txt

bits    meaning
 31-30  00 - no parameters: uses _IO macro
    10 - read: _IOR
    01 - write: _IOW
    11 - read/write: _IOWR
   //方向,输出模式,读写位
 29-16  size of arguments
    //传递数据的大小
 15-8   ascii character supposedly
    unique to each driver
 //类型,驱动的标识位,一个特殊字符(ASCII)代表不同的一个驱动
 7-0    function #
  //功能
/*************总结:本质上是为了组成一个不同的32位的数************/
So for example 0x82187201 is a read with arg length of 0x218,
character 'r' function 1. Grepping the source reveals this is: 
//举例说明,如果读一个这个数据长度为多少地址多少通过_IOR这个宏合成如下的例子
#define VFAT_IOCTL_READDIR_BOTH         _IOR('r', 1, struct dirent [2])

3、命令码的编码详解

通过例子可以确认到_IOR这个宏是其中一个封装命令码的宏,找到其原型在文件asm-generic/ioctl.h中:

#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)))

可以看到这些封装命令码的宏又都调用了一个宏_IOC,那继续看这个宏是怎么定义的。

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \     //dir(读写)方向左移30位
     ((type) << _IOC_TYPESHIFT) | \    //type类型左移8位
     ((nr)   << _IOC_NRSHIFT) | \      //nr功能左移0位
     ((size) << _IOC_SIZESHIFT))      // size传递数据大小左移16位
 /**通过分析是dir、type、nr、size几个数都左移了一个不知道的宏的位数,通过查找发现这些宏如下,
 所以得到上边每行注释左移位数**/
#define _IOC_NRBITS 8                                                                                   
#define _IOC_TYPEBITS   8
# define _IOC_SIZEBITS  14
#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT+_IOC_NRBITS)  // 0+8 = 8                                                     
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT+_IOC_TYPEBITS) //8+8 = 16
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT+_IOC_SIZEBITS) //16+14 = 30

总结:我们通过分析_IOC这个宏可以发现他做了这么一件事,将一个32位的数拆成了四个部分,分别是dir、type、nr、size,分别如下解释和图示:
bit31~bit30:“区别读写” 区,作用是区分是读取命令还是写入命令;
bit29~bit16:“数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit15~bit8 : “ 魔数” (也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit7~bit0 :“区别序号”区,是区分命令的命令顺序序号。
在这里插入图片描述

4、eg:来封装一个命令码

1、直接封装一个IO命令码,无需读写数据(不需要用户和内核数据传输):

    #define  LED_ON  _IO(‘a’,0)
    #define  LED_OFF _IO(‘a’,1)

2、直接封装一个读结构体的命令码(用户读取内核的数据):

   struct  aa{
      };
   #define READ_STRUCT_aa _IOR(‘z’,0,struct aa)
   /***这里第三个参数大小,size不是说直接sizeof(struct aa),有一个宏_IOC_TYPECHECK,查看原型得到用了宏
   定义_IOC_TYPECHECK(t), ***/
   #define _IOC_TYPECHECK(t) \                                                                             
      ((sizeof(t) == sizeof(t[1]) && \
      sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
      sizeof(t) : __invalid_size_argument_for_IOC)

个人总结:命令码封装之后每一个32的数值代表了一种确定的功能,在一个系统里每一个功能是互不干扰的,比如相机设置的参数(拍照的大小宽高、亮度、分辨率),将这个参数分装成一个命令码给内核,下次拍照就用这个命令码设置的参数。再有所有的开发更过面向用户的,以用户为中心,所以,一个优秀的驱动,不是说全部都将寄存器的设置和用户隔离开,比如你要循环读一个外设的数据,这个读取的周期可能是在4个、5个、6个时钟周期之后,这些都可以通过用户实际需求来更改,而不是在驱动中已经写死一个周期。一个优秀的驱动工程师应该将硬件的功能最大化保留的提供给用户选择,而不是直接屏蔽掉硬件的一部分功能。相当于开了一个和用户360度无死角接触的API。

你可能感兴趣的:(linux)