构造IOCTL命令的学习心得-----_IO,…
在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一,这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFO和audio等这类非串行设备输入流修改波特率,如果每一个ioctl命令都是唯一的,应用程序进行这种操作时就会得到一个EINVAL错误,而不是无意间成功地完成了意想不到的操作。
在驱动程序里, 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() 的实现,其它的类似。
要按Linux内核的约定方法为驱动程序选择ioctl编号,应该首先看看include/asm/ioctl.h和Doucumention/ioctl-number.txt这两个文件。头文件定义了要使用的位字段:类型(幻数)、序数、传送方向以及参数大小等。ioctl-number.txt文件中罗列了内核所使用的幻数,选择自己的幻数要避免和内核冲突。以下是对include/asm/ioctl.h中定义的宏的注释:
#define _IOC_NRBITS 8 //序数(number)字段的字位宽度,8bits
#define _IOC_TYPEBITS 8 //幻数(type)字段的字位宽度,8bits
#define _IOC_SIZEBITS 14 //大小(size)字段的字位宽度,14bits
#define _IOC_DIRBITS 2 //方向(direction)字段的字位宽度,2bits
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) //序数字段的掩码,0x000000FF
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) //幻数字段的掩码,0x000000FF
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) //大小字段的掩码,0x00003FFF
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) //方向字段的掩码,0x00000003
#define _IOC_NRSHIFT 0 //序数字段在整个字段中的位移,0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻数字段的位移,8
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小字段的位移,16
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向字段的位移,30
#define _IOC_NONE 0U //没有数据传输
#define _IOC_WRITE 1U //向设备写入数据,驱动程序必须从用户空间读入数据
#define _IOC_READ 2U //从设备中读取数据,驱动程序必须向用户空间写入数据
//构造无参数的命令编号
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//构造从驱动程序中读取数据的命令编号
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
//用于向驱动程序写入数据命令
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//用于双向传输
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
//从命令参数中解析出数据方向,即写进还是读出
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//从命令参数中解析出幻数type
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//从命令参数中解析出序数number
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//从命令参数中解析出用户数据大小
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
以上内容转自:article.phpfans.net/
我的理解:
假如我定义了一个命令MY_CMD:
#define MY_CMD_MAGIC
0xdf
//type字段,由于字段宽度为8 bits,所以不能大于0xff
#define MY_CMD
_IOW(MY_CMD_MAGIC,0,unsigned
int)
于是有命令MY_CMD各组成字段(dir,size,type,nr)分别为:01(=_IOC_WRITE),00
0000 0000 0100(=sizeof(unsigned int)),1101 1111(=MY_CMD_MAGIC),0000
0000(=0)。用十六进制表示即0x4004df00。这个32
bits的"整数"就是该命令的编号(LDD3原文是command number),也就是命令MY_CMD在系统中的"身份证号码"了!
为什么上面我用"身份证号码"作比喻呢?众所周知,一个国家里所有公民的身份证号都是各不相同的。身份证编号有一定的规则:即把身份证号划分成几个字段,各段位数可不等,每个字段编码都有它的实际意义,例如我们现在用的的身份证前N位(不记得具体是多少了)表示一个具体的省、市、等地区,不同地区的人该字段肯定不同了;另外有个表示出生年月的字段(据说以前整个号码最后一位偶数表示男性,奇数表示女性,现在貌似没这个规则了,此为题外话)。
类似地,我们要为系统里所有的IOCTL命令编号。我们身份证用的是15(上一代是18位)位十进制数编码(最后一位可能是拉丁字母);我们用32位二进制数为IOCTL命令编码,把它划分成4个字段,每段也有它的实际意义。要保证每个命令编号为系统唯一,主要靠命令的type和nr字段。我们称type字段内容为magic
number,即幻数,它表示命令的类型。
看到上段红色这句话,我想可能有细心的人会问:"那么命令到底有哪些类型呢?"老实说我也不知道正确答案。大概因为大家都知道基本数据类型有整形、浮点型、字符型...人的性格类型有外向型、内向型...这些我们常见的类型都是可以用文字来枚举描述的,所以潜意识就觉得有类型就应该有文字可描述吧。回到正题,我想每个magic
number,就像上面的宏定义中:#define MY_CMD_MAGIC 0xdf,MY_CMD_MAGIC就是类型名了吧!不知道我的想法对不对?!反正大家知道一个magic number就对应唯一一种命令类型就是了。LDD原文中有一段:
type
The magic number. Just choose one number (after consulting
ioctl-number.txt)
and use it throughout the driver. This field is eight bits wide
(_IOC_TYPEBITS).
原文是说type的内容叫幻数(magic number),强调它是一个8位二进制数(number)。
因此不同type的命令就有不同的magic number,因此命令编码自然就不同了。但如果两个命令type相同,它们的magic number就相同,于是就不能仅靠type字段区分了。于是nr字段就起作用了:
number
The ordinal (sequential) number. It's eight bits (_IOC_NRBITS)
wide.
nr(number)即序号,一般地我们从0开始编号。由于nr字段为8位二进制数,所以nr的取值范围为0~255。同一种type的命令每个nr值对应唯一一个命令。
其他两个字段在这里不作深究。
值得一提的是LDD3里这么一段话:
The header also defines macros that may be used in your
driver to decode the num-
bers: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr).
We won't go
into any more detail about these macros because the header file is
clear, and sample
code is shown later in this section.
根据ioctl.h文件的定义,很明显_IOC_DIR(nr),
_IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个宏里面的参数就是一个IOCTL命令,即一个32位的二进制数,而文件中参数居然用nr表示!当然用nr表示不是逻辑上的错误。但是别忘了文件中还有_IOW(type,nr,size)这样的定义IOCTL命令的宏,这其中的参数nr是IOCTL命令编号中的一个nr字段,一个8位的二进制数!我想很多新人都会对此产生莫大的疑惑!所以我认为把_IOC_DIR(nr),
_IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个解码宏的参数改用cmd表示更恰当!
但是,我知道这不是LDD3作者的错,因为内核头文件里面也是这么表示的。我想内核开发者不可能没意识到这个问题。因此,我猜测这其中肯定有个历史原因:大概以前版本的命令不管type是否一样,nr字段的值都是唯一的,于是仅靠nr字段就可以解码出一个IOCTL命令的其他字段吧?!但即使这样_IOC_DIR(nr),
_IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)也没必要保留这种写法啊!到底谁可以告诉我真相?
LDD3没有对_IOC_DIR(nr), _IOC_TYPE(nr),
_IOC_NR(nr),_IOC_SIZE(nr)里面的nr作任何解释,只是实例中有如下用法:
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return
-ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
可见那个nr参数完全就是我所说的32位的IOCTL命令编码。靠,既然这样好歹也对着个confusion作一下简单的解释啊!如果LDD3一书那"既不承认,也不否认"的暧昧态度让我真让我哭笑不得的话,那么国内某书(具体哪本我就不说了)简直令我抓狂,我摘书中的两段话如下:
_IO(type,nr):定义一个没有数据传输的命令编号。type为幻数,nr为命令编号。...
...
_IOC_DIR(nr): 获得命令编号的命令传输方向(direction)。这个宏的参数就是命令编号。
晕了没有?红色标记处的nr分明就是命令编号的nr(即序号)字段嘛!真XX@#¥&*$!
对国内的书越来越失望了!没有多少自己的东西就罢了!连翻译的都翻不好!误人子弟!~
构造IOCTL命令的学习心得-----_IO,…的更多相关文章
- 关于构造IOCTL命令的学习心得
在编写ioctl代码之前,需要选择对应不同命令的编号.为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一,这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFO和audio等这类非 ...
- Linux学习心得之 Linux下命令行Android开发环境的搭建
作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Linux学习心得之 Linux下命令行Android开发环境的搭建 1. 前言2. Jav ...
- Linux学习心得之 linux命令
作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 前言 本篇博客是对 每日一linux命令(http://www.cnblogs.com/pe ...
- 我的MYSQL学习心得(十) 自定义存储过程和函数
我的MYSQL学习心得(十) 自定义存储过程和函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心 ...
- 我的MYSQL学习心得(十)
原文:我的MYSQL学习心得(十) 我的MYSQL学习心得(十) 我的MYSQL学习心得(一) 我的MYSQL学习心得(二) 我的MYSQL学习心得(三) 我的MYSQL学习心得(四) 我的MYSQL ...
- Java开发学习心得(一):SSM环境搭建
目录 Java开发学习心得(一):SSM环境搭建 1 SSM框架 1.1 Spring Framework 1.2 Spring MVC Java开发学习心得(一):SSM环境搭建 有一点.NET的开 ...
- 《Linux内核分析》期末总结及学习心得
[洪韶武 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ] 一.学习心得 本学 ...
- 我的MYSQL学习心得(一) 简单语法
我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- 我的MYSQL学习心得(四) 数据类型
我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...
随机推荐
- L132
Major Opioid Maker to Pay for Overdose-Antidote Development A company whose prescription opioid mark ...
- ARM 内核SP,LR,PC寄存器
深入理解ARM的这三个寄存器,对编程以及操作系统的移植都有很大的裨益. 1.堆栈指针r13(SP):每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式.非异常模 ...
- Intellij IDEA带参数启动Springboot注意事项
问题 不同版本的spring-boot-maven-plugin的jvm参数配置有所不同,同时与通过main方法启动springboot程序传递参数也有所不同. 分析 在运行main方法时,可以通过j ...
- Android内存优化(一)DVM和ART原理初探
相关文章 Android内存优化系列 Java虚拟机系列 前言 要学习Android的内存优化,首先要了解Java虚拟机,此前我用了多篇文章来介绍Java虚拟机的知识,就是为了这个系列做铺垫.在And ...
- 混用Application.LoadLevel 和 PhotonNetwork.LoadLevel
最近这一周从上周五晚上加完班回家夜里都12点了. 又赶紧送孩子去儿童医院 .. 就肺炎住院了. 真是有啥别有病呢. 悲剧的是我周三夜里陪了一夜后. 转天晚上也发烧了.. 周四 周五输了两天液. ...
- bzoj 1925 地精部落
Written with StackEdit. Description 传说很久以前,大地上居住着一种神秘的生物:地精. 地精喜欢住在连绵不绝的山脉中.具体地说,一座长度为 \(N\) 的山脉 \(H ...
- request.getDispatcher().forward(request,response)和response.sendRedirect()的区别
在进行web开发时,跳转是最常见的,今天在这里来学习下2种跳转: 第一种是request.getDispatcher().forward(request,response): 1.属于转发,也是服务器 ...
- Fragment详解及举例
1.为什么用Fragment(Android3.0提出)来替代TabActivity(Android4.0以后正式被弃用)? 因为Fragment可以适应各种不同屏幕大小,也就是适应不同屏幕的分辨率. ...
- phpstorm2017.3.6的激活、样式设置和汉化
一:安装phpstorm2017.3.6,并激活.设置样式.(1)先在phstorm官网里www.jetbrains.com下载phpstorm2017.3.6,按照步骤安装即可.下面开始激活!(2) ...
- Spring中类型自动装配--byType
在Spring中,“类型自动装配”的意思是如果一个bean的数据类型与其它bean属性的数据类型相同,将自动兼容装配它. 例如,一个“persion” bean 公开以“ability”类数据类型作为 ...