Chinaunix首页 | 论坛 | 博客
  • 博客访问: 385890
  • 博文数量: 57
  • 博客积分: 2299
  • 博客等级: 大尉
  • 技术积分: 1109
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-27 23:12
文章分类
文章存档

2011年(4)

2010年(53)

分类: 嵌入式

2010-02-28 00:40:12

说明:由于本人刚刚接触linux设备驱动,可以这样说,第五章的并发和竞态在我这个阶段根本不会遇到,但也不是就我完全不考虑,现在对scull结构还不是很理解透彻,学习并发和竞态会有一定不可预料的难度,并且在本科阶段也不会遇到,等学习第二遍的时候再细究,所以先跳过,学习更实用一点的高级字符设备驱动程序。

Linux设备驱动程序学习[4]—高级字符驱动程序操作1

一、          Ioctl

大部分设备除了读写能力,还可进行超出简单的数据传输之外的操作,所以设备驱动也必须具备进行各种硬件控制操作的能力. 这些操作常常通过 ioctl 方法来支持:

在用户空间,ioctl有如下原型:

int ioctl(int fd, unsigned long cmd, ...); 

后面连续的点代表可变参数。但是在实际系统中,ioctl必须有精确的原型定义,因为应用程序必须通过硬件门才可以与硬件通信,所谓的硬件门即驱动驱动程序。

驱动程序的ioctl方法原型与用户空间有所不同;

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode filp 指针是对应应用程序传递的文件描述符 fd 的值,与传给open方法的参数一样。

注意:无论如何,可选参数arg都以unsigned long的形式传送,如果应用程序未传递arg,那么arg处于未定义状态。由于对该参数的类型检查关闭了,故应用程序传递一个非法参数编译器也是不会报警的,这样,相关联的程序错误就很难发现。

 

选择ioctl命令

为了防止向错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的 ioctl 命令代号, 每个命令号被划分为多个位字段。要按 Linux 内核的约定方法为驱动选择 ioctl 的命令号, 应该首先看看 include/asm/ioctl.h Documentation/ioctl-number.txt 要使用的位字段符号定义在 :

type(幻数)   8 位宽(_IOC_TYPEBITS),参考ioctl-number.txt选择一个数,并在整个驱动中使用它。

number(序数):   顺序编号,8 位宽(_IOC_NRBITS)

direction(数据传送的方向):       可能的值是 _IOC_NONE(没有数据传输)_IOC_READ _IOC_WRITE _IOC_READ|_IOC_WRITE (双向传输数据)。该字段是一个位掩码(两位), 因此可使用 AND 操作来抽取_IOC_READ _IOC_WRITE

size(数据的大小):      宽度与体系结构有关,ARM14.可在宏 _IOC_SIZEBITS 中找到特定体系的值.    中包含的 定义了一些构造命令编号的宏:

_IO(type,nr)/*没有参数的命令*/

_IOR(type, nr, datatype)/*从驱动中读数据*/

_IOW(type,nr,datatype)/*写数据*/

_IOWR(type,nr,datatype)/*双向传送*/

/*type number 成员作为参数被传递, 并且 size 成员通过应用 sizeof datatype 参数而得到*/

这个头文件还定义了用来解开这个字段的宏:

_IOC_DIR(nr)

_IOC_TYPE(nr)

_IOC_NR(nr)

_IOC_SIZE(nr)

具体的使用方法在实验中展示。

返回值

POSIX 标准规定:如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为"不合适的设备 ioctl。然而,它返回-EINVAL仍是相当普遍的。

预定义命令

有一些ioctl命令是由内核识别的,当这些命令用于自己的设备时,他们会在我们自己的文件操作被调用之前被解码. 因此, 如果你选择一个ioctl命令编号和系统预定义的相同时,你永远不会看到该命令的请求,而且因为ioctl 号之间的冲突,应用程序的行为将无法预测。预定义命令分为 3 :

1)用于任何文件(常规, 设备, FIFOsocket) 的命令

2)只用于常规文件的命令

3)特定于文件系统类型的命令

下列 ioctl 命令是预定义给任何文件,包括设备特定文件:

FIOCLEX :设置 close-on-exec 标志(File IOctl Close on EXec)

FIONCLEX :清除 close-no-exec 标志(File IOctl Not CLose on EXec)

FIOQSIZE :这个命令返回一个文件或者目录的大小; 当用作一个设备文件, 但是, 它返回一个 ENOTTY

错误。

FIONBIO"File IOctl Non-Blocking I/O"("阻塞和非阻塞操作"一节中描述) 

使用ioctl参数

在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小

