LDD3 高级字符驱动程序操作

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!

你可能感兴趣的:(LDD3 高级字符驱动程序操作)