linux设备驱动程序5,6章

来源:互联网 发布:如何做到知敬畏守规矩 编辑:程序博客网 时间:2024/05/22 00:33
第五章 字符设备驱动程序的拓展操作
补充设备读写操作的功能之一就是控制硬件,最常用的方法就是ioctl方法。另一种方法就是检查写到设备中的数据流,使用特殊序列作为控制命令

ioctl与设备相关,允许应用程序访问被驱动硬件的特殊功能,

ioctl
系统调用需要有明确的参数个数

各个命令需要的参数个数不同,有的还需要指针做参数(可以向ioctl传递任意数目数据,设备可以从用户空间接受任意大小的数据)

选择ioctl命令
在编写代码之前,可以选择需要对应不同命令的命令号

命令号由多个位字段决定

头文件定义位字段:类型,基数,传送方向,参数的尺度

如果应用程序和驱动程序使用了相同号码,在驱动程序中实现switch语句就可以了

新的定义号码:类型  号码  方向  尺寸

每个宏都对应一种可能的数据传输方向,其他字段通过参数传递

头文件还定义了解宏码

实现整数参数传递:(1)通过指针(2)显示数值

交换:给出方向字段的所有可能值
移位:将告知和查询操作组合在一起

显式的命令基数只是用来区分命令

参数cmd的值内核并没有使用,而且也不可能使用

返回值
ioctl的实现通常根据命令号码的一个switch语句

预定义命令
ioctl系统调用大部分都用于操作设备,但有一些由内核识别的

预定义命令分3组:任何文件,普通文件,文件系统相关的

当用指针引用用户空间,首先确定他指向了合理的用户空间

验证读只检查地址是否合法的

返回值0意味着成功,负值代表错误

scull源代码在switch之前分析ioctl号码的位字段

scull的ioctl实现只传递设备的可配置参数

非ioctl设备控制
有时通过向设备自身发送写控制顺序,可以更好地完成对设备的控制

通过打印控制的缺点是:给设备增加了策略限制

写控制特别适合这样的设备:不传输数据,仅响应命令

阻塞型IO
read的一个问题是:当尚未有数据可读时,而又没有到文件尾时如何处理:处于睡眠状态等待数据

睡眠与唤醒
当进程等待数据时,他要进入睡眠状态,以便其他进程可以使用计算资源

唤醒进程需要使用和睡眠进程一样的队列

wake_up唤醒睡在队列上的任意进程,wake_up_interruptible则唤醒中断进程

编写可重入进程
进程睡眠后,驱动程序仍是活的,可以由另一进程调用

如果需要大规模的保存数据,可以把指针保存在局部变量里,并用kmalloc获取实际存储空间

要把所有调用了sleep_on的函数写成可重入的,包括所有在这个函数调用轨迹的函数

等待队列
等待队列是个循环链表,最后一个结构指向第一个结构

进程每次睡在队列上,描述其睡眠的数据结构驻留在与进程相对应的不兑换的堆栈页中

阻塞型和非阻塞型操作
O_NONBLOCK标志:打开-非阻塞,可以在打开时复位

一般read和write假设输入和输出缓冲区,减少了用户级/内核级的转换和上下文切换的数目

适合与设备的输出缓冲区尺寸当然和设备相关

设置了此标志,若是在无数据就绪时调用了read,在缓冲区没有空间时调用了write,系统返回-EAGAIN

非阻塞型操作立即返回,允许应用查询数据

open一个设备需要一个很长时间的初始化,那么也会返回-EAGAIN

样例实现scullpipe
是scull设备中的一部分

在驱动程序内部,阻塞在read调用的进程在数据达到时被唤醒,通常发出中断提醒

无需特殊硬件,就可以执行scull,scull目标与传统驱动程序完全不同

可以使用另一个进程的唤醒读函数

设备驱动程序使用一个包含两个等待队列和一个缓冲区的设备结构

write从不完全填充缓冲区,总是留下至少一个字节的空洞

普通程序不执行非阻塞操作,有一个nbtext可以用来测试非阻塞型操作

Select
使用非阻塞型IO时,应用程序经常要利用select调用,涉及设备文件时,还依赖于一个设备方法。还可以用来实现不同源输入的多路调用

当选择的程序没有一个可以接受或者返回函数时,进程才真正进入睡眠状态

异常设备的涵义与设备有关