心。当用一个指针引用用户空间, 我们必须确保用户地址是有效的,其校验(不传送数据)由函数access_ok 实现,定义在 :

 int access_ok(int type, const void *addr, unsigned long size);

 第一个参数应当是 VERIFY_READ(读)或VERIFY_WRITE(读写);addr 参数为用户空间地址,size 为字节数,可使用sizeof()access_ok 返回一个布尔值: 1 是成功(存取没问题) 0 是失败(存取有问题)。如果它返回假,驱动应当返回 -EFAULT 给调用者。

注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的

内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直接使用put_user(datum, ptr)get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回 -EFAULT. put_user(datum, ptr)

__put_user(datum, ptr)

get_user(local, ptr)

__get_user(local, ptr)

这些宏它们相对copy_to_user copy_from_user, 并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址. 传送的数据大小依赖 prt 参数的类型, 并且在编译时使用 sizeof typeof 等编译器内建宏确定。他们只传送1248 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested". 在这些情况中,必须使用 copy_to_user 或者 copy_from_user

__put_user__get_user 进行更少的检查(不调用 access_ok), 但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用 access_ok 检查过的时候。作为通用的规则:当实现一个 read 方法时,调用 __put_user 来节省几个周期, 或者当你拷贝几个项时,因此,在第一次数据传送之前调用 access_ok 一次。

权能与受限操作

Linux 内核提供了一个更加灵活的系统, 称为权能(capability)。内核专为许可管理上使用权能并导

出了两个系统调用 capget capset,这样可以从用户空间管理权能,其定义在

中。对设备驱动编写者有意义的权能如下: CAP_DAC_OVERRIDE /*越过在文件和目录上的访问限制(数据访问控制或 DAC)的能力。*/

CAP_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/

CAP_SYS_MODULE /*加载或去除内核模块的能力*/

CAP_SYS_RAWIO /*进行 "raw"(裸)I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯*/

CAP_SYS_ADMIN /*截获的能力, 提供对许多系统管理操作的途径*/

CAP_SYS_TTY_CONFIG /*执行 tty 配置任务的能力*/

在进行一个特权操作之前, 一个设备驱动应当检查调用进程有合适的能力,检查是通过 capable 函数来进行的(定义在 )范例如下: if (! capable (CAP_SYS_ADMIN))

 return -EPERM;

二、          定位设备(llseek实现)

llseek是修改文件中的当前读写位置的系统调用。内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是文件中的当前读写位置。对于 lseek 系统调用要正确工作,读和写方法必须通过更新它们收到的偏

移量来配合。如果设备是不允许移位的,你不能只制止声明 llseek 操作,因为缺省的方法允许移位。应当在你的 open 方法中,通过调用 nonseekable_open 通知内核你的设备不支持 llseek : int nonseekable_open(struct inode *inode; struct file *filp);

完整起见, 你也应该在你的 file_operations 结构中设置 llseek 方法到一个特殊的帮助函数 no_llseek(定义在 )。 具体的应用在试验程序中学习.

一、   源码及测试

驱动程序scull.c添加

static int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

       int err = 0, tmp;

       int retval = 0;

/*

       * extract the type and number bitfields, and don't decode

       * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()

 */

       if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)

              return -ENOTTY;

       if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)

              return -ENOTTY;

/*

       * the direction is a bitmask, and VERIFY_WRITE catches R/W

       * transfers. `Type' is user-oriented, while

       * access_ok is kernel-oriented, so the concept of "read" and

       * "write" is reversed

 */

       if (_IOC_DIR(cmd) & _IOC_READ)

              err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));

       else if (_IOC_DIR(cmd) & _IOC_WRITE)

              err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));

       if (err)

              return -EFAULT;

       switch(cmd)

       {

              case SCULL_IOCRESET:

                     scull_quantum = SCULL_QUANTUM;

                     scull_qset = SCULL_QSET;

                     PDEBUG("Reset!\n");

                     break;

 

              case SCULL_IOCSQUANTUM: /* Set: arg points to the value */

                     if (! capable (CAP_SYS_ADMIN))

                            return -EPERM;

                     retval = __get_user(scull_quantum, (int __user *)arg);

                     PDEBUG("Set: arg points to the value:%d\n",scull_quantum);

                     break;

 

              case SCULL_IOCTQUANTUM: /* Tell: arg is the value */

                     if (! capable (CAP_SYS_ADMIN))

                            return -EPERM;

                     scull_quantum = arg;

                     PDEBUG("STell: arg is the value:%d\n",scull_quantum);

                     break;

 

              case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */

                     retval = __put_user(scull_quantum, (int __user *)arg);

                     break;

 

              case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */

                     PDEBUG("Query: return it (it's positive) :%d\n",scull_quantum);

                     return scull_quantum;

 

              case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */

                     if (! capable (CAP_SYS_ADMIN))

                            return -EPERM;

                     tmp = scull_quantum;

                     retval = __get_user(scull_quantum, (int __user *)arg);

                     if (retval == 0)

                            retval = __put_user(tmp, (int __user *)arg);

                     break;

 

              case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */

                     if (! capable (CAP_SYS_ADMIN))

                            return -EPERM;

                     tmp = scull_quantum;

                     scull_quantum = arg;

                     return tmp;

              default: /* redundant, as cmd was checked against MAXNR */

                     return -ENOTTY;

       }

       return retval;

}

