版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
ARM-uClinux下编写加载驱动程序详细过程本文主要介绍在uClinux下,通过加载模块的方式调试10控制蜂鸣器的驱动程序。实验过程与上篇文章所讲的过程基本相似,更多注重细节及注意事项。本文适合学习ARM—Linux的初学者。//==================================================================硬件平台:MagicARM2200教学试验开发平台(LPC2290)Linuxversion2.4.24,gccversion2.95.3电路连接:P0.7——蜂鸣器,低电平发声。实验条件:uClinux内核已经下载到开发板上,能够正常运行;与宿主机相连的网络、串口连接正常。//==================================================================编写蜂鸣器的驱动程序相对来说容易实现,不需要处理中断等繁琐的过程,本文以蜂鸣器的驱动程序为例,详细说明模块化驱动程序设计的主要过程和注意事项。一、编写驱动程序驱动程序的编写与上文所说的编写过程基本相同,这里再详细说明一下。//==========================================//蜂鸣器驱动程序:beep.c文件//#include<linux/module.h>#include<linux/kernel.h>#include<linux/module.h>#include<linux/kernel.h>#include<linux/types.h>#include<linux/fs.h>#include<linux/errno.h>/*内核相关*//*linux定义类型*//*文件系统file_opertions结构体定义*//*出错信息*//*PINSEL0注意:低2位是UART0复用口,不要改动*/#definePINSEL0(*((volatileunsigned*)0xE002C000))/*P0口控制寄存器*/#defineI00PIN(*((volatileunsigned*)0xE0028000))#defineIO0SET(*((volatileunsigned*)0xE0028004))#defineIO0DIR(*((volatileunsigned*)0xE0028008))#defineIO0CLR(*((volatileunsigned*)0xE002800C))#defineMAJOR_NUMBER254/*自定义的主设备号*/#defineBEEP_CMD0/*自定义的控制命令*//*函数声明*/staticintbeep_open(structinode*inode,structfile*file);staticintbeep_ioctl(structinode*inode,structfile*file,unsignedintcmd,unsignedlongarg);staticintbeep_release(structinode*inode,structfile*file);staticintbeep_init(void);staticvoidbeep_cleanup(void);/*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x*//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^/volatilestaticintbeep_major=MAJOR_NUMBER;/*全局变量:主设备号自定义为254*//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x*//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^//*注册函数:用到file_operations结构体。将蜂鸣器结构体自命名为beep_test,在注册模块时要用到*/staticstructfile_operationsbeep_test={owner:THIS_MODULE,ioctl:beep_ioctl,open:beep_open,release:beep_release,};/*注意:此处的分号(;)不要丢掉*//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x**x*//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^/#defineBEEPCON0x00000080staticvoidbeep_port_init(void)//蜂鸣器端口初始化:设置P0.7口为输出,初始值为高(蜂鸣器不发声){IO0DIR=BEEPCON;IO0SET=BEEPCON;}staticvoidbeep(intbeep_status)//蜂鸣器操作:根据参数(beep_status)状态判断是否发声{if(beep_status==0)IO0CLR=BEEPCON;elseIO0SET=BEEPCON;}staticintbeep_open(structinode*inode,structfile*file)//beep_test结构体中的open()函数实体,以下同{MOD_INC_USE_COUNT;//注册模块数加1beep_port_init();return0;}staticintbeep_ioctl(structinode*inode,structfile*file,unsignedintcmd,unsignedlongarg){if(cmd==0){printk("beepon!\n");beep(0);}else{printk("beepoff!\n");beep(1);}return0;}staticintbeep_release(structinode*inode,structfile*file){MOD_DEC_USE_COUNT;//模块数减1return0;}staticintbeep_init(void)//模块加载、初始化函数:将模块加载到内核运行{intresult;result=register_chrdev(beep_major,"named_beep",&beep_test);if(result<0){printk(KERN_INFO"beep:can'tgetmajornumber\n");returnresult;}if(beep_major==0)beep_major=result;printk(KERN_INFO"beep:initOK!\n");/*注意:驱动程序运行在内核空间,从内核打印信息要用printk()函数而不是printf()函数,而且要配有优先级*/return0;}staticvoidbeep_cleanup(void)//模块卸载函数:将模块从内核卸载出去{unregister_chrdev(beep_major,"named_beep");}/*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^//*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^//*以下部分是驱动程序的关键,后面做详细说明*///module_init(beep_init);//module_exit(beep_cleanup);intinit_module(void)//加载模块{returnbeep_init();}voidcleanup_module(void)//卸载模块{beep_cleanup();}////驱动程序文件结束//以上是整个驱动程序文件的全部内容,将文件保存,这里将其命名为beep.c。整个驱动程序很简单,只填写了几个操作函数beep_open()、beep_release()和beep_ioctl()。其实控制蜂鸣器用beep_ioctl()—个函数即可,其它函数基本都是空操作。在驱动文件最后的两个函数对驱动程序来数是及其重要的。应用程序与内核的区别就是应用程序从头到尾完成一个任务,而内核则为以后处理某些请求而注册自己,完成这个任务后,他的“主”函数就立即终止。换句话说,init_module()函数(名称不能更改)是模块入口点,如同应用程序的main()函数一样,换句话说,模块入口点init_module()函数的任务就是为以后调用模块的函数做准备;cleanup_module()函数(名称不能更改)是模块的第二个入口点,此函数仅当模块被卸载前才被调用。它的功能是去掉init_module()函数所作的事情。这两个函数由〈linus/modele.h〉头文件声明,有关模块实现的源代码可以参见../kernel/module.c。init_module()函数在模块被加载时执行,模块的初始化就是通过调用init_module()函数完成的。它注册驱动设备,需调用register_chrdev()函数实现。register_chrdev有3个参数:(1):希望获得的设备主号,即beep_major全局变量,如果是0,系统将选择一个没有被占用的设备号返回;(2):设备文件名,自定义设备文件名,这里用named_beep,它返回这个驱动程序所使用的主设备号;(3):用来登记驱动程序实际执行操作的函数指针,即beep_test结构体。如果登记成功,register_chrdev返回设备的主设备号;否则返回一个负值。模块是内核的一部分,但并未被编辑到内核中,他们被分别编译和连接成目标文件。用命令insmod插入一个模块到内核中,用命令rmmod卸载一个模块。这两个命令分别调用init_module()函数和cleanup_module()函数。关于insmod和rmmod命令,后面还会用到。在2.3版本以后的Linux内核中,提供了一种新的方法来命名这两个函数。例如可以定义beep_init()和beep_cleanup()两个函数,然后在源代码文件末尾使用下面的语句,其效果是一样的。module_init(beep_init);module_exit(beep_cleanup);注意:这两个宏是在〈linux/init.h〉中被定义的,所以源码文件中必须包括这个头文件。而且,这两行语句必须在函数声明后使用,否则会编译出错。驱动程序部分先暂且介绍到这,继续往下介绍,如何将驱动程序加载到内核中去,又如何利用驱动程序来控制蜂鸣器发声。二、编译驱动程序编译驱动程序的过程比较无聊,按照步骤一步一步进行即可。但首先需要了解linux的基本操作命令,最好会Makefile文件的编写,至少能看懂也行。下面一步一步的介绍:1、将驱动程序beep.c文件传到宿主机中。这里所说的“宿主机”是运行Linux的PC机,可以是安装了Linux操作系统的本地机,亦可以是Linux服务器。由于嵌入式Linux的开发板资源有限,不可能在开发板上运行开发和调试工具。统称需要交叉编译调试的方式进行,即“宿主机+目标板”的形式。程序在宿主机上编译—连接—定位,得到的可执行文件则在目标板上运行。而“目标板”就是实验的硬件平台MagicARM2200教学试验开发平台。本实验是在PC机上通过虚拟机搭建的宿主机。目标板和宿主机通过串口和网口连接,其中串口当作终端,作为人机交互界面。若目标板可以看成一台计算机的话,那么串口终端就相当于这台计算机的显示器,通过linux命令对目标板进行相关操作;而网口是与宿主机相连接,作为数据传输、共享的通道。这里利用linux的NFS服务器将宿主机系统下/home/armwork目录作为共享目录,在目标板上通过mount命令将此目录挂在到目标板上的/mnt目录下,于是打开目标板的/mnt目录所见的内容就是宿主机上/home/armwork目录的内容。当然以上所说的内容包括宿主机的建立、交叉开发环境(arm-elf-gcc)的安装、uClinux系统移植、网卡串口驱动、嵌入TCP/IP协议栈等工作都已经做好,这里只是利用这一平台介绍驱动程序的编写。而且这些工作步骤也比较单调,可以很容易找到现成的步骤说明,这里就不做过多说明了。说了一堆前提条件,现在开始继续。下面的工作都是在宿主机上进行的。前面所说的beep.c驱动文件是在Windows环境下编写的(当然也可以在Linux下编写,如用vi编写),使用SSHSecure或FlashFXP等FTP工具,将beep.c文件上传到宿主机中,先在宿主机的/home/armwork目录下建立一个新目录命名为beep。在宿主机的终端命令行上输入下面两条命令:(这里假定宿主机中已经存在有/home/armwork目录,当然也可以直接在图形编辑环境下新建目录)cd/home/armworkmkdirbeepcdbeep这三条命令意思分别为:将/home/armwork目录指定为当前目录;在当前目录下建立beep目录;将beep目录指定为当前目录。然后,利用FTP工具将beep.c文件上传到刚刚建立的beep目录下,输入ls命令显示当前目录下的内容:ls-l(命令均为为字母l,不是数字1)显示内容为文件的详细信息:-rw-r--r--1rootroot28915月518:12beep.c依次为操作权限、用户、文件大小、日期、文件名等信息。2、编写Makefile文件。以下是Makefile文件的详细内容,将其保存命名为Makefile(文件名不能更改)#各项对应驱动程序的文件名。EXEC=beepOBJS=beep.oSRC=beep.c#交叉环境所在的目录,根据各自机器存放位置修改。INCLUDE=/usr/src/uClinux-dist/linux-2.4.x/include#所使用的交叉环境CC=arm-elf-gccLD=arm-elf-ldMODCFLAGS=-D__KERNEL__-I$(INCLUDE)-Wall-O2-fno-strict-aliasing-fno-common-pipe-fno-builtin-D__linux__-g-DNO_MM-mapcs-32-march=armv4-mtune=arm7tdmi-mshort-load-bytes-msoft-float-nostdinc-iwithprefixincludeLDFLAGS=-marmelf-rall:$(EXEC)$(EXEC):$(OBJS)$(LD)$(LDFLAGS)-o$@$(OBJS)%.o:%.c$(CC)$(MODCFLAGS)-mapcs-c$<-o$@clean:-rm-f$(EXEC)*.elf*.gdb*.o##其中代码的含义这里不做详细讲解,其作用就是将beep.c文件编译生成beep.o文件和beep可执行文件。关于Makefile文件的编写,可以参考相关资料,这里不对写法做详细说明。3、编译驱动程序。用同样的方法,将Makefile文件上传到宿主机的beep目录下。可以利用ls命令查看目录内容。确定文件正确并传输成功,在beep目录下输入make命令编译。#make显示如下结果:arm-elf-gcc-D__KERNEL__-I/usr/src/uClinux-dist/linux-2.4.x/include-Wall-O2-fno-strict-aliasing-fno-common-pipe-fno-builtin-D__linux__-g-DNO_MM-mapcs-32-march=armv4-mtune=arm7tdmi-mshort-load-bytes-msoft-float-nostdinc-iwithprefixinclude-mapcs-cbeep.c-obeep.oarm-elf-ld-marmelf-r-obeepbeep.omake命令是在当前目录下找到Makefile文件,并对Makefile文件的代码进行解析、执行。而Makefile文件就类似于DOS下的批处理文件。如果遇到问题请查看Makefile文件、操作权限等是否正确如果一切顺利,那么驱动程序就编译成功了,beep目录下会多出几个文件,用ls命令查看:#ls-l显示如下:-rw-r--r--1rootroot867625月609:35beep-rw-r--r--1rootroot28915月518:12beep.c-rw-r--r--1rootroot866245月609:35beep.o-rw-r--r--1rootroot5475月514:35Makefile其中beep文件即为可执行文件。后面介绍要加载到模块的文件就是此文件。三、加载模块到内核前面已经把驱动程序beep.c编译成可执行文件beep,那么这个beep可执行文件即为要加载的所谓的“模块”。既然模块已经做好,下面的工作就轻松了,加载工作非常简单。加载模块的工作是在目标板上进行的,因为我们所要做的就是为目标板做驱动程序,所要加载的模块是要加载到目标板上的uClinux内核中。宿主机与目标板上都运行着linux系统,而其用途是不同的,这一点一定要注意,不要混淆。这里再强调一遍:宿主机的Linux系统安装有编译调试工具,为的是编译C语言程序,生成目标文件(.0文件)和可执行文件。而目标板上的uClinux系统是为实际应用所做的系统,它仅仅是为了直接运行宿主机所生成的可执行代码。这样做的目的就是为了节省目标板的硬件开销,或者说是为了完成在目标板上不可能完成的工作,即编译调试程序。首先运行目标板,成功运行uClinux系统,并成功把宿主机上的/home/armwork目录挂在到目标板的/mnt目录下。这一步的作用前面已经说过,是为了让目标板共享宿主机上的/home/armwork目录,即要想打开宿主机上的/home/armwork目录,只要打开目标板上的/mnt目录就可以了。于是前面所编译生成的beep可执行文件就可以直接通过这个目录获取了。以下操作都是对目标板进行的,这就需要通过串口的人机终端进行操作。首先连接好串口数据线。若在Windows下,则打开超级终端(开始->所有程序->附件->通讯->超级终端),新建一个超级终端并设置参数使之与目标板相匹配。若在宿主机Linux下,则启动minicom(调整好终端命令行的大小后,输入minicom命令)根据minicom的提示,按CTRL+A,松开后再按Z,进入minicom配置界面,同样要配置成与目标板相匹配的参数。以下操作都是在超级终端或minicom下进行输入的。接下来,在目标板上建立设备节点,输入如下命令:cd/mnt/beeprm-f/dev/beepmknod/dev/beepc2540这两行命令分别为:设置/mnt/beep目录为当前目录;强行删除原有相同名称的设备节点;创建名为beep的设备节点,类型为字符型设备,主设备号为254,从设备号为0。因为/mnt目录已经被挂在到宿主机的/home/armwork目录了,那么查看/home/armwork目录下的文件就可以通过查看/mnt目下的文件方式实现了。从命令中能够看出,设备节点是存储在/dev目录下的,可以通过下面命令查看系统已经建立了哪些设备节点。ls/dev接下来,加载模块到内核,使用insmod命令insmodbeep这一命令将当前目录(/mnt/beep目录)下的beep刻执行文件加载到内核中,执行insmod命令,即调用init_modele()函数,显示结果如下:Usingbeepbeep:initOK!第一句是系统提示的输出,后一句是在驱动程序的init_modele()函数中执行beep_init()函数实现的输出,可以根据需要修改beep_init()函数。以上工作需要多次输入命令,操作繁琐,容易出错。要解决这一问题可以将以上输入的命令编写到一起,保存为一个文本文件,如下:#!/bin/shrm-f/dev/beepmknod/dev/beepc2540insmodbeep将以上代码保存自命名为loadbeep文件,要加载模块的时候只需执行loadbeep文件即可。>./loadbeep注意:是“.”和“/”后面跟文件名,表示执行此文件。执行此文件等同于执行以上命令。同时还用注意权限问题,如果出现“./loadbeep:Permissiondenied"提示信息,表示目标板没有对宿主机文件执行的权限。这时需要在宿主机上修改loadbeep文件的使用权限,在宿主机的root用户(linux下拥有最好权限的用户)下输入以下命令:#chmod755loadbeep这样,其它用户就拥有执行该文件的权限了。其中命令前的“#”表示root用户,“$”表示其它用户。若模块使用后,不再需要,则可以使用rmmod命令卸载模块:rmmodbeep这一命令将调用cleanup_module()函数。若要查看当前已经加载过的设备模块,可以使用lsmod命令查看:lsmod这一命令只是输出的是/proc目录下的modules文件,当然可以用cat命令直接查看该文件。至此,模块加载工作也完成了。四、编写应用程序,测试驱动模块编写测试应用程序和编写驱动程序的过程基本相同,这里不再重复,程序代码如下://==========================================//测试程序:main.c文件//#include<fcntl.h>main(){intfd;inti;fd=open("/dev/beep",O_RDONLY);/*打开设备文件beep,O_RDONLY表示以只读方式打开,并会调用驱动程序中的beep_open()函数*/if(fd==-1)//若打开失败,则报告出错,退出{printf("Cannotopenfile\n");exit(-1);}for(i=0;i<3;i++)//让蜂鸣器每隔1秒响一次,共响三次{ioctl(fd,0,0);//IO操作函数,指令码为0,调用驱动程序中的beep_ioctl()函数,控制蜂鸣器发声sleep(1);//Linux系统函数,让进程暂停一段时间,可用于延时,时间单位是秒ioctl(fd,1,0);//IO操作函数,指令码为1,调用驱动程序中的beep_ioctl()函数,控制蜂鸣器停止发声sleep(1);}close(fd);//关闭设备文件,并会调用驱动程序中的beep_release()函数printf("Success!\n");//用户程序运行在用户空间,打印信息用printf()函数return(0);}////测试程序文件结束//==========================================测试程序完成,下面将测试程序按照上传驱动程序的方法上传到宿主机中。在宿主机的/home/armwork目录下新建一个目录,这里命名为exc目录。将测试程序main.c上传到这个目录下。下面编写编译测试程序的Makefile文件。##EXEC=mainOBJS=main.oSRC=main.cCC=arm-elf-gccBASEPATH=/usr/src/uClinux-distLIBPATH=$(BASEPATH)/libLLIBPATH=$(LIBPATH)/uClibc/libINCLUDEPATH=$(BASEPATH)/linux-2.4.x/includeLDFLAGS=-Os-g-Dlinux-D__linux__-Dunix-D__uClinux__-DEMBEDLDLIBS=-I$(LIBPATH)/uClibc/include-I$(LIBPATH)/libm-I$(LIBPATH)/libcrypt_old-I$(BASEPATH)-fno-builtin-nostartfiles-D__PIC__-fpic-msingle-pic-base-I$(INCLUDEPATH)LDLIBS_EXEC=-Wl,-elf2flt$(LLIBPATH)/crt0.o$(LLIBPATH)/crti.o$(LLIBPATH)/crtn.o-L$(LIBPATH)/uClibc/.-L$(LLIBPATH)-L$(LIBPATH)/libm-L$(LIBPATH)/libnet-L$(LIBPATH)/libdes-L$(LIBPATH)/libaes-L$(LIBPATH)/libpcap-L$(LIBPATH)/libcrypt_old-L$(LIBPATH)/libssl-L$(LIBPATH)/zlib-lcLDLIBS_OBJS=-call:$(EXEC)$(EXEC):$(OBJS)$(CC)$(LDFLAGS)$(LDLIBS)$(LDLIBS_EXEC)-o$@$(OBJS)%.o:%.c$(CC)$(LDFLAGS)$(LDLIBS)$(LDLIBS_OBJS)-c$<-o$@clean:-rm-f$(EXEC)*.elf*.gdb*.o##保存并命名为Makefile(文件名不能更改),将当前目录设置为/home/armwork/exc目录,上传Makefile文件到当前目录中,输入make命令,编译测试程序main.c文件,生成main.o目标文件和main可执行文件。显示输出以下结果:arm-elf-gcc-Os-g-Dlinux-D__linux__-Dunix-D__uClinux__-DEMBED-I/usr/src/uClinux-dist/lib/uClibc/include-I/usr/src/uClinux-dist/lib/libm-I/usr/src/uClinux-dist/lib/libcrypt_old-I/usr/src/uClinux-dist-fno-builtin-nostartfiles-D__PIC__-fpic-msingle-pic-base-I/usr/src/uClinux-dist/linux-2.4.x/include-c-cmain.c-omain.oarm-elf-gcc-Os-g-Dlinux-D__linux__-Dunix-D__uClinux__-DEMBED-I/usr/src/uClinux-dist/lib/uClibc/include-I/usr/src/uClinux-dist/lib/libm-I/usr/src/uClinux-dist/lib/libcrypt_old-I/usr/src/uClinux-dist-fno-builtin-nostartfiles-D__PIC__-fpic-msingle-pic-base-I/usr/src/uClinux-dist/linux-2.4.x/include-Wl,-elf2flt/usr/src/uClinux-dist/lib/uClibc/lib/crt0.o/usr/src/uClinux-dist/lib/uClibc/lib/crti.o/usr/src/uClinux-dist/lib/uClibc/lib/crtn.o-L/usr/src/uClinux-dist/lib/uClibc/.-L/usr/src/uClinux-dist/lib/uClibc/lib-L/usr/src/uClinux-dist/lib/libm-L/usr/src/uClinux-dist/lib/libnet-L/usr/src/uClinux-dist/lib/libdes-L/usr/src/uClinux-dist/lib/libaes-L/usr/src/uClinux-dist/lib/libpcap-L/usr/src/uClinux-dist/lib/libcrypt_old-L/usr/src/uClinux-dist/lib/libssl-L/usr/src/uClinux-dist/lib/zlib-lc-omainmain.o编译后,该目录多出几个文件。用ls命令查看详细文件信息,显示结果如下-rwxr--r--1rootroot294645月611:34main-rw-r--r--1rootroot5155月517:31main.c-rwxr-xr-x1rootroot7423365月611:34main.gdb-rw-r--r--1rootroot68285月611:34main.o-rw-r--r--1rootroot9515月514:42Makefile从显示结果可以看出,现在的main可执行文件只有宿主机的root用户拥有可执行权限,其它用户只拥有只读权限,使用chmod命令更改权限:#chmod755main用ls命令查看修改后的文件详细信息为:-rwxr-xr-x1rootroot294645月611:34main这样,目标板也拥有对该文件的可执行权限了。现在回过头来再看目标板,通过超级终端,设置当前目录为/mnt/exc目录,执行main文件(输入./main命令),一切正常则可以听到测试程序所设计的:蜂鸣器每秒响一声,共响三声,同时超级终端有如下显示输入:beepon!beepoff!beepon!beepoff!beepon!beepoff!Success!其中最后一条“Success!”是在测试程序主函数里倒数第二条代码实现的(见前面的测试程序),其它显示是在驱动程序中beep_ioctl()函数里实现的。至此,测试工作完成。五、总结本文从编写驱动程序开始到编译驱动程序、加载模块一直到测试驱动程序,详细的介绍了如何在ARM的uClinux系统环境下加载模块化驱动程序及具体实现过程,完整阐述了模块化驱动程序的编写方法及注意事项,对ARM-Linux的初学者来说能起到一定的引导作用。而本文对Makefile文件的编写及分析没有进行介绍,在以后的文章中会详细介绍Makefile文件的编写。以上所完成的实验过程是在MagicARM2200教学试验开发平台实验测试通过的,所将内容大多是我的个人理解,而我也只是初学者,因此纰漏在所难免,也望各位读者指教。全文完Linux下编写驱动程序2008年05月01日星期四18:44摘要:设备驱动程序是操作系统内核与机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节。那么驱动程序如何书写实现这一接口功能是本文讨论的重点,并以一简单的驱动程序介绍书写细节。在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。(应用程序一般是在用户态下进行)也就是说系统必须在驱动程序的子函数返回后才能进行其它的工作,即驱动程序不能进入死循环。字符型设备驱动程序的编写包含一下信息:#define_NO_VERSION_#include<linux/modules.h>#include<linux/version.h>charkernel_version[]=UTS_RELEASE这段定义了一些版本信息,虽然用处不大,但也必不可少。<linux/config.h>最好要包含。由于用户进程是通过设备文件同硬件打交道,对设备文件的操作不外乎就是一些系统调用,如open,read,write,close„„,(注意,不是fopen,fread,)但是如何把系统调用和驱动程序联系起来呢?这需要了解一个非常关键的数据结构:structfile_opertions{int(*seek)(struetinode*,structfile*,off_t,int);/*文件定位*/int(*read)(struetinode*,structfile*,char,int);/*读取数据*/int(*write)(struetinode*,structfile*,off_t,int);/*写数据*/int(*readdir)(struetinode*,structfile*,structdirent*,int);/读取相关目录*/int(*select)(structinlde*,structfile*,int,select_table*);/非阻塞设备访问*/int(*ioctl)(structinlde*,structfile*,unsignedint,unsignedlong);int(*mmap)(structinlde*,structfile*,structvm_area_struct*);int(*open)(structinlde*,structfile*);int(*release)(structinlde*,structfile*);int(*fsync)(struetinlde*,struetfile*);/*强制同步*/int(*fasync)(structinlde*,structfile*);int(*check_media_change)(structinlde*,structfile*);int(*revalidata)(dev_tdev);/*使设备重新有效*/}其中read,write,open,close(release),ioctl是最核心的,必须实现的。这个结构体的每个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备注册程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operatons的各个域。以下是简单的字符型设备的驱动程序编写方式,例子程序并不牵扯到具体设备,只是个编写框架。#include<linux/types.h>//Linux基本类型定义#include<linux/fs.h>//文件系统相关头文件#include<linux/mm.h>//memmorymanagement内存管理#include<linux/errno.h>//错误代码#include<asm/segment.h>//汇编文件unsignedinttest_major=0;/*定义一个主设备号(主设备号、从设备号在Linux设备管理中有相关介绍)*/staticintread_test(structinode*inode,structfile*file,char*buf,intcount){/*本函数对应于file_opertions中read的实现,函数名自己定义。inode为设备节点,file为设备文件描述符(open()打开后自动或得),buf为数据缓冲区,count为数据传送个数。“static”这里修饰函数名表示函数只在本文件中有效。这里函数只实现简单数据拷贝功能。*/intleft;if(verify_area(VERIFY_WRITE,buf,count)==-EFAULT)//验证缓存中的数据是否有效return-EFAULT;//错误码,在<linux/errno.h>包含for(left=count;left>0;left--){__put_user(1,buf,1);/*“__”表示内核调用函数,此函数表示把数据从内核空间放到用户空间,参数依次表示:填充数、用户空间、数据量。*/buf++;}returncount;}这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态(内核空间),必须用—put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数,参考内核调用接口函数。在向用户空间拷贝数据之前,必须验证buf空间是否可用。这就用到verify_area()。staticintwrite_test(structinode*inode*inode,structfile*file,constchar*buf,intcount){returncount;}写数据函数,具体没有实现,直接返回计数值。staticintopen_test(structinode*inode,structfile*file)MOD_INC_USE_COUNT;//宏:注册模块数加1return0;//返回0表示成功,根据函数自己定义。}这个函数比较简单,它不牵扯到设备文件,仅将模块数加1。staticvoidrelease_test(structinode*inode,structfile*file){MOD_DEC_USE_COUNT;//模块数减1}以上实现四个函数,后三个函数都是空操作,实际调用发生时什么也不做,它们仅仅为file_operations结构体提供函数指针。下面开始注册刚刚写好的函数structfile_operationstest_fops=/*file_operations结构体名,test_fops结构体对象*/{NILL,/*seek*/read_test,write_test,NULL,/*test_readdir*/NULL,/*test_mmap*/open_test,release_test,NULL,/*test_fsvnc*/NULL,/*test_fasync*//*其它位置均填为空NILL*/};设备驱动程序的主体可以说是写好了,现在把驱动程序嵌入内核。驱动程序可用按照两种方式编译。一种是编译进内核(kernel),另一种是编译成模块(modules)如*.0文件,如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。1、登记注册设备:方式一:编译进内核,利用函数init_module()intinit_module(void){intresult
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- GB/T 40283.2-2026自动化系统与集成制造应用解决方案的能力单元互操作第2部分:能力模板和软件单元编目
- app研发外包合同
- 与电信签了外包合同
- 中保劳务外包合同
- 互联网运营外包合同
- 保温外包合同
- 企业制造外包合同
- 体育场地外包合同
- 信用卡营销外包合同
- 入职外包合同
- 2024年广东惠州大亚湾开发区招聘公办学校教师真题
- 西部计划考试考题及答案
- 锂电池电极工艺培训资料
- 工厂急救知识培训课件
- GB/T 42596.2-2024机床安全压力机第2部分:机械压力机安全要求
- 睡眠科技在改善老年人睡眠中的应用
- GB/T 17727-2024船用法兰非金属垫片
- TGDGX 0003-2024 高校物业服务费用测算及基本人员配置规范
- 实验室生物安全手册资料
- 切口机操作规程
- 农村电商智慧树知到期末考试答案章节答案2024年西昌学院
评论
0/150
提交评论