在嵌入式中经常碰到魔数,比如:
#define PLATDRV_MAGIC 0X60
#define LED_OFF _IO(PLATDRV_MAGIC,0X18)
#define LED_ON _IO(PLATDRV_MAGIC,0X19)
然后我们会调用ioctl(fd[i],LED_OFF)或者ioctl(fd[i],LED_ON)
网上资料:_IO(魔数,基数)
_IOR(魔数,基数,变量型)
_IOW(魔数,基数,变量型)
_IOWR(魔数,基数,变量型)
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
再看 _IOC() 的定义://include/asm/ioctl.h
#define _IOC(dir,type,nr,size)
(((dir)<< _IOC_DIRSHIFT)|((type)<<_IOC_TYPESHIFT)|((nr)<<_IOC_NRSHIFT)|((size) << _IOC_SIZESHIFT))
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //16+14=30
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //0+8=8
#define _IOC_NRSHIFT 0
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)//8+8=16
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8 //
#define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits
由上面的定义,往上推得到:
_IOC_TYPESHIFT = 8
_IOC_SIZESHIFT = 16
_IOC_DIRSHIFT = 30
所以#define _IOC(dir,type,nr,size) (((dir)<<30)|((type)<<8)|((nr)<<0)((size)<<16))
cmd的大小为 32位,共分 4 个域:
bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。//dirbit29~bit16 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。//size
bit15~bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。//type
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。//nr
所以#define LED_OFF _IO(PLATDRV_MAGIC,0X18)就是_IOC(_IOC_NONE,PLATDRV_MAGIC,0X18,0)
其中_IOC_NONE为0 ,也即是:
((0<<30)|(0x60<<8)|(0x18<<0)|(0<<16))
现在我们来看ioctl(fd[i],LED_OFF)
ioctl的原型:int ioctl(int fd, int cmd, …)
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是
一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。
怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl
中命令码是唯一联系用户程序命令和驱动程序支持的途径。
在Linux核心中是这样定义一个命令码的:
| 设备类型 | 数据尺寸 | 魔数 | 序列号 |
| 2 bit | 14 bit | 8 bit | 8bit |
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。