13.字符设备驱动开发及相关概念
本实验目标
对Linux内核、驱动有初步的认识
假如对此部份不太熟悉的同学可以下载国嵌的linux视频瞧瞧,非有很大帮助。
1应用程序、库、内核、驱动程序的关系
从上到下,一个软件系统可以分为:应用程序、库、操作系统(内核)、驱动程序。开发人员可以专注于自己熟悉的部份,对于相邻层,只要了解它的插口,无需关注它实现的细节。
以照亮一个LED为例linux下内核与应用程序之间的通信,这4层软件的协作关系如下,如图1所示。
图1
1).应用程序使用库提供的open函数打开代表LED的设备文件。
2).库按照open函数传入的参数执行“SWI”指令,该指令会导致CPU异常,步入内核。3).内核的异常处理函数依据这种参数找到相应的驱动程序,返回一个文件句柄给库,从而返回给应用程序。
4).应用程序得到文件句柄后,使用库提供的write或ioclt函数发出控制命令。
5).库按照write和ioclt函数传人的参数执行“swi”指令,这条指令会导致CPU异常,步入内核。
6).内核的异常处理函数依据这种参数调用驱动程序的相关函数,照亮LED。
库(例如glibc)给应用程序提供的open、read、write、ioctl、mmap等插口函数被称为系统调用,它们都是设置好相关寄存器后,执行某条指令引起异常步入内核。除系统调用插口外,库还提供其他函数,例如字符串处理函数(strcpy、strcmp等)、输入/输出函数(scanf、printf等)、数学库,还有应用程序的启动代码等。
在异常处理函数中,内核会依照传入的参数执行各类操作,例如按照设备文件名找到对应的驱动程序,调用驱动程序的相关函数等。通常来说,当应用程序调用open、read、
write、ioctl、mmap等函数后,将会使用驱动程序中的open、read、write、ioctl、mmap函数来执行相关操作,例如初始化、读、写等。实际上,内核和驱动程序之间并没有界线,由于驱动程序最终是要编进内核去的:通过静态链接和动态加载。
从里面操作LED的过程可以晓得,与应用程序不同,驱动程序从不主动运行,它是被动的:按照应用程序的要求进行初始化,依照应用程序的要求进行读写。驱动程序加载进内核时,只是告诉内核“我在这儿,我能做这种工作”,至于那些“工作”何时开始,取决于应用程序。其实,这不是绝对的,例如用户完全可以写一个系统时钟触发的驱动程序,让它手动照亮LED。
在Linux系统中linux服务器代维,应用程序运行于“用户空间”,拥有MMU的系统才能限制应用程序的权限(例如将它限制于某个显存块中),这可以防止应用程序的错误使整个系统崩溃。而驱动程序运行于“内核空间”,它是系统“信任”的一部份,驱动程序的错误有可能导致整个系统崩溃。
2Linux驱动程序分类
Linux的外设可以分为3类:字符设备、块设备和网路插口。
字符设备是能否像字节流(例如文件)一样被访问的设备,就是说对它的读写是以字节为单位的。例如并口在进行收发数据时就是一个字节一个字节的进行的,我们可以在驱动程序内部使用缓冲区来储存数据以提升效率,而且并口本身对这并没有要求。字符设备的驱动程序中实现了open、close、read、write等系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0等)来访问字符设备。
块设备上的数据以块的方式储存,例如NANDFlash上的数据就是以页为单位储存的。块设备驱动程序向用户层提供的插口与字符设备一样,应用程序也可以通过相应的设备文件(例如/dev/mtdblock0、/dev/hda1等)来调用open、close、read、write等系统调用,与块设备传送任意字节的数据。对用户而言,字符设备和块设备的访问方法没有差异。块设备驱动程序的非常之处如下。
1).操作硬件的插口实现方法不一样。
块设备驱动程序先将用户发来的数据组织成块,再写入设备;或从设备中读出若干块数据,再从中挑出用户须要的。
2).数据块上的数据可以有一定的格式。
一般在块设备中根据一定的格式储存数据,不同的文件系统类型就是拿来定义这种格式的。内核中,文件系统的层次坐落块设备驱动程序里面,这意味着块设备驱动程序不仅向用户层提供与字符设备一样的插口外,还要向内核其他部件提供一些插口,这种插口用户是看不到的。这种插口促使可以在块设备上储存文件系统,挂载块设备。
网路插口同时具有字符设备、块设备的部份特征,难以将它划入这两类中:假如说它是字符设备linux下内核与应用程序之间的通信,他的输入/输出却是有结构的、成块的(报文、包、帧);假如说它是块设备,它的“块”又不是固定大小的,大到数百甚至数千字节,小到几字节。
UNIX式的操作系统访问网路插口的方式是给它们分配一个唯一的名子(例如eth0),
但这个名子在文件系统中(例如/dev目录下)不存在对应的节点项。应用程序、内核和网路驱动程序间的通讯完全不同于字符设备、块设备,库、内核提供了一套和数据包传输相关的函数,而不是open、read、write等。
3Linux驱动程序开发步骤
Linux内核就是由各类驱动组成的,内核源码中有大概85%是各类驱动程序的代码。内核中驱动程序种类齐全,可以在同类驱动的基础上进行更改以符合具体单板。
编撰驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。例如,x86构架的内核对IDE硬碟的支持十分健全:首先通过BIOS得到硬碟的信息,或则使用默认I/O地址去枚举硬碟,之后辨识分区、挂载文件系统。对于其他构架的内核红帽linux系统下载,只是要指定了硬碟的访问地址和中断号,旁边的枚举、识别和挂接的过程完全是一样的。其实更改的代码不超过10行,耗费精力的地方在于:了解硬碟驱动的框架,找到更改的位置。
编撰驱动程序还有好多须要注意的地方,例如:驱动程序可能同时被多个进程使用,这须要考虑并发的问题;尽可能发挥硬件的作用以提升性能。诸如在硬碟驱动程序中既可以使用DMA也可以不用,使用DMA时程序比较复杂,而且可以提升效率;处理硬件的各类异常情况(虽然效率低),否则出错时可能造成整个系统崩溃。
通常来说,编撰一个Linux设备驱动程序的大致流程如下。
1).查看原理图、数据指南,了解设备的操作技巧。
2).在内核中找到相仿的驱动程序,以它为模板进行开发,有时侯须要从零开始。
3).实现驱动程序的初始化:例如向内核注册这个驱动程序,这样应用程序传入文件名时,内核能够找到相应的驱动程序。
4).设计所要实现的操作,例如open、close、read、write等函数。
5).实现中断服务(中断并不是每位设备驱动所必须的)。
6).编译该驱动程序到内核中,或则用insmod命令加载。
7).测试驱动程序。
4驱动程序的加载和卸载
可以将驱动程序静态编译进内核中,也可以将它作为模块在使用时再加载。在配置内核时,假如某个配置选项被设为m,就表示它将会被编译成一个模块。在2.6的内核中,模块的扩充名为.ko,可以使用insmod命令加载,使用rmmod命令卸载,使用lsmod命令查看内核中早已加载了什么模块。
当使用insmod加载模块时,模块的初始化函数被调用,它拿来向内核注册驱动程序;当使用rmmod卸载模块时,模块的清理函数被调用。在驱动代码中,这两个函数要么取固定的名子:init_module和cleanup_module,要么使用以下两行来标记它们(假定初始化函数、清除函数为my_init和my_cleanup)。
moudle_init(my_init);module_exit(my_cleanup);