第六章:高级字符驱动程序操作

来源:互联网 发布:cocos2dx棋牌游戏源码 编辑:程序博客网 时间:2024/05/19 22:24
1、ioctl
除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。
在用户空间,ioctl系统调用有如下原型:
  1. int ioctl(int fd, unsigned long cmd, .../*char *argp*/);
驱动程序的ioctl方法原型与用户空间的版本不一样
  1. int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
inode 和 filp 两个指针对应于应用程序传递的文件描述符fd,这和传递给open方法的参数一样。
参数cmd由用户空间不经修改的传递给驱动程序。
可选的arg参数则无论用户程序使用的是指针还是整数值,它都以unsigned long的形式传递给驱动程序。
大多数的ioctl的实现中都包含一个switch语句来根据cmd参数选择对应的操作。

2、选择ioctl命令
在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内惟一。
为了方便程序员创建位移的ioctl命令号,每一个命令号被分为多个位字段。
在2.6内核中,命令号被分为了4个字段,定义在<linux/ioctl.h>中
type
        幻数。选择一个号码,并在整个驱动程序中使用这个号码。这个字段有8位宽(_IOC_TYPEBITS)
number
        序数(顺序编号)。也是8位宽(_IOC_NRBITS).
direction
        如果命令涉及到数据的传输,则该字符段定义数据的传输方向。 可用的值有_IOC_NONE、_IOC_READ、_IOC_WRITE以及_IOC_READ | _IOC_WRITE .注意这个字段一个掩码,可以用逻辑AND操作从中分解出    _IOC_READ 和 _IOC_WRITE
size
        所涉及的用户数据大小。这个字段的宽度与用户体系结构有关,通常是13位或者14位。具体可以通过_IOC_SIZEBITS找到针对特定体系结构的具体数值。
      、
在<linux/ioctl.h>中包含的<asm/ioctl.h>头文件定义了一些构造命令编号的宏:
  1. _IO(type, nr) //用于构造无参数的命令编号
  2. _IOR(type, nr, datatype) //用于构造从驱动程序中读取数据的命令编号
  3. _IOW(type, nr, datatype) //用于写入数据的命令
  4. _IOWR(type, nr, datatype) //用于双向传输
ioctl命令长32bit。分为上述的四个段,具体的分布如下:
DIR(2)   SIZE(14)   TYPE(8)   NR(8)

另外的一些宏:
  1. _IOC_DIR(nr) //判断nr的方向
  2. _IOC_TYPE(nr) //判断nr的类型
  3. _IOC_NR(nr) //判断nr的序数
  4. _IOC_SIZE(nr) //判断nr的大小

3、使用ioctl参数
在前面的ioctl中有一个附加的参数argp。如果这个是个整数,那就直接使用就可以了。但是如果是一个指针的话,就需要注意问题了。
如果这个指针是指向用户空间的话,就必须确保指向的用户空间是合法的。驱动程序应该负责对每个用到的用户空间地址作适当的检查,如果是非法地址则返回一个错误。
在<asm/uaccess.h>中声明的access_ok函数是用来验证地址的。
  1. int access_ok(int type, const void *addr, unsigned long size);
  2. /* 第一个参数应该是VERIFY_READ或VERIFY_WRITE,取决于要执行的动作是读取还是写入用户空间的内存注意,VERIFY_WRITE 是 VERIFY_READ 的超集 -- 如果可以安全的写内存块,那么自然也总能读到内存块
  3. addr参数是一个用户空间地址,size是字节数。*/
返回值
此函数检查用户空间中的内存块是否可用。如果可用,则返回真(非0值),否则返回假 (0) 。
除了copy_from_user和copy_to_user之外,还可以使用已经为最常用的数据大小1(1、2、4、8字节)优化过的一组函数。
  1. #include <asm/uaccess.h>
  2. put_user(datum. ptr)
  3. __put_user(datum, ptr)
  4. get_user(local, ptr)
  5. __get_user(local, ptr)
put_user  和 __put_user两个宏的作用是把datum写到用户空间。
它们相对比较快,当要传递单个数据时,应该用这些宏,而不是用copy_to_user。由于这些宏不进行类型检查,所以可以传递给put_user任意类型的指针,只要是一个用户空间地址指针就行。传递的数据大小依赖于ptr参数的类型,在编译时由编译器的内建指令sizeof和typeof确定。总之,如果ptr是一个字符指针,就传递1个字节。
put_user进行检查以确保进程可以写入指定的内存地址,并在成功时返回0,出错时返回-EFAULT。__put_user做的检查少些(它不调用access_ok)。如果指向用户不能写入的内存时,就会出现操作失败。因此__put_user应该在已经使用access_ok检验过的内存区后再使用。

get_user 和 __get_user 用于从用户空间接收一个数据。除了传输方向相反之外,它们与put_user和__put_user差不多。接收的数值被保存在local中,返回值指明了操作是否成功。

4、权能和受限操作
全部权能操作都可以在<linux/capability.h>中找到,其中包含了系统能够理解的所有权能;不修改内核源代码,驱动程序作者或系统管理员就无法定义新的权能。
  1. CAP_DAC_OVERRIDE
  2. /* 越过文件或目录的访问限制(数据访问控制或DAC)的能力 */
  3. CAP_NET_ADMIN
  4. /* 执行网络管理任务的能力, 包括那些能影响网络接口的任务 */
  5. CAP_SYS_MODULE
  6. /* 载入和卸载内核模块的能力 */
  7. CAP_SYS_RAWID
  8. /* 执行“裸”IO操作的能力 */
  9. CAP_SYS_ADMIN
  10. /* 截获的能力,他提供了访问许多系统管理操作的途径 */
  11. CAP_SYS_TTY_CONFIG
  12. /* 执行tty配置任务的能力 */
在执行一项特权操作之前,设备驱动程序应该检查调用进程是否有合适的权能。权能的检查通过capable函数实现(定义在<sys/sched.h>中)
  1. int capable(int capability);
  2. /* 有相应的权能时返回真,没有相应权能时返回假 */。
原创粉丝点击