IOCTL
除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。简单的数据传输之外,大部分设备可以执行其他一些操作,比如用户空间经常会要求设备锁门、弹出介质、报告错误信息、改变波特率或者执行自破坏,等等。这些操作通常通过ioctl方法支持,该方法实现了同名的系统的调用。
用户空间
int ioctl(int fd,unsigned long cmd,...);
这里用点只是为了在编译时防止编译器进行类型检查。第三个参数的具体形式依赖于要完成的控制命令,也就是第二个参数。有些控制命令不要参数,有些需要一个整数参数,某些需要一个指针参数。使用指针可以向ioctl调用传递任意数据,这样设备可以与用户空间交换任意数量的数据。
本质上说每一个ioctl就是一个独立地系统调用,而且是非公开的。有些需求需要我们通过其他途径实现繁杂的控制操作,可能的方式包括:将命令嵌入到数据流中,或者使用虚拟文件系统,比如sysfs或者设备相关的文件系统。但是对于真正的设备操作来说,ioctl仍然是最简单且最有效的选择。
驱动程序的ioctl方法原型和用户空间的版本存在有些不同:int(*ioctl)(struct inode *inode,struct file *flip,unsigned int cmd,unsigned long arg);
inode和flip两个指针的值对应于应用程序传递的文件描述符fd,这和传给open方法的参数一样。参数cmd由用户不经修改地传递给驱动程序,可选的arg参数则无论用户使用的是指针还是整数值,都会以unsigned long的形式传递给驱动程序,如果用户空间调用程序没有传递第三个参数,那么驱动程序所接受的arg参数就处在未定义状态。由于对这个附加参数的类型检查就被关闭了,所以如果为ioctl传递一个非法参数,编译器是无法报警的,这样相关联的程序错误就很难被发现。
ioctl实现都包括一个switch语句来根据cmd参数选择对应的操作。不同的命令被赋予不同的数值,为了简化代码,通常会在代码中使用符号名代替数值,这些符号名由C的预处理语句定义。定制的设备驱动程序通常会在它们的头文件中声明这些符号,如scull中声明了所使用的符号。为了访问这些符号,用户程序自然也要包含这些头文件。
选择ioctl命令为了选择对错误的设备使用正确的命令,命令好应该在系统范围内唯一。这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFO和audio等非串行设备输入流修改波特率。如果每一个ioctl命令是唯一的,应用程序进行这种操作时就会得到一个EINVAL错误,而不是无意间成功的完成了意想不到的操作。
要按照linux内核的约定的方法为ioctl编号,应该首先看看include/asm/ioctl.h和Document/ioctl-number.txt这两个文件。头文件规定了要使用的位段:类型(幻数)、序数、传送方向以及参数大小等等。ioctl-number.txt文件中罗列了内核所使用的幻数,这样,在选择自己的幻数时就可以避免和内和冲突。这个文件也给出了为什么应该使用这个约定的原因。
以上来自LDD3
/*
* The original linux ioctl numbering scheme was just a general
* "anything goes" setup, where more or less random numbers were
* assigned. Sorry, I was clueless when I started out on this.
*
* On the alpha, we'll try to clean it up a bit, using a more sane
* ioctl numbering, and also trying to be compatible with OSF/1 in
* the process. I'd like to clean it up for the i386 as well, but
* it's so painful recognizing both the new and the old numbers..
*/
最初linux ioctl命令号命令仅仅是一个通用的“怎么写都行”的初始版本,使用了很多随机的分配的数字。抱歉,我开始的时候毫无头绪。
在alpha上,我们试图清理一点,使用更清楚点的ioctl的序列号,并且也试图努力和OSF/1在处理上兼容。我也愿意清理一下i386,但是同时识别老的和新的序列号实在是非常痛苦的。
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 13
#define _IOC_DIRBITS 3
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
/*
* Direction bits _IOC_NONE could be 0, but OSF/1 gives it a bit.
* And this turns out useful to catch old ioctl numbers in header
* files for us.
*/
#define _IOC_NONE 1U
#define _IOC_READ 2U
#define _IOC_WRITE 4U
#define _IOC(dir,type,nr,size) \
((unsigned int) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT)))
/* used to create numbers */
#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))
/* used to decode them.. */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
/* ...and for the drivers/sound files... */
#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)
今天就先读3页书吧,毕竟每天自己的时间也不是太多,每天在公司做自己的事情,回到住处打开书看一会就已经11点了,考虑明天把书带到公司。明天继续,come on!