1TMTHEARCHITECTUREFORTHEDIGITALWORLD第七章linux设备驱动程序开发2TM2ARM及Thumb指令集7.1设备驱动概述设备驱动可以理解为操作系统的一部分,对于一个特定的硬件设备来说,其对应的设备驱动程序是不同的。比如网卡、声卡、键盘、鼠标、显卡等。对于操作系统来说,挂接的设备越多,所需要的设备驱动程序也越多。操作系统本身并没有对种类繁多的硬件设备提供持久不变的“设备驱动”,也就是说操作系统在没有设备驱动程序支持下是无法正常支配硬件行为的。这个时候就需要独立开发一套适合自己产品的设备驱动。正是操作系统留下了扩展设备驱动的接口,才有了现在支持各种应用场合的硬件设备的蓬勃发展。对于嵌入式开发,更没有通用的驱动程序可以便用。因此,驱动程序开发是整个嵌入式系统设计过程中必不可少的一部分。3TM3ARM及Thumb指令集7.1设备驱动概述7.1.1设备驱动程序功能设备驱动程序是Linux内核的重要组成部分。像操作系统的其他部分一样,驱动程序在一个高优先级的环境下工作,如果发生错误则可能会引发严重的问题。设备驱动程序控制了操作系统和硬件设备之间的交互,完成以下功能:对设备初始化和释放;对设备进行管理,包括实时参数设置,以及提供对设备的操作接口;读取应用程序传送给设备文件的数据或者回送应用程序请求的数据;检测和处理设备出现的错误。4TM4ARM及Thumb指令集应用程序硬件层驱动程序文件系统整个设备管理子系统的结构如图7.1所示。5TM5ARM及Thumb指令集7.1设备驱动概述2.Linux设备驱动程序接口系统调用是操作系统内核与应用程序之间的接口,驱动程序则是操作系统内核与机器硬件的接口。设备驱动程序能够直接访问硬件的代码,必须为应用程序提供系统调用。以便应用程序能访问设备。在LINUX中,主要有三种设备即:字符设备.块设备和网络设备,与此相关主要有三类设备驱动程序,字符设备驱动程序,块设备驱动程序和网络设备驱动程序.他们的系统调用是一致的,采用统一的接口(在数据结构file_operations中)。应用程序使用设备就像使用读写普通的文件一样方便,使用相同的open(),close(),read(),write()等,真正做到了与设备无关。6TM6ARM及Thumb指令集7.1.2设备类型Linux中的设备可以分为三类:字符设备块设备网络设备一个运行的linux系统,当前使用的设备可以通过文件/proc/devices查看。7TM7ARM及Thumb指令集驱动程序中涉及的几个概念模块的概念Linux可以以模块的形式加载设备类型,通常来说一个模块对应实现一个设备驱动,因此是可以分类的。一般一个设备驱动对应一类设备的模块方式,这样便于多个设备的协调工作也利于应用程序的开发和扩展。Linux的驱动开发调试有两种方法:(1)直接编译到内核,再运行新的内核来测试;(2)编译为模块的形式,单独加载运行调试。通常情况下设备驱动的模块动态加载更为普遍,开发人员不必在调试过程中频繁启动机器就能完成设备驱动的开发工作。8TM8ARM及Thumb指令集模块加载与卸载模块方式调试:(1)编译的模块直接插入内核:用insmod工具;(2)从内核中卸载模块:用rmmod。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用module_init()和module_exit()函数,还可以用lsmod命令来查看所有已加载的模块的状态。Linux中模块可以用C语言编写,用gcc命令编译成模块*.ko9TM9ARM及Thumb指令集编写HelloWorld模块#includelinux/init.h#includelinux/module.h#includelinux/kernel.hMODULE_LICENSE(DualBSD/GPL);staticint__inithello_init(void){printk(KERN_ALERTHello,World!\n);return0;}staticvoid__exithello_exit(void){printk(KERN_ALERTGoodbye,world!\n);}module_init(hello_init);module_exit(hello_exit);定义__init、__exit、module_init、module_exit所必需的宏定义所有模块相关的宏,比如MODULE_LICENSEmodule_init()、module_exit()为内核特殊宏,分别用来定义模块被装载和卸载时调用的函数定义printk()中相关的宏,比如KERN_ALERT10TM10ARM及Thumb指令集编写HelloWorld模块#includelinux/init.h#includelinux/module.h#includelinux/kernel.hMODULE_LICENSE(DualBSD/GPL);staticinthello_init(void){printk(KERN_ALERTHello,World!\n);return0;}staticvoidhello_exit(void){printk(KERN_ALERTGoodbye,world!\n);}module_init(hello_init);module_exit(hello_exit);内核函数printk被定义在linux内核中,他类似于标准C函数printf().用MODULE_LICENSE宏来声明该模块的许可协议,声明为BSD和GPL双重协议许可11TM11ARM及Thumb指令集编写HelloWorld模块MODULE_AUTHOR(“BENSON”);//可选的MODULE_DESCRIPTION(STUDY_MODULE);//可选的12TM12ARM及Thumb指令集Printk函数printk(KERN_ALERTHello,World!\n);问:为什么不用printf()函数呢?答:在讲交叉编译工具链的时候,曾经讲到在编译内核的时候不能使用标准的C库和其他函数库的支持,所以不能使用printf()库函数内核有自己的打印函数printk(),它通过自身的运行而不需要C库的帮助.在使用insmod装载之后,内核与内核公共函数和变量进行连接,从而可以使用printk()函数.其中KERN_ALERT宏是标记printk()打印出字符的优先等级的,通常有八种消息级别,定义在include/linux/kernel.h的文件中.13TM13ARM及Thumb指令集printk(日志级别“消息文本”);这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。#defineKERN_EMERG0/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/#defineKERN_ALERT1/*报告消息,表示必须立即采取措施*/#defineKERN_CRIT2/*临界条件,通常涉及严重的硬件或软件操作失败*/#defineKERN_ERR3/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/#defineKERN_WARNING4/*警告条件,对可能出现问题的情况进行警告*/#defineKERN_NOTICE5/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/#defineKERN_INFO6/*提示信息,如驱动程序启动时,打印硬件信息*/#defineKERN_DEBUG7/*调试级别的消息*/14TM14ARM及Thumb指令集linux2.4编写HelloWorld模块的MakefileEXEC=helloOBJS=hello.oSRC=hello.cINCLUDE=/usr/src/linux/fs2410_2.6.8/includeCC=/usr/local/3.3.2/bin/arm-linux-gccLD=/usr/local/3.3.2/bin/arm-linux-ldMODCFLAGS=-O2-Wall-D__KERNEL__-DMODULE-I$(INCLUDE)-march=armv4tLDFLAGS=-rall:$(EXEC)$(EXEC):$(OBJS)$(LD)$(LDFLAGS)-ohellohello.o%.o:%.c$(CC)$(MODCFLAGS)-mapcs-c$-o$@clean:-rm-f$(EXEC)*.o*~core$@:表示完整的目标文件,包括扩展名$:比目标文件更新的依赖文件$^:表示所有的依赖文件最后生成hello.o和hello用来指定编译内核时所用的编译选项15TM15ARM及Thumb指令集Linux2.6编写HelloWorld模块的MakefileP230ifneq($(KERNELRELEASE),)obj-m:=hello.oelseKDIR=/lib/modules/2.6.8.1-ptx1/buildPWD:=$(shellpwd)default:$(MAKE)-C$(KDIR)M=$(PWD)modulesEndif指定了内核源代码的位置,其中保存有内核的顶层makefile文件obj-m表示要由hello.c文件编译得到hello.o,并作为模块编译obj-y则表示要连接进内核obj-x则目标不会被编译。M=$(PWD)指明存放hello.c的路径。16TM16ARM及Thumb指令集17TM17ARM及Thumb指令集M=$(PWD)指明存放hello.c的路径。18TM18ARM及Thumb指令集(1)先让FS2410P教学平台进入Linux环境,利用超级终端来显示,Linux进入命令行的模式下。(2)输入命令cd/tmp,进入tmp目录,因为/tmp是在SDRAM中,可以放数据。(3)将hello.ko下载到/tmp目录下。采用的rz命令来传输的,rz命令是通过Zmodem协议来传输的。先在教学平台的下输入rz命令(Linux环境下),接着点击超级终端的“传送”—“发送文件”,在弹出的对话框中设置如下:19TM19ARM及Thumb指令集然后点击发送。数据传输完后,再回车,接着通过ls来查看/tmp目录下是否有hello.ko文件。(4)hello.ko下载成功后,接下来我们要进行真正的加载和运行的工作了。改变hello.ko的属性,命令如下:chmod755hello.ko(5)加载hello.ko模块:insmod./hello.ko这时我们就可以看到期待已久的Hello,world了。(6).卸载驱动模块:rmmodhello.ko卸载时就可在屏幕上看到如下信息:Goodbye,world20TM20ARM及Thumb指令集驱动程序中涉及的几个概念设备驱动程序的设备号和入口点Linux系统通过设备号来区分不同设备。设备号由两部分组成:主设备号和次设备号。主设备号标识设备对应的驱动程序。系统中不同的设备可以有相同的主设备号,主设备号相同的设备使用相同的驱动程序。次设备号用来区分具体驱动程序的实例。一个主设备号可能有多个设备与之对应,这多个设备正是在驱动程序内通过次设备号来进一步区分的。次设备号只能由设备驱动程序使用,内核的其他部分仅将它作为参数传递给驱动程序。在/proc/devices中列出了系统中处于活动状态设备的主设备号,所谓的活动状态是指与该设备对应的设备驱动已经被系统内核装载。21TM21ARM及Thumb指令集/dev/字符为c表示字符设备,为b表示块设备两个数字对应主设备号和次设备号对于现有Linux操作系统,/dev目录是必不可少的,这个目录包含了所有Linu