Chinaunix首页 | 论坛 | 博客
  • 博客访问: 113257
  • 博文数量: 23
  • 博客积分: 471
  • 博客等级: 一等列兵
  • 技术积分: 251
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-21 15:21
文章分类
文章存档

2017年(1)

2013年(2)

2011年(20)

分类: LINUX

2011-08-18 14:48:40

  本章主要介绍一些高级字符设备操作,主要涉及到驱动程序中ioctl实现,以及和用户空间保持同步的几种途径,并且我们还掌握如何使进程休眠/唤醒,如何实现非阻塞I/O,以及设备在可读取或者写入的时候通知用户空间。
 
1.ioctl
 
   ioctl主要用来控制设备,比如一个串口需要设定波特率,设定数据位等,这时就可以通过ioctl来实现控制设备。下面是系统调用原型:
  1. int ioctl(int fd,unsigned long cmd,...);
这里的"..."在linux系统中表示可变数目的参数表,但是在这里比较特殊表示可选参数,用char *argp表示定义,这里用点是为了防止编译器进行类型检查,第三个参数的形式具体依赖第二个参数控制命令在驱动程序中的完成方式,有些控制命令不需要参数,或者需要一个int,或者一个指针。
 
   驱动程序中ioctl方法原型如下:
  1. int (*ioctl)(struct inode *inode,struct file *flip,unsigned int cmd,unsigned long arg);

inode和flip大家应该都比较熟悉了吧,cmd就是对应系统调用ioctl中的第二个参数,而arg对应第三个参数,驱动程序中统一将arg类型转换成unsigned long。

   1.1  选择ioctl命令

     命令号即是我们传递给驱动程序的一个标号来表明我对应的命令应该做什么操作,所以很简单的话,这个命令可以依次定义为0,1,2,3,....,但是想想这样定义有什么坏处呢?坏处就是我们大部分设备的命令号都是相同的,这样的话不免我们可能将命令发送给了错误的设备,这样的话有可能导致出错,很好的是内核提供了方便我们构造命令号的API以及一系列方法,下面就介绍给大家:

    在中,命令号被划分为4个位字段。

    type:幻数,8位宽,现有的kernel中使用了的幻数在Documentation/ioctl/ioctl-number.txt中定义。

    nr:编号,8位宽,驱动程序设计者自行定义,无限制。一般定义为顺序编号1,2,3,4,5...

    direction:涉及到数据传输的ioctl命令,指定传输的方向,可使用的包括:_IOC_NONE,_IOC_READ,_IOC_WRITE,以及_IOC_READ | _IOC_WRITE,注意该数据传输方向是从应用程序角度,如果是_IOC_READ意味着驱动程序要写数据到用户空间;而_IOC_WRITE意味着要向读取用户空间的数据写到设备。

    size:所涉及的用户数据大小,这个字段宽度和体系结构相关,一般是13或14位,可以通过宏_IOC_SIZEBITS找到针对特定体系结构的具体数值,一般少用到该字段。

    在中定义了一些构造命令的宏

      _IO(type,nr) 无参数的编号;

      _IOR(type,nr,datatype) 从驱动程序读取数据的命令编号

      _IOW(type,nr,datatype) 写入数据的命令

      _IOWR(type,nr,datatype) 双向传输

———————————————————————————————————————————————

   1.2  返回值

        ioctl的实现通常就是一个基于命令号的switch语句,如果命令号不能匹配合法的操作时,默认的选择一般是返回-EINVAl或者-ENOTTY,返回-ENOTTY似乎更为合理,但是普遍做法是返回-EINVAL。

   1.3  预定义命令

       在linux系统中有一些自定义的命令号,为了不与ioctl的冲突,这里把常用的列出来:

       FIOCLEX:当调用进程指向一个新程序时,文件描述符将被关闭。

       FIONCLEX:恢复通常的文件行为,撤销上诉命令的工作。

       FIOASYNC:设置/复位文件异步通知,后面会有讨论。

       FIOQSIZE:返回文件或者目录的大小,用于设备文件,返回-ENOTTY的错误。

       FIONBIO:修改filp->f_flags中的O_NOBLOCK标记,第三个参数表明是设置还是清楚该标志,通过fcntl系统调用完成。

    1.4  使用ioctl参数

        如果ioctl的第三个参数是一个指针的话,在内核中使用必须要小心验证,需要用到函数:

  1. int access_ok(int type,const void *addr,unsigned int size);
       type:VERIFY_READ/VERITY_WRITE
       addr:用户空间地址
       size:字节数 例如ioctl要读取一个整数,那么该参数就是sizeof(int).
       返回值:1--成功,0--失败,如果失败驱动程序要返回-EFAULT给调用者.

因为ioctl一般数据量较小,没必要用到copy_from_user或者copy_to_user这种安全的API,所以需要进行检查用户空间指针,我猜的话copy_from_user或者copy_to_user内部也应该用到了该函数检查指针吧.

      经过验证后的地址可以使用的话,内核提供了为最为常用的数据大小传输提供了一组优化后的宏,在中定义:

put_user(datum,ptr)

__put_user(datum,ptr)

 把datum写到ptr指向的用户空间,速度较快,传送单个数据时使用。含下划线的宏必须用access_ok函数验证。

       get_user(local,ptr) 
        __get_user(local,ptr)
         除了传输方向相反外,其余和put_user类似,同样__get_user的地址必须经过access_ok验证。

       

      阅读(1470) | 评论(0) | 转发(0) |
      给主人留下些什么吧!~~