struct file_operations scull_fops = {

       .owner  =    THIS_MODULE,

//     .llseek =   scull_llseek,

       .read =     scull_read,

                     .write =  scull_write,

       .ioctl =    scull_ioctl,

       .open =     scull_open,

       .release =  scull_release

};

Scull.h添加

/* Use 'k' as magic number */

#define SCULL_IOC_MAGIC 'k'

/* Please use a different 8-bit number in your code */

 

#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)

/*

 * S means "Set" through a ptr,

 * T means "Tell" directly with the argument value

 * G means "Get": reply by setting through a pointer

 * Q means "Query": response is on the return value

 * X means "eXchange": switch G and S atomically

 * H means "sHift": switch T and Q atomically

 */

#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)

#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)

#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)

#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)

#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)

#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)

#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)

#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)

#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)

#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)

#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)

#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 12

应用程序如段下:

Scull-test.c

#ifdef TEST_IOCTL

       if((fd = open("/dev/scull",O_WRONLY)) == -1)

{

       perror("Open");

       return -1;

} else {

       printf("File Opened!\n");

}    

       /*test io reset,no data*/

       if(ioctl(fd, SCULL_IOCRESET) == 0)

              PDEBUG("SCULL_IOCRESET OK!\n");

       else

              perror("ioctl-Rest!");

       /*Set: arg points to the value*/

       if(ioctl(fd, SCULL_IOCSQUANTUM, &num) == 0)

              PDEBUG("Set: arg points to the value OK! num = %d\n",num);

       else

              perror("ioctl-SCULL_IOCSQUANTUM!");

       /*tell: arg is the value*/

       if(ioctl(fd, SCULL_IOCTQUANTUM, num) == 0)

              PDEBUG("tell: arg is the value OK! num = %d\n",num);

       else

              perror("ioctl-SCULL_IOCTQUANTUM:");

       /* Query: return it (it's positive) */

       if((tmp  = ioctl(fd, SCULL_IOCQQUANTUM)) == num)

              PDEBUG("Query: return it (it's positive)  OK! tmp = %d\n",tmp);

       else

              perror("ioctl-SCULL_IOCQQUANTUM:");

       sleep(1);

       close(fd);

#endif

Scull-test.h添加

/* Use 'k' as magic number */

#define SCULL_IOC_MAGIC 'k'

/* Please use a different 8-bit number in your code */

#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)

/*

 * S means "Set" through a ptr,

 * T means "Tell" directly with the argument value

 * G means "Get": reply by setting through a pointer

 * Q means "Query": response is on the return value

 * X means "eXchange": switch G and S atomically

 * H means "sHift": switch T and Q atomically

 */

#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)

#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)

#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)

#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)

#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)

#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)

#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)

#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)

#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)

#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)

#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)

#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 12

上述代码未参考前辈代码,完全通过读了《LDD3》写出,看了规范的io控制驱动才发现天嵌手册上的驱动程序不是很规范,估计是为了让像我一样的新手开始的时候少接触些新东西,用简单的语言来说明问题。这个测试程序只是测试驱动的几个功能,其它功能可以自己再添加,方法类似。

测试结果如下:

[root@gfy-S3C2440 /tmp]# ./scull-test

Module Opened![kernel]

File Opened!

scull[kernel]: Reset!

scull[kernel]: Set: arg points to the value:123

scull[kernel]: STell: arg is the value:123

scull[kernel]: Query: return it (it's positive) :123

scull[user]:SCULL_IOCRESET OK!

scull[user]:Set: arg points to the value OK! num = 123

scull[user]:tell: arg is the value OK! num = 123

scull[user]:Query: return it (it's positive)  OK! tmp = 123

Module Released![kernel]

从上述显示可以看出来自内核和应用程序的消息,程序中加上slepp(1);的原因是:当应用程序调用了close(fd);关闭文件时,也就执行了驱动中

static int scull_release(struct inode *inode, struct file *filp)

{

       printk(KERN_WARNING"\nModule Released![kernel]\n");

       return 0;

}这个函数,由于我加入了调试信息,以便于观察每一次文件操作时调用的那个驱动函数(暂且叫函数),当不加上slepp(1);close(fd);之前时,驱动的调试信息便会比应用程序的信息先显示在终端上,把观察效果搞乱了,内在原因是clos时立即就调用驱动的. Release,在调用到显示printk的这段时间里应用程序在close之前需要显示的信息还未能在终端上显示,故造成上述显示时序不对的情况,加上sleep以后即可给应用程序足够时间来显示close之前的信息。
阅读(1577) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~