全部博文(57)
分类: 嵌入式
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(数据的大小): 宽度与体系结构有关,ARM为14位.可在宏 _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)用于任何文件(常规, 设备, FIFO和socket) 的命令
(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 等编译器内建宏确定。他们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"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_NET_ADMIN /*进行网络管理任务的能力, 包括那些能够影响网络接口的任务*/
CAP_SYS_MODULE /*加载或去除内核模块的能力*/
CAP_SYS_RAWIO /*进行 "raw"(裸)I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯*/
CAP_SYS_ADMIN /*截获的能力, 提供对许多系统管理操作的途径*/
CAP_SYS_TTY_CONFIG /*执行 tty 配置任务的能力*/
在进行一个特权操作之前, 一个设备驱动应当检查调用进程有合适的能力,检查是通过 capable 函数来进行的(定义在
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-S
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;