1. 问题背景
最近在写到一个简单的驱动程序需要用到ioctl提供给应用程序使用,本着对ioctl的cmd的简单理解(就是个数值,只要应用中调ioctl时传下来的cmd和驱动中xx_ioctl中的cmd能匹配上就行),就随意的将我需要用到的三个命令定义为了0,1,2。
在使用过程中,在应用程序初始化时open设备后,调用ioctl对驱动中进行了配置,ioctl能正确调用到驱动中的xxx_ioctl,本以为没啥问题,但是在应用程序中后续经过了“很多年”以后,应用中的某个地方经过了一系列的函数调用,再次调用到了ioctl向驱动下发前面定义个三个命令之一,但驱动中xxx_ioctl()函数并没有进入,经过排查排除了文件描述符fd不对的可能性。那问题很可能出现在cmd上了。
2.知识点学习
经过对《Linux设备驱动程序》第6章第1节-ioctl的学习,找到了一点答案。
linux内核的期望是不管谁的ioctl的每个cmd在系统范围内最好是唯一的,这样才能避免不可预知的错误的调用匹配到你我都用的cmd的编号,导致产生未知的影响。无数“教训”告诉我们这是容易出现的。
因此内核就有了一套创建ioctl命令号的规范,用以避免上面的问题。简而言之的话,就是将命令号进行了分段,各个段有了一定的意义,这样各段一组合就增加了命令号表示范围,不仅起到了互相区分的作用(想想身份证号编法),还使得ioctl的功能变得多姿多彩。具体就是一个命令号由4段组成:
type(幻数) number direction size
各段的详细解释这里就不提了,直接贴出书中介绍:
当然怎么去定义比较方便呢?在include/asm/ioctl.h中(不同版本,不同架构该文件的位置可能不一定,但相信你总能找到它)中定义了相关的宏定义,以及方便大家进行ioctl命令号定义的宏,如下:
/*
* Used to create numbers.
*
* NOTE: _IOW means userland is writing and kernel is reading. _IOR
* means userland is reading and kernel is writing.
*/
#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))
大家使用这几个宏,自己传入type,number,size参数即可定义出 “大概率”不会重复的命令号。
3.解决措施
通过前面的学习了解,我按照内核提供的这个规范的ioctl命令定义方式,重新定义了我那三个命令(驱动中和应用中都要定义的一样),之后再跑, 之前的问题就不会出现了,即使再"多年以后"应用中对ioctl的召唤,也能正确的找到它的fd对应的哪个xxx_ioctl。
4.思考
(1)本次问题出现的原因,未能得到具体的根因,只知道大概是因为由于ioctl命令号定义的不规范(直接用数字,而且从0开始),存在在ioctl系统调用过程中错误匹配的可能,导致命令最终没调用到期望的驱动程序的xxx_ioctl函数(待深入学习ioctl的系统调用过程)。
(2)规范中对type的定义,查阅了一些资料,但还是没有搞的很清楚,只知道它就是一个8bit长度的值,通常使用字符,在内核源码的Documentation/ioctl/ioctl-number.x中列出了出现过的type和number组合,但这个文件并不一定很完备,只能做个参考。因此我们在自己要定义新cmd时,可以看一下你所用内核的该文件中列出的已使用的这些<type,number>组合,虽说尽管你用了这里列出来的组合也大概率不会造成重复的命令号(因为还有direction和size的也在增加多样性),但还是建议避开这里列的组合来定义自己的命令号。