




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第七章linux设备驱动程序开发第一页,共八十二页。7.1设备驱动概述设备驱动可以理解为操作系统的一部分,对于一个特定的硬件设备来说,其对应的设备驱动程序是不同的。比如网卡、声卡、键盘、鼠标、显卡等。对于操作系统来说,挂接的设备越多,所需要的设备驱动程序也越多。操作系统本身并没有对种类繁多的硬件设备提供持久不变的“设备驱动”,也就是说操作系统在没有设备驱动程序支持下是无法正常支配硬件行为的。这个时候就需要独立开发一套适合自己产品的设备驱动。正是操作系统留下了扩展设备驱动的接口,才有了现在支持各种应用场合的硬件设备的蓬勃发展。对于嵌入式开发,更没有通用的驱动程序可以便用。因此,驱动程序开发是整个嵌入式系统设计过程中必不可少的一部分。第二页,共八十二页。7.1设备驱动概述7.1.1设备驱动程序功能设备驱动程序是Linux内核的重要组成部分。像操作系统的其他部分一样,驱动程序在一个高优先级的环境下工作,如果发生错误则可能会引发严重的问题。设备驱动程序控制了操作系统和硬件设备之间的交互,完成以下功能:对设备初始化和释放;对设备进行管理,包括实时参数设置,以及提供对设备的操作接口;
读取应用程序传送给设备文件的数据或者回送应用程序请求的数据;检测和处理设备出现的错误。第三页,共八十二页。应用程序硬件层驱动程序文件系统整个设备管理子系统的结构如图7.1所示。第四页,共八十二页。7.1设备驱动概述2.Linux设备驱动程序接口系统调用是操作系统内核与应用程序之间的接口,驱动程序则是操作系统内核与机器硬件的接口。设备驱动程序能够直接访问硬件的代码,必须为应用程序提供系统调用。以便应用程序能访问设备。在LINUX中,主要有三种设备即:字符设备.块设备和网络设备,与此相关主要有三类设备驱动程序,字符设备驱动程序,块设备驱动程序和网络设备驱动程序.他们的系统调用是一致的,采用统一的接口(在数据结构
中)。应用程序使用设备就像使用读写普通的文件一样方便,使用相同的open(),close(),read(),write()等,真正做到了与设备无关。第五页,共八十二页。7.1.2设备类型
Linux中的设备可以分为三类:字符设备块设备网络设备一个运行的linux系统,当前使用的设备可以通过文件/proc/devices查看。
第六页,共八十二页。驱动程序中涉及的几个概念模块的概念Linux可以以模块的形式加载设备类型,通常来说一个模块对应实现一个设备驱动,因此是可以分类的。一般一个设备驱动对应一类设备的模块方式,这样便于多个设备的协调工作也利于应用程序的开发和扩展。Linux的驱动开发调试有两种方法:(1)直接编译到内核,再运行新的内核来测试;(2)编译为模块的形式,单独加载运行调试。通常情况下设备驱动的模块动态加载更为普遍,开发人员不必在调试过程中频繁启动机器就能完成设备驱动的开发工作。第七页,共八十二页。模块加载与卸载模块方式调试:(1)编译的模块直接插入内核:用insmod工具;(2)从内核中卸载模块:用rmmod。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用module_init()和module_exit()函数,还可以用lsmod命令来查看所有已加载的模块的状态。Linux中模块可以用C语言编写,用gcc命令编译成模块*.ko第八页,共八十二页。编写HelloWorld模块#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>MODULE_LICENSE("DualBSD/GPL");staticint__init
hello_init(void){printk(KERN_ALERT"Hello,World!\n");return0;}staticvoid__exit
hello_exit(void){printk(KERN_ALERT"Goodbye,world!\n");}module_init(hello_init);module_exit(hello_exit);定义__init、__exit、module_init、
module_exit所必需的宏定义所有模块相关的宏,比如MODULE_LICENSEmodule_init()、module_exit()为内核特殊宏,分别用来定义模块被装载和卸载时调用的函数定义printk()中相关的宏,比如KERN_ALERT第九页,共八十二页。编写HelloWorld模块#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>MODULE_LICENSE("DualBSD/GPL");staticinthello_init(void){printk(KERN_ALERT"Hello,World!\n");return0;}staticvoidhello_exit(void){printk(KERN_ALERT"Goodbye,world!\n");}module_init(hello_init);module_exit(hello_exit);内核函数printk被定义在linux内核中,他类似于标准C函数printf().用MODULE_LICENSE宏来声明该模块的许可协议,声明为BSD和GPL双重协议许可第十页,共八十二页。编写HelloWorld模块MODULE_AUTHOR(“BENSON”);//可选的MODULE_DESCRIPTION("STUDY_MODULE");//可选的第十一页,共八十二页。Printk函数printk(KERN_ALERT"Hello,World!\n");问:为什么不用printf()函数呢?答:在讲交叉编译工具链的时候,曾经讲到在编译内核的时候不能使用标准的C库和其他函数库的支持,所以不能使用printf()库函数内核有自己的打印函数printk(),它通过自身的运行而不需要C库的帮助.在使用insmod装载之后,内核与内核公共函数和变量进行连接,从而可以使用printk()函数.其中KERN_ALERT宏是标记printk()打印出字符的优先等级的,通常有八种消息级别,定义在include/linux/kernel.h的文件中.第十二页,共八十二页。printk(日志级别“消息文本”);这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/#defineKERN_DEBUG"<7>"/*调试级别的消息*/
第十三页,共八十二页。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用来指定编译内核时所用的编译选项第十四页,共八十二页。Linux2.6编写HelloWorld模块的MakefileP230ifneq($(KERNELRELEASE),)obj-m:=hello.oelseKDIR=/lib/modules/-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的路径。第十五页,共八十二页。第十六页,共八十二页。M=$(PWD)指明存放hello.c的路径。第十七页,共八十二页。(1)先让FS2410P教学平台进入Linux环境,利用超级终端来显示,Linux进入命令行的模式下。(2)输入命令cd/tmp,进入tmp目录,因为/tmp是在SDRAM中,可以放数据。(3)将hello.ko下载到/tmp目录下。采用的rz命令来传输的,rz命令是通过Zmodem协议来传输的。先在教学平台的下输入rz命令(Linux环境下),接着点击超级终端的“传送”—>“发送文件”,在弹出的对话框中设置如下:第十八页,共八十二页。然后点击发送。数据传输完后,再回车,接着通过ls来查看/tmp目录下是否有hello.ko文件。(4)hello.ko下载成功后,接下来我们要进行真正的加载和运行的工作了。改变hello.ko的属性,命令如下:chmod755hello.ko(5)加载hello.ko模块:insmod./hello.ko
这时我们就可以看到期待已久的Hello,world了。(6).卸载驱动模块:rmmodhello.ko卸载时就可在屏幕上看到如下信息:
Goodbye,world
第十九页,共八十二页。驱动程序中涉及的几个概念设备驱动程序的设备号和入口点Linux系统通过设备号来区分不同设备。设备号由两部分组成:主设备号和次设备号。主设备号标识设备对应的驱动程序。系统中不同的设备可以有相同的主设备号,主设备号相同的设备使用相同的驱动程序。次设备号用来区分具体驱动程序的实例。一个主设备号可能有多个设备与之对应,这多个设备正是在驱动程序内通过次设备号来进一步区分的。次设备号只能由设备驱动程序使用,内核的其他部分仅将它作为参数传递给驱动程序。在/proc/devices中列出了系统中处于活动状态设备的主设备号,所谓的活动状态是指与该设备对应的设备驱动已经被系统内核装载。第二十页,共八十二页。/dev/字符为c表示字符设备,为b表示块设备两个数字对应主设备号和次设备号对于现有Linux操作系统,/dev目录是必不可少的,这个目录包含了所有Linux系统所知道的字符设备,块设备和网络设备第二十一页,共八十二页。/proc/devices
p233在/proc/devices中列出了系统中处于活动状态设备的主设备号第二十二页,共八十二页。dev_t类型dev_t类型内核用dev_t类型来保存设备编号,dev_t是个32位的数,12位表示主设备号,20为表示次设备号。在实际使用中,是通过中定义的宏来转换格式。获得dev_t的主设备号和次设备号,应使用以下的宏,例如:dev_tdev;MAJOR(dev);/*主设备号*/MINOR(dev);/*次设备号*/如果要将主设备号、次设备号转换成dev_t类型,则使用以下的宏,例如:Intmajor=4,minor=3;MKDEV(intmajor,intminor)第二十三页,共八十二页。设备入口点p234设备入口点设备入口点也可以理解为“设备文件句柄”,一个设备的入口点和磁盘上的普通文件系统一样,可以删除(rm),移动(mv)和复制(cp)等。我们可以在文件系统中使用mknod命令创建一个设备入口点。在文件系统中创建了设备入口点并没有代表响应的设备驱动和硬件已经准备好,只是代表了和设备驱动通信的一部分。下面给一个创建字符设备入口点的实例:mknod/dev/testCharc
1000其中c代表字符设备,如果想创建块设备则用b代替c。参数100代表该设备的主设备号,0代表该设备的次设备号。第二十四页,共八十二页。设备入口点创建设备的入口点之后,可以通过如下命令查看:ls–l/dev/testChar删除设备入口点也非常简单:rm/dev/testChar第二十五页,共八十二页。用户空间和内核空间p220设备驱动运行在内核空间(在一些操作系统的书籍里面称为“系统态”),而应用程序则运行在用户空间(也可称为“用户态”)。设备驱动程序运行在内核空间比应用程序执行的优先级要高很多。内核态具有最高的运行级别,可以做任何事。应用程序则运行在最低级别的用户态,在这一级别处理器禁止对硬件的直接访问和对内存的未授权访问。内核空间和用户空间分别引用不同的内存映射,也就是程序代码使用不同的地址空间.Linux操作系统通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。第二十六页,共八十二页。字符设备p218字符设备:字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备。如果一个硬件设备是以字符流的方式被访问的话,那就应该将它归于字符设备;字符设备是linux最简单的设备,可以像文件一样访问,区别主要在于:普通文件可以来/回读/写,而字符设备只能是顺序读/写.我们可以对其调用打开、读取、写和关闭。初始化字符设备时,驱动程序向linux登记,并在字符设备向量中增加一个device_struct数据结构条目,这个设备的主设备标识符(tty的主设备号为4)用作这个向量表的索引第二十七页,共八十二页。系统调用系统调用字符设备是linux最简单的设备,可以像文件一样访问.所以在用户空间中,系统进程(应用程序)对设备文件的操作通过系统调用来完成,如open、read、write、close等。下面通过一个简单的例子来了解一下系统调用的使用:第二十八页,共八十二页。一个简单的应用程序#defineDEVICE_GPIOTEST“/dev/gpio”//设备入口点intmain(intargc,char*argv[]){intfd;intval=-1;fd=open(DEVICE_GPIOTEST,O_RDONLY);//打开设备if(fd<0)//fd为返回的设备文件描述字handle
{perror("cannotopendevice");exit(1);}while(1){printf("pleaseselectnumbertorunprogram\n");printf("1:ledon\n2:quit");第二十九页,共八十二页。一个简单的应用程序scanf("%d",&val);if(val==1)ioctl(fd,1,10);/*主要用于对设备进行读写之外的其他控制*/elseif(val==2){close(fd);/*关闭设备*/}}return0;}第三十页,共八十二页。设备驱动和文件系统的关系
●open——打开设备准备I/O操作。
open——打开设备准备I/O操作。其调用格式为:intopen(char*,intaccess);
该函数返回文件描述字handle,如果返回值小于0,表示打开设备文件失败第三十一页,共八十二页。close——close()函数的作用是关闭由open()函数打开的文件,其调用格式为:intclose(inthandle);该函数关闭文件描述字handle相连的文件。
read——从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。write——往设备上写数据,对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。write()函数的调用格式为:intwrite(inthandle,void*buf,intcount);write()函数把count个字节从buf指向的缓冲区写入与handle相连的文件中,返回值为实际写入的字节数。7.1.1设备驱动和文件系统的关系第三十二页,共八十二页。ioctl——主要用于对设备进行读写之外的其他控制。用户空间的ioctl函数的原型为:intioctl(inffd,intcmd,…)其中的…代表可变数目的参数表,实际中是一个可选参数,一般定义为:intioctl(inffd,intcmd,char*argp)其中fd为文件描述字handlecmd为命令字,不同的命令字对应不同的操作.第三十三页,共八十二页。7.1.1设备驱动和文件系统的关系
●open——打开设备准备I/O操作。
open——打开设备准备I/O操作。该函数返回文件描述字handle
其调用格式为:intopen(char*,intaccess);
第三十四页,共八十二页。close——close()函数的作用是关闭由open()函数打开的文件,其调用格式为:intclose(inthandle);该函数关闭文件描述字handle相连的文件。
read——从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。write——往设备上写数据,对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。write()函数的调用格式为:intwrite(inthandle,void*buf,intcount);write()函数把count个字节从buf指向的缓冲区写入与handle相连的文件中,返回值为实际写入的字节数。7.1.1设备驱动和文件系统的关系第三十五页,共八十二页。ioctl——主要用于对设备进行读写之外的其他控制。用户空间的ioctl函数的原型为:intioctl(inffd,intcmd,…)其中的…代表可变数目的参数表,实际中是一个可选参数,一般定义为:intioctl(inffd,intcmd,char*argp)驱动程序中定义的ioctl方法原型为:int(*ioctl)(structinode*inode,structfile*intcmd,unsignedlongarg)inode和filp两个指针对应应用程序传递的文件描述符fd、cmd不会被修改地传递给驱动程序,可选的参数arg则无论用户应用程序使用的是指针还是其他类型值,都以unsignedlong的形式传递给驱动。第三十六页,共八十二页。7.1.3设备号
linux/uclinux内核还需要:主设备号标识设备对应的驱动程序。系统中不同的设备可以有相同的主设备号,主设备号相同的设备使用相同的驱动程序。次设备号用来区分具体驱动程序的实例。一个主设备号可能有多个设备与之对应,这多个设备正是在驱动程序内通过次设备号来进一步区分的。次设备号只能由设备驱动程序使用,内核的其他部分仅将它作为参数传递给驱动程序。第三十七页,共八十二页。字符型设备主设备号的添加和注销
字符型设备主设备号的添加和注销分别通过调用函数register_chrdev()和unregister_chrdev()实现,这两个函数原型在<linux/fs.h>文件说明。externintregister_chrdev(unsignedintmajor,constchar*name,struct*fops);externintunregister_chrdev(unsignedintmajor,constchar*name);第三十八页,共八十二页。7.2设备驱动程序基础
7.2.1设备驱动中关键数据结构在linux系统中,设备驱动程序所提供的这组入口点由一个文件操作结构进行说明,分别是:(文件操作)数据结构file数据结构inode数据结构它们定义于Linux/fs.h文件中。第三十九页,共八十二页。(文件操作)数据结构由于用户进程是通过设备文件同硬件打交道的,所以对设备文件的操作不外乎一些系统调用,如open、read、write、close等。但是如何把系统调用和驱动程序关联起来,这里需要一个非常关键的数据结构,既(文件操作)。它用来存储驱动内核模块提供的对设备进行的各种操作的函数指针。第四十页,共八十二页。1.数据结构
struct{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);结构体中的每一个成员都对应着驱动内核模块用来处理某个请求事物的函数的地址第四十一页,共八十二页。int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,struct*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);};第四十二页,共八十二页。p228在simple设备驱动程序中它的结构是如下初始化的:structsimple_fops={.owner=THIS_MODULE,.llseek=simple_llseek,.read=simple_read,.write=simple_write,.ioctl=simple_ioctl,.open=simple_open,.release=simple_release,};对simple_fops变量进行初始化将对应操作的函数名作为指针传递给相应的成员第四十三页,共八十二页。在数据结构中,指出了设备驱动程序所提供的入口点位置,分别是:1)structmodule*owner该成员是结构中唯一一个不是声明操作的成员;它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载.几乎所有时间中,它被简单初始化为THIS_MODULE,一个在<linux/module.h>中定义的宏.2)loff_t(*llseek)(structfile*,loff_t,int);llseek方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值.loff_t参数是一个"longoffset",并且就算在32位平台上也至少64位宽.错误由一个负返回值指示.第四十四页,共八十二页。3)ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);用来从设备中获取数据.当为空时,导致read系统调用以-EINVAL(“Invalidargument”)失败.一个非负返回值代表了成功读取的字节数其中ssize_t为int或long型,和平台相关,__user用来声明为用户态4)ssize_t(*aio_read)(structkiocb*,char__user*,size_t,loff_t);初始化一个异步读--可能在函数返回前不结束的读操作.如果这个方法是NULL,所有的操作会由read代替进行(同步地).第四十五页,共八十二页。5)ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*)发送数据给设备.如果NULL,-EINVAL返回给调用write系统调用的程序.如果非负,返回值代表成功写的字节数.6)ssize_t(*aio_write)(structkiocb*,constchar__user*,size_t,loff_t*);初始化设备上的一个异步写.7)int(*readdir)(structfile*,void*,filldir_t);对于设备文件这个成员应当为NULL;它用来读取目录,并且仅对文件系统有用.第四十六页,共八十二页。unsignedint(*poll)(structfile*,structpoll_table_struct*);poll方法用作查询对一个或多个文件描述符的读或写是否会阻塞.poll方法应当返回一个位掩码指示是否非阻塞的读或写是可能的,并且,可能地,提供给内核信息用来使调用进程睡眠直到I/O变为可能.如果一个驱动的poll方法为NULL,设备假定为不阻塞地可读可写.int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);ioctl系统调用提供了发出设备特定命令的方法..如果设备不提供ioctl方法,系统调用将返回一个错误.第四十七页,共八十二页。int(*mmap)(structfile*,structvm_area_struct*);mmap用来请求将设备内存映射到进程的地址空间.如果这个方法是NULL,mmap系统调用返回-ENODEVint(*open)(structinode*,structfile*);该操作用来打开设备文件,也是对设备文件进行的第一个操作,如果这个操作为空,设备打开一直成功,但是你的驱动程序不会被调用第四十八页,共八十二页。int(*flush)(structfile*);flush操作在进程关闭它的设备文件描述符的拷贝时调用;它应当执行(并且等待)设备的任何未完成的操作.当前,flush在很少驱动中使用.如果flush为NULL,内核简单地忽略用户应用程序的请求.int(*release)(structinode*,structfile*);在文件结构被释放时引用这个操作.如同open,release可以为NULL.int(*fsync)(structfile*,structdentry*,int);这个方法是fsync系统调用的后端,用户调用来刷新任何挂着的数据.如果这个指针是NULL,系统调用返回-EINVAL.第四十九页,共八十二页。int(*aio_fsync)(structkiocb*,int);这是fsync方法的异步版本int(*fasync)(int,structfile*,int);这个操作用来通知设备它的FASYNC标志的改变.异步通知是一个高级的主题,在第6章中描述.这个成员可以是NULL如果驱动不支持异步通知.int(*lock)(structfile*,int,struct*);lock方法用来实现文件加锁;加锁对常规文件是必不可少的特性,但是设备驱动几乎从不实现它.第五十页,共八十二页。ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);该操作用来对一个包含多个内存区的单个读或写操作;这些系统调用允许它们这样做而不必对数据进行额外拷贝.如果这些函数指针为NULL,read和write方法被调用(可能多于一次).ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);这个方法实现sendfile系统调用的读,使用最少的拷贝从一个文件描述符搬移数据到另一个.例如,它被一个需要发送文件内容到一个网络连接的web服务器使用.设备驱动常常使sendfile为NULL.ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);sendpage是sendfile的另一半;它由内核调用来发送数据,一次一页,到对应的文件.设备驱动实际上不实现sendpage.第五十一页,共八十二页。unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.这个任务通常由内存管理代码进行;这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求.大部分驱动可以置这个方法为NULL.int(*check_flags)(int)这个方法允许模块检查传递给fnctl(F_SETFL...)调用的标志.int(*dir_notify)(structfile*,unsignedlong);第五十二页,共八十二页。2.
inode数据结构inode译成中文就是索引节点。每个存储设备或存储设备的分区(存储设备是硬盘、软盘、U盘......)被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block,Block是用来存储数据用的。而inode呢,就是用来存储这些数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令,能通过inode值最快的找到相对应的文件。做个比喻,比如一本书,存储设备或分区就相当于这本书,Block相当于书中的每一页,inode就相当于这本书前面的目录,一本书有很多的内容,如果想查找某部份的内容,我们可以先查目录,通过目录能最快的找到我们想要看的内容。虽然不太恰当,但还是比较形象。第五十三页,共八十二页。当我们用ls查看某个目录或文件时,如果加上-i参数,就可以看到inode节点了;比如比如hello.c文件的inode值为444676第五十四页,共八十二页。2.
inode数据结构p222文件系统处理的文件所需要的信息在inode(索引结点)数据结构中。Inode数据结构提供了关于特殊设备文件/dev/DriverName的信息,定义如下:
structinode称做索引节点数据结构,inode(索引结点)数据结构定义如下:structinode{structhlist_nodei_hash;structlist_headi_list;structlist_headi_sb_list;structlist_headi_dentry;unsignedlongi_ino;atomic_ti_count;umode_ti_mode;…….}第五十五页,共八十二页。Inode结构包含了大量有关文件的信息,但通常情况下对设备驱动开发比较有用的成员有下面两个dev_ti_rdev;//该成员包含了设备编号structcdev*i_cdev;//指向字符设备文件的指针说明:当inode指向一个字符设备文件时,该成员包含了指向structcdev结构的指针,其中cdev结构是字符设备结构体第五十六页,共八十二页。cdev结构在Linux2.6内核中一个字符设备用cdev结构来描述,其定义如下:structcdev{structkobjectkobj;
structmodule*owner;//所属模块conststruct*ops;//文件操作结构
structlist_headlist;
dev_tdev;//设备号unsignedintcount;};第五十七页,共八十二页。3.file数据结构设备驱动程序中,另一个非常重要的数据结构就是File结构,它不同于应用程序空间的FILE指针,FILE指针定义在C库中因而不会出现在内核代码中,而sturctfile只出现在内核代码中,从不出现在用户程序中内核用inode结构表示具体的文件,而file结构表示打开的文件描述符对于单个文件,可能会有许多个表示打开的文件描述符file结构,但是它们都指向了单个的inode结构,所以file结构和inode结构是不同的.第五十八页,共八十二页。3.file数据结构structfile{structlist_head f_list;//打开的文件形成一个列表structdentry *f_dentry;//指向相关目录项的指针structvfsmount*f_vfsmnt;//执行VFS挂载点的指针struct *f_op;//执行文件操作的指针atomic_t f_count;//使用该结构的进程数unsignedint f_flags;//文件打开的标志,如读写等mode_t f_mode;//文件打开的模式3.file数据结构第五十九页,共八十二页。loff_t f_pos;//文件的当前位置unsignedlongf_reada,f_ramax,f_raend,f_ralen,f_rawin;/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/structfown_struct f_owner;//文件的所有者unsignedint f_uid,f_gid;//用户的UID和GIDint f_error;//网络写操作错误码unsignedlong f_version;//版本号void *private_data;//tty驱动程序使用structkiobuf *f_iobuf;long f_iobuf_lock;};第六十页,共八十二页。7.2.2字符设备驱动开发p2287.2.2设备驱动程序的开发流程
(1)硬件接口设计或使用嵌入式处理器的生产商提供参考接口电路。(2)设计所要实现的文件操作,定义结构。(3)实现所需的文件操作调用,如read、write等。(2)实现初始化模块函数__init,其中定义设备号。(3)实现初始化函数,其中实现驱动的注册。(6)实现中断服务,并用request_irq向内核注册。(7)实现卸载模块函数__exit,完成驱动的注销,释放设备号(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。第六十一页,共八十二页。定义结构p228在simple设备驱动程序中它的结构是如下初始化的:structsimple_fops={.owner=THIS_MODULE,.llseek=simple_llseek,.read=simple_read,.write=simple_write,.ioctl=simple_ioctl,.open=simple_open,.release=simple_release,};对simple_fops变量进行初始化将对应操作的函数名作为指针传递给相应的成员第六十二页,共八十二页。7.2.2字符设备驱动开发p2287.2.2设备驱动程序的开发流程
(1)硬件接口设计或使用嵌入式处理器的生产商提供参考接口电路。(2)设计所要实现的文件操作,定义结构。(3)实现所需的文件操作调用,如open、read、write等。(2)实现初始化模块函数__init,其中定义设备号。(3)实现初始化函数,其中实现驱动的注册。(6)实现中断服务,并用request_irq向内核注册。(7)实现卸载模块函数__exit,完成驱动的注销,释放设备号(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。第六十三页,共八十二页。实现所需的文件操作调用p228staticintdevice_open(structinode*inode,structfile*file)
{
printk(“simpledeviceisopen\n”);//打印一句话try_module_get(THIS_MODULE);……return0;
}
函数:try_module_get()--如果模块已经插入内核,则递增该模块引用计数,每次加1;如果该模块还没有插入内核,则返回0表示出错。
第六十四页,共八十二页。实现所需的文件操作调用staticintdevice_release(structinode*inode,structfile*file)
{
printk(“Simpledevice_releasecall\n");
module_put(THIS_MODULE);……return0;
}函数:module_put()--与try_module_get()相对应,该模块引用计数减1;如果该模块还没有插入内核,则返回0表示出错第六十五页,共八十二页。7.2.2字符设备驱动开发7.2.2设备驱动程序的开发流程
(1)硬件接口设计或使用嵌入式处理器的生产商提供参考接口电路。(2)设计所要实现的文件操作,定义结构。(3)实现所需的文件操作调用,如read、write等。(2)实现初始化模块函数__init,其中定义设备号。(3)实现初始化函数,其中实现驱动的注册。(6)实现中断服务,并用request_irq向内核注册。(7)实现卸载模块函数__exit,完成驱动的注销,释放设备号(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。第六十六页,共八十二页。1)设备主设备号的添加和注销p225设备驱动在加载时首先需要调用入口函数module_init(),该函数完成设备驱动的初始化工作,比如分配设备号,驱动程序的注册,寄存器置位、结构体赋值等。第六十七页,共八十二页。1)设备主设备号的添加和注销p226建立字符设备以前,必须分配设备号。可以使用register_chrdev_region函数完成,该函数原型如下:intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name)First:是要分配的设备号范围的起始值Count:是请求的连续设备号的数目Name:是与该编号范围关联的设备名称,也就是在/proc/devices和sysfh中出现的名称。如何能够确定起始范围?第六十八页,共八十二页。实际上,Linux提供了动态分配设备号的函数,通过这个函数,内核可以为我们分配合适的设备号。函数原型如下:intalloc_chrdev_region(dev_t*dev,unsignedintfirsetminor,unsignedintcount,char*name)该函数的返回值为<0,表示不能获得设备号dev:用于输出的参数,保存已分配范围的第一个编号。Firstminor:是要使用的被请求的第一个次设备号,通常设置为0。Count:是请求的连续设备号的数目Name:是与该编号范围关联的设备名称第六十九页,共八十二页。动态分配设备号例如:#defineCDRIVER_NAME“simple_chrev”dev_tsimple_dev;intresult,count=1,CDRIVER_MAJOR=0,CDRIVER_MINOR=0;result=alloc_chrdev_region(&simple_dev,CDRIVER_MINOR,count,CDRIVER_NAME)CDRIVER_MAJOR=MAJOR(simple_dev);If(result<0){printk(KERN_ERR“cannotgetmajor%d\n",CDRIVER_MAJOR);}第七十页,共八十二页。将不需要的资源及时释放是一个好的编程习惯。在不使用设备号时,也应该把它释放,只需调用unregister_chrdev_region函数即可。函数原型如下:Voidunregister_chrdev_region(dev_tfirst,unsignedcount)例如:staticvoid__exit
simple_exit(void){unregister_chrdev_region(simple_dev,count)}第七十一页,共八十二页。7.2.2字符设备驱动开发7.2.2设备驱动程序的开发流程
(1)硬件接口设计或使用嵌入式处理器的生产商提供参考接口电路。(2)设计所要实现的文件操作,定义结构。(3)实现所需的文件操作调用,如read、write等。(2)实现初始化模块函数__init,其中定义设备号。(3)实现初始化函数,其中实现设备驱动的注册。(6)实现中断服务,并用request_irq向内核注册。(7)实现卸载模块函数__exit,完成驱动的注销,释放设备号(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。第七十二页,共八十二页。linux2.4驱动的注册和卸载在初始化中最重要的一个工作就是向内核注册该设备.在linux2.4内核中,对于字符设备调用register_chrdev()完成注册,对于块设备需要调用register_blkdev()完成注册。注册成功后,该设备获得了系统分配的主设备号、自定义的次设备号,并建立起与文件系统的关联。对于PCI、USB设备的注册与此略有不同,将在后面的章节中介绍。设备驱动在卸载时需要回收相应的资源,令设备的响应寄存器值置位并从系统中注销该设备,字符设备调用unregister_chrdev()、块设备调用unregister_blkdev()。第七十三页,共八十二页。register_chrdev函数的定义intregister_chrdev(unsignedintmajor,constchar*name,struct*fops);major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 会计部门年度工作计划的制定
- 2025年公路涂料项目发展计划
- 2025有关电梯安装维护合同
- 城市扩张背景下的土地综合利用策略
- 学生睡眠健康与心理关系
- 2025标准租赁合同模板
- 科研写作技巧与试题及答案
- 以职业培训为纽带的家政人才输送体系建设研究
- 企业数字化创新团队建设与管理
- 学生编程技能的培养与实战经验分享
- 2025深圳劳动合同下载
- 《同济堂会计信息披露违规案例研究》
- 【MOOC】中医与辨证-暨南大学 中国大学慕课MOOC答案
- 2023年重庆市万州区高笋塘街道电报路社区工作人员考试模拟试题及答案
- 2024安徽合肥市轨道交通集团限公司常态化招聘管理单位遴选500模拟题附带答案详解
- 工程项目安全施工教育培训制度(2篇)
- 《石油化工硫黄回收加热炉工程技术规范》
- 2024年江西省公务员录用考试《行测》真题及答案解析
- 公路工程劳务分包指导价
- 电子商务概论(第四版)课件 第11、12章 电子商务典型应用、电子商务应用案例
- 设备安装说明及技术指导方案
评论
0/150
提交评论