如实看到程序尾了,还没有数据返回,就设一个阻塞型open

select调用的目的是事先判断是否有IO操作会阻塞,是read和write的延伸。还可以让程序等待多个数据流

从设备读取数据
如果输入缓冲区有数据的话,即使比请求的要少,而且保证剩余数据马上就到,read调用不应该延迟
如果输入缓冲区无数据的话,read必须阻塞,select报告数据不可读

向设备写数据
如果输出缓冲区有空间,write不做任何延迟返回数据,哪怕数据少于所请求的,但至少要有一个字节

若果设备报告可以写,write调用要保持一致,非阻塞

fsync调用应该仅在程序已经完全刷新了之后才能返回

异步触发
打开异步触发机制
(1)指定进程是文件的属主(2)fcntl命令设置设备的FASYNC标志

并不是所有设备都支持异步触发,你也可以选择是否异步触发

我们必须在文件关闭时调用fasync方法,从活动异步读者链表中去除被关闭的文件

定位设备
如果设备中没有lseek,内核相应的默认实现是从文件的起始处和当前位置开始定位

scull处理一个精确定义的数据区

设备文件的访问控制
不经未授权的用户不允许使用设备,而且有时只能一个授权用户打开设备

如果open系统调用将请求转发给驱动程序,open就会成功

独享设备
提供访问控制的话,只允许一个进程打开设备(独享)

但是独享对于设备驱动程序非常的易于实现

参访打开标志的最佳场所是设备结构,他从属于设备

限制每次只有一个用户访问
如果没有其他人能够控制设备,只允许一个使用有访问权

open调用在首次打开时授权,但他记下设备的属主。意味着用户可以多次打开该设备,允许合作进程无缝连接

阻塞型打开替代EBUSY
设备不能访问时返回一个错误

可以用返回EBUSY代替阻塞,然后release负责唤醒任何等待的进程

在打开时克隆设备
管理访问控制的另一作用是:在进程打开设备时,创建设备的一个私有副本

内核代码可以轻松的修改从应用角度看的世界(计算机)

第六章时间流
内核中的时间间隔
操作系统通过时钟中断确定时间间隔

时钟中断发生时,jiffies就+1

处理器调度过于频繁的话,系统会不稳定

如果改变了HZ的定义,那么必须重新编译和安装所有的模块

内核中的一切都和HZ有关

时钟中断的最好方法是保留HZ的缺省值

获取当前时间
一般用jiffies获取时间驱动程序一般不需要知道强上时间,cron或at才需要

用户程序可以把墙上时间换成系统时钟

延迟执行
长延时可以用while等待,什么也不做(精度不高时)

更好的延迟方法:允许其他的程序在延时间隔里运行

还可以在内核态下让内核睡眠,只要没有系统时间唤醒进程使他离开等待队列,那么一旦时间达到timeout值,调度器就会唤醒睡眠进程

timeout可以实现阻塞的系统调用或是用来计算延时

短延时
使用内核函数udelay(是个忙等待函数,等待过程中不能执行其它函数任务)

任务队列
不想占用中断还想延时任务:任务队列  内核定时器

任务队列是任务的一张列表,每个任务用一个函数指针和参数表示,任务运行时,接受一个void指针,返回值也为void
类型

任务队列在安全时间运行

中断时间取决于内核中的全局变量intr-count,当它不为0,执行代码和其他部分是异步的。这些异步代码是不能使当前系统进入睡眠

当intr-count非0时,不能激活调度器

预定义的任务队列
延时任务的简单方法是使用内核维护的任务队列

调度器队列
tq_scheduler:该队列中的任务不会再中断时间内运行,因此少了很多限制

定时器队列
定时器队列在中断时间内执行

一个任务可以把自己再插回到原来的队伍中

立即队列
mark_bh标志该处理程序是活跃的

intr-count+1后,马上执行

运行自己的任务队列
内核不会自动处理定制的任务队列,定制的任务队列要程序员自己处理

内核定时器
内核中的最终计时资源还是定时器

你可以制定任务在什么时候被完成,但不能确定队列上的任务何时执行

linux有新定时器和旧定时器(32个静态定时器)

新的定时器被组织成双向链接表,可以加入任意多的定时器

定时器总是可以正确的超时,即使处理器正在执行系统调用

运行在内核模式的系统不会被调出,但时钟中断是例外,与当前进程无关


原创粉丝点击