设备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),cmd从用户空间传下来,可选的参数arg以一个unsigned long的形式传递,不管他是一个整数还是一个指针。如果cmd命令不涉及数据传输,则第三个参数arg的值毫无任何意义。
如何实现ioctl方法?步骤:定义命令,实现命令。
定义命令:在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。ioctl命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了得幻数。
定义命令:定义ioctl命令的正确方法是使用4个位段,这个列表中介绍的符号定义在中:
(1)Type:幻数(类型),表明哪个设备的命令,在参考了ioctl-number.txt之后选出,8位宽。
(2)Number:序号,表明设备命令中的第几个,8位宽。
(3)Direction:数据传送的方向,可能的值使_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE。数据传送石从应用程序的观点来看的,_IOC_READ的意思是从设备读。
(4)Size:用户数据的大小。(13/14位宽,视处理器而定)
内核提供了下列宏来帮助定义命令:
(1)_IO(type,nr),没有参数命令
(2)_IOR(type,nr,datatype),从驱动中读数据
(3)_IOW(type,nr,datatype),写数据到驱动
(4)_IOWR(type,nr,datatype),双向传输,type和number成员作为参数被传递
实现命令,Ioctl函数实现:
定义好了命令,下一步就是有要实现Ioctl函数了,Ioctl函数的实现包括如下3个技术环节:(1)返回值(2)参数使用(3)命令操作
(1)返回值:Ioctl函数的实现通常是根据命令执行的一个switch语句。但是,命令号不能通过匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)
(2)参数:如何使用Ioctl中的参数?如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。
不需要检测:copy_from_user,copy_to_user,get_user,put_user
需要检测:__get_user,__put_user
用这个函数进行检测:int access_ok(int type,const void *addr,unsigned long size)
第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr参数是要操作的用户内存地址,size是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数等于sizeof(int),access返回一一个布尔值:1是成功(存取没有问题)和0是失败(存取有问题),如果该函数返回失败,则Ioctl应当返回-EFAULT。
定义命令
命令实现
内核等待序列
等待队列:在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队列中取出进程。
Linux2.6内核提供了如下等待队列的操作:
(1)定义等待队列:wait_queue_head_t my_queue
(2)初始化等待队列:init_waitqueue_head(&my_queue)
(3)定义并初始化等待队列:DECLARE_WAIT_QUEUE_HEAD(my_queue)
(4)有条件睡眠:wait_event(queue,condition),当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上;
wait_event_interruptible(queue,condition),当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
int wait_event_killable(wait_queue_t queue,condition),当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
(5)无条件睡眠(老版本,不建议使用)
sleep_on(wait_queue_head_t *q),让进程进入不可中断睡眠,并把它放入等待队列q。
interruptible_sleep_on(wait_queue_head_t *q),让进程进入可中断睡眠,并把它放入等待队列q。
(6)从等待队列中唤醒进程
wake_up(wait_queue_t *q),从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程。
wake_up_interruptible(wait_queue_t *q),从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的进程。
阻塞型设备驱动
功能:前一节我们在设计简单字符驱动程序时,跳过了一个重要的问题,当一个设备无法立刻满足用户的读写请求时应当如何处理?例如,调用read时没有数据可读,但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。应用程序通常不关心这种问题,应用程序只是调用read或write并得到返回值。驱动程序应当(缺省地)阻塞进程,使他进入睡眠,直到请求得到满足。
阻塞方式:在阻塞型驱动程序中,Read实现方式如下:如果进程调用read,但设备没有数据或数据不足,进程阻塞。当新数据到达之后,唤醒被阻塞进程。
非阻塞方式:阻塞方式是文件读写操作的默认模式,但应用程序可以通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式(该标志定义在中,在打开文件时指定)。如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单的返回-EAGAIN,而不会阻塞进程。
实例演示:阻塞型字符驱动
Poll设备操作
Poll方法
什么是Poll方法?功能是什么?
Select系统调用
Select系统调用用于多路监控系统当没有一个文件满足要求时,Select将阻塞调用进程。
Select系统调用(使用方法)
1.将要监控的文件添加到文件描述符集
2.调用Select开始监控
3.判断文件是否发生变化
Poll方法:应用程序常常使用select系统调用,他可能会阻塞进程。这个调用由驱动poll方法实现,原型为
自动创建设备文件