一.linux 驱动程序分为3类:字符设备,块设备,和网络接口设备。
1.字符设备是能够像字节流(比如文件)一样访问的设备,对它的读写是以字节为单位的。比如串口就是在收发数据时候
一个字节一个字节进行的,我们常使用缓冲区存放数据提高效率,但是串口本身无要求。应用程序可以通过设备文件来访问字符设备。
2.块设备上的数据以块的形式存放,比如nand flash上的数据以页为单位。
字符设备和块设备的访问方式无差别,块设备的特别之处
(1)操作硬件的接口实现方式不一样
块设备驱动程序先将用户发来的数据组织成块,再写入涉笔,或从设备中读取若干块数据,再从中挑出用户需要的。
(2)数据块上的数据可以有一定的格式
通常在块设备中按照一定的格式存放数据,不同的文件系统类型就是用来定义这种格式的。
3.网络设备同时具有字符设备和块设备的部分特点,如果说他是字符设备,它的输入、输出却时有结构的,成块的(报文,包,帧),如果说他
是块设备,他的块却不是固定的大小。
二.linux驱动编写的大致流程
1.查看原理图,数据手册,了解设备的操作方法
2.在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始(很少,网络实例也很多)
3.实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件时,内核才能找到相应的驱动程序
4.设计所要实现的操作,比如open close read write等
5.实现中断服务(并不是每个驱动必须)
6.编译该驱动程序到内核中,或者用insmod命令加载
7.测试驱动程序
在配置内核时,如果某个配置项被设为m,就表示它将会被编译成一个模块,扩展名为.ko,可以用insmod命令加载,使用
rmmod命令卸载,使用lsmod命令查看内核中已经加载了哪些模块。
三.linux app将所有设备都看成文件,以操作文件的方式访问设备。应用程序不能直接访问硬件,而是通过统一接口函数调用硬件驱动。
常用系统调用接口:
对于系统调用,驱动都有一个与之对应的函数,这些函数集合在一个file_operations类型的数据结构中。
模块在初始化的时候,将主设备号与file_operations结构体等一起向内核注册。这样linux系统就会根据设备文件的类型
(字符还是块设备),主设备号找到内核中注册的file_operations结构,次设备号供驱动程序自身用来辨别它是同类设备
中的第几个。
(1)app使用库提供的open函数打开代表LED的设备文件
(2)库根据open函数传入的参数执行“swi”指令,这条指令引发cpu异常,进入内核
(3)内核的异常处理函数根据参数找到相应的驱动,返回一个文件句柄给库,然后传到app
(4)app得到句柄后,使用库提供的write等函数发出控制指令
(5)write-->"swi"->进入内核
(6)内核异常处理函数根据这些参数调用驱动程序的相关函数,点亮LED
四.
字符设备驱动
1.确定主设备号
2.定义file_openatios 结构体
{
.open =
.read=
}
3.注册register_dev(主设备号,名字,file_openatios。。)
4.入口函数
5.出口函数
五.
字符设备驱动,通用的几种方式
1.查询方式(不断查询是否有数据到来)-》妈妈不断打开门查看孩子有没有醒-》缺点:占用率太高,太累
2.休眠唤醒(app进入到驱动程序,没数据就休眠。中断服务程序唤醒)妈妈进屋查看有没有醒,没醒就在屋子里和孩子一起睡,然后被唤醒--》缺点:小孩生病了,一直没醒,没法唤醒,妈妈也不了解情况
3.poll机制,相当于加个闹钟(例如30分钟之内孩子醒了就把我唤醒了,如果没唤醒妈妈也会被闹钟唤醒)
4.异步通知(妈妈不想休眠出去干活,孩子醒了打开房门发信号通知妈妈)
以上都只限于内部使用,别人无法通用。
输入子系统(按键等)解决该问题,融入别人的代码
六.
如果用字符驱动的方式,操作块设备驱动
falsh操作
来回跳,机械结构定位,非常浪费时间,效率变低。这种方式进行优化 1 3 2,这样只调动一次
flash 执行两次操作
1.先不执行,放入队列
2.优化后执行。(调整顺序,或合并等)
所以块设备不能直接提供读写函数