linux设备驱动.doc_第1页
linux设备驱动.doc_第2页
linux设备驱动.doc_第3页
linux设备驱动.doc_第4页
linux设备驱动.doc_第5页
免费预览已结束,剩余126页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux设备驱动程序学习(0)设备驱动介绍& Hello, world!模块设备驱动程序的作用设备驱动程序就是这个进入Linux内核世界的大门。设备驱动程序在Linux内核中扮演着特殊的角色。它是一个独立的“黑盒子”,使某个特定硬件响应一个定义好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序。将这些调用映射到作用于实际硬件的设备特有操作上,则是设备驱动程序的任务。设备驱动的分类字符设备:字符(char)设备是个能够像字节流(类似文件)一样被访问的设备。字符设备驱动程序通常至少要实现open、close、read和write系统调用。块设备:一个块设备驱动程序主要通过传输固定大小的数据来访问设备。块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户程序是透明的。在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。网络接口:任何网络事务都经过一个网络接口形成,即一个能够和其他主机交换数据的设备。它可以是个硬件设备,但也可能是个纯软件设备。访问网络接口的方法仍然是给它们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函数而不是read、write等。驱动模块的特点(1)驱动模块运行在内核空间,运行时不能依赖于任何标准C库等应用层的库、模块,所以在写驱动时所调用的函数只能是作为内核一部分的函数,即使用“EXPORT_SYMBOL”导出的函数。insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局内核项(即函数和变量的地址),这是实现模块化驱动程序所必须的。Linux使用模块层叠技术,我们可以将模块划分为多个层,通过简化每个层可缩短开发周期。如果一个模块需要向其他模块导出符号,则使用下面的宏:EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);符号必须在模块文件的全局变量部分导出,因为这两个宏将被扩展为一个特殊变量的声明,而该变量必须是全局的。(2)驱动模块和应用程序的一个重要不同是:应用程序退出时可不管资源释放或者其他的清除工作,但模块的退出函数必须仔细撤销初始化函数所作的一切,否则,在系统重新引导之前某些东西就会残留在系统中。(3)处理器的多种工作模式(级别)其实就是为了操作系统的用户空间和内核空间设计的。在Unix类的操作系统中只用到了两个级别:最高和最低级别。(4)要十分注意驱动程序的并发处理。(5)内核API中具有双下划线(_ _)的函数,通常是接口的底层组件,应慎用。(6)内核代码不能实现浮点数运算。参考资料:/u/30180/showart.php?id=1421920模块结构介绍利用Linux设备驱动程序的第一个例程:Hello World模块了解内核驱动模块的结构。#include#includestaticinthello_init(void)printk(KERN_ALERTHello, Tekkaman Ninja!n);return0;staticvoidhello_exit(void)printk(KERN_ALERTGoodbye, Tekkaman Ninja!n Love Linux !Love ARM ! Love KeKe !n);module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE(Dual BSD/GPL);1所有模块代码中都包含一下两个头文件:#include#include2所有模块代码都应该指定所使用的许可证:MODULE_LICENSE(Dual BSD/GPL);此外还有可选的其他描述性定义:MODULE_AUTHOR();MODULE_DESCRIPTION();MODULE_VERSION();MODULE_ALIAS();MODULE_DEVICE_TABLE();上述MODULE_声明习惯上放在文件最后。3初始化和关闭初始化的实际定义通常如下:staticint_ _initinitialization_function(void)/*初始化代码*/module_init(initialization_function)清除函数的实际定义通常如下:staticint_ _exitcleanup_function(void)/*清除代码*/module_exit(cleanup_function)4一个简单的Makefile文件:KERNELDIR=/home/tekkaman/working/SBC2440/linux-PWD:=$(shell pwd)INSTALLDIR = /home/tekkaman/working/rootfs/lib/modulesCROSS_COMPILE=arm-9tdmi-linux-gnu-CC=$(CROSS_COMPILE)gccobj-m:=hello.o.PHONY:modules modules_install cleanmodules:$(MAKE)-C$(KERNELDIR)M=$(PWD)modulesmodules_install:cp hello.ko $(INSTALLDIR)clean:rm-rf*.o*core.depend.*.cmd*.ko*.mod.c.tmp_versionsobj-m:=hello.o代表了我们要构造的模块名为hell.ko,make会在该目录下自动找到hell.c文件进行编译。如果hello.o是由其他的源文件生成(比如file1.c和file2.c)的,则在下面加上(注意红色字体的对应关系):hello-objs:= file1.o file2.o .$(MAKE)-C $(KERNELDIR)M=$(PWD)modules-C $(KERNELDIR)指定了内核源代码的位置,其中保存有内核的顶层makefile文件。M=$(PWD)指定了模块源代码的位置modules目标指向obj-m变量中设定的模块。5编译模块make modules、make modules_install。rootTekkaman-Ninja Helloworld# make modulesmake -C /home/tekkaman/working/SBC2440/linux- M=/home/tekkaman/working/Linuxdriver/Helloworld modulesmake1: Entering directory /home/tekkaman/working/SBC2440/linux- CC M /home/tekkaman/working/Linuxdriver/Helloworld/hello.o Building modules, stage 2. MODPOST 1 modules CC /home/tekkaman/working/Linuxdriver/Helloworld/hello.mod.o LD M /home/tekkaman/working/Linuxdriver/Helloworld/hello.komake1: Leaving directory /home/tekkaman/working/SBC2440/linux-rootTekkaman-Ninja Helloworld# make modules_installcp hello.ko /home/tekkaman/working/rootfs/lib/modulesrootTekkaman-Ninja Helloworld#6在开发板上的操作:Tekkaman2440SBC2440V4#cd /lib/modules/Tekkaman2440SBC2440V4#lscs89x0.kohello.kop80211.ko prism2_usb.koTekkaman2440SBC2440V4#insmod hello.koHello, Tekkaman Ninja!Tekkaman2440SBC2440V4#lsmodModule Size Used by Not taintedhello 1376 0Tekkaman2440SBC2440V4#rmmod helloGoodbye, Tekkaman Ninja!Love Linux !Love ARM ! Love KeKe !Tekkaman2440SBC2440V4#lsmodModule Size Used by Not taintedTekkaman2440SBC2440V4#Linux内核模块的初始化出错处理一般使用“goto”语句。通常情况下很少使用“goto”,但在出错处理是(可能是唯一的情况),它却非常有用。在 大二学习C语言时,老师就建议不要使用“goto”,并说很少会用到。在这里也是我碰到的第一个建议使用“goto”的地方。“在追求效率的代码中使用goto语句仍是最好的错误恢复机制。”Linux设备驱动程序(第3版)以下是初始化出错处理的推荐代码示例:structsomething*item1;structsomethingelse*item2;intstuff_ok;void my_cleanup(void) if(item1) release_thing(item1); if(item2) release_thing2(item2); if(stuff_ok) unregister_stuff(); return;int_init my_init(void) interr=-ENOMEM; item1=allocate_thing(arguments); item2=allocate_thing2(arguments2); if(!item2|!item2) gotofail; err=register_stuff(item1,item2); if(!err) stuff_ok=1; else gotofail; return 0;/*success*/fail: my_cleanup(); return err;模块参数内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变。以下是我的实验程序:#include#include#includeMODULE_LICENSE(Dual BSD/GPL);staticchar*whom=Tekkaman Ninja;staticinthowmany=1;staticintTNparam=1,2,3,4;staticintTNparam_nr=4;module_param(howmany,int,S_IRUGO);module_param(whom,charp,S_IRUGO);module_param_array(TNparam,int,&TNparam_nr,S_IRUGO);staticinthello_init(void)inti;for(i=0;ihowmany;i+)printk(KERN_ALERT(%d) Hello, %s !n,i,whom);for(i=0;i 8;i+)printk(KERN_ALERTTNparam%d : %d n,i,TNparami);return0;staticvoidhello_exit(void)printk(KERN_ALERTGoodbye, Tekkaman Ninja !n Love Linux !Love ARM ! Love KeKe !n);module_init(hello_init);module_exit(hello_exit);实验结果是 :Tekkaman2440SBC2440V4#cd/lib/modules/Tekkaman2440SBC2440V4#lscs89x0.ko hello.ko prism2_usb.kohello-param.ko p80211.koTekkaman2440SBC2440V4#insmod hello-param.ko howmany=2 whom=KeKeTNparam=4,3,2,1(0)Hello,KeKe !(1)Hello,KeKe !TNparam0:4TNparam1:3TNparam2:2TNparam3:1TNparam4:1836543848TNparam5:7958113TNparam6:1836017783TNparam7:0Tekkaman2440SBC2440V4#insmod hello-param.ko howmany=2 whom=KeKe TNparam=4,3,2,1,5,6,7,8TNparam: can only take 4 argumentshello_param: 4 invalid for parameter TNparaminsmod: cannot insert hello-param.ko: Invalid parameters (-1): Invalid argumentTekkaman2440SBC2440V4#我这个实验除了对参数的改变进行实验外,我的一个重要的目的是测试“module_param_array(TNparam,int,&TNparam_nr,S_IRUGO);”中&TNparam_nr对输入参数数目的限制作用。经过我的实验,表明&TNparam_nr并没有对输入参数的数目起到限制作用。真正起到限制作用的是“static int TNparam = 1,2,3,4;”本身定义的大小,我将程序进行修改:static int TNparam = 1,2,3,4;改为static int TNparam = 1,2,3,4,5,6,7,8;其他都不变。编译后再进行实验,其结果是:Tekkaman2440SBC2440V4#insmod hello-param.ko howmany=2 whom=KeKeTNparam=4,3,2,1,5,6,7,8(0)Hello,KeKe !(1)Hello,KeKe !TNparam0:4TNparam1:3TNparam2:2TNparam3:1TNparam4:5TNparam5:6TNparam6:7TNparam7:8Tekkaman2440SBC2440V4#(15)“#include ” 最重要的头文件之一。包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明。(16)“#include ” 包含所构造内核版本信息的头文件。在学习过程中找到了几篇很好的参考文档:(1)第一章 模块(Modules) URL:/diary,103232026.shtml(2)从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响URL:/developerworks/cn/linux/l-module26/(3)Linux2.6内核驱动移植参考URL:/u1/40912/showart_377391.htmlLinux设备驱动程序学习(1)-字符设备驱动程序今天进入Linux设备驱动程序(第3版)第三章字符设备驱动程序的学习。这一章主要通过介绍字符设备scull(Simple Character Utility for Loading Localities,区域装载的简单字符工具)的驱动程序编写,来学习Linux设备驱动的基本知识。scull可以为真正的设备驱动程序提供样板。一、主设备号和次设备号主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。内核用dev_t类型()来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。在实际使用中,是通过中定义的宏来转换格式。(dev_t)-主设备号、次设备号MAJOR(dev_t dev)MINOR(dev_t dev)主设备号、次设备号-(dev_t)MKDEV(int major,int minor)建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在中声明:int register_chrdev_region(dev_t first, unsigned int count,char *name); /指定设备编号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); /动态生成设备编号void unregister_chrdev_region(dev_t first, unsigned int count); /释放设备编号分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。以下是在scull.c中用来获取主设备好的代码:if (scull_major) dev = MKDEV(scull_major, scull_minor); result = register_chrdev_region(dev, scull_nr_devs, scull); else result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,scull); scull_major = MAJOR(dev);if (result 0) printk(KERN_WARNING scull: cant get major %dn, scull_major); return result;在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。看到这里,就可以理解为什么mdev和udev可以动态、自动地生成当前系统需要的设备文件。udev就是通过读取sysfs下的信息来识别硬件设备的.(请看理解和认识udevURL:/u/6541/showart_396425.html)二、一些重要的数据结构大部分基本的驱动程序操作涉及及到三个重要的内核数据结构,分别是file_operations、file和inode,它们的定义都在。三、字符设备的注册内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev。代码应包含,它定义了struct cdev以及与其相关的一些辅助函数。注册一个独立的cdev设备的基本过程如下:1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)struct cdev *my_cdev = cdev_alloc();2、初始化struct cdev void cdev_init(struct cdev *cdev, const struct file_operations *fops) 3、初始化cdev.ownercdev.owner = THIS_MODULE;4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)int cdev_add(struct cdev *p, dev_t dev, unsigned count)从系统中移除一个字符设备:void cdev_del(struct cdev *p)以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):/* Set up the char_dev structure for this device.*/static void scull_setup_cdev(struct scull_dev *dev, int index)int err, devno = MKDEV(scull_major, scull_minor + index);cdev_init(&dev-cdev, &scull_fops);dev-cdev.owner = THIS_MODULE;dev-cdev.ops = &scull_fops;/这句可以省略,在cdev_init中已经做过err = cdev_add (&dev-cdev, devno, 1);/* Fail gracefully if need be 这步值得注意*/if (err)printk(KERN_NOTICE Error %d adding scull%d, err, index);四、scull模型的内存使用 以下是scull模型的结构体:/* Representation of scull quantum sets.*/struct scull_qset void *data;struct scull_qset *next;struct scull_dev struct scull_qset *data; /* Pointer to first quantum set */int quantum; /* the current quantum size */int qset; /* the current array size */unsigned long size; /* amount of data stored here */unsigned int access_key; /* used by sculluid and scullpriv */struct semaphore sem; /* mutual exclusion semaphore */struct cdev cdev; /* Char device structure*/;scull驱动程序引入了两个Linux内核中用于内存管理的核心函数,它们的定义都在: void *kmalloc(size_t size, int flags);void kfree(void *ptr);以下是scull模块中的一个释放整个数据区的函数(类似清零),将在scull以写方式打开和scull_cleanup_module中被调用:int scull_trim(struct scull_dev *dev) struct scull_qset *next, *dptr; int qset = dev-qset; /* 量子集中量子的个数*/ int i; for (dptr = dev-data; dptr; dptr = next) /* 循环scull_set个数次,直到dptr为NULL为止。*/ if (dptr-data) for (i = 0; i datai);/* 释放其中一个量子的空间*/ kfree(dptr-data);/* 释放当前的scull_set的量子集的空间*/ dptr-data = NULL;/* 释放一个scull_set中的void *data指针*/ next = dptr-next; /* 准备下个scull_set的指针*/ kfree(dptr);/* 释放当前的scull_set*/ dev-size = 0; /* 当前的scull_device所存的数据为0字节*/ dev-quantum = scull_quantum;/* 初始化一个量子的大小*/ dev-qset = scull_qset;/* 初始化一个量子集中量子的个数*/ dev-data = NULL;/* 释放当前的scull_device的struct scull_qset *data指针*/ return 0;以下是scull模块中的一个沿链表前行得到正确scull_set指针的函数,将在read和write方法中被调用:/*Follow the list*/struct scull_qset *scull_follow(struct scull_dev *dev, int n)struct scull_qset *qs = dev-data;/* Allocate first qset explicitly if need be */if (! qs) qs = dev-data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);if (qs = NULL)return NULL; /* Never mind */memset(qs, 0, sizeof(struct scull_qset);/* Then follow the list */while (n-) if (!qs-next) qs-next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);if (qs-next = NULL)return NULL; /* Never mind */memset(qs-next, 0, sizeof(struct scull_qset);qs = qs-next;continue;return qs;其实这个函数的实质是:如果已经存在这个scull_set,就返回这个scull_set的指针。如果不存在这个scull_set,一边沿链表为scull_set分配空间一边沿链表前行,直到所需要的scull_set被分配到空间并初始化为止,就返回这个scull_set的指针。 五、open和release open方法提供给驱动程序以初始化的能力,为以后的操作作准备。应完成的工作如下:(1)检查设备特定的错误(如设备未就绪或硬件问题);(2)如果设备是首次打开,则对其进行初始化;(3)如有必要,更新f_op指针;(4)分配并填写置于filp-private_data里的数据结构。而根据scull的实际情况,他的open函数只要完成第四步(将初始化过的struct scull_dev dev的指针传递到filp-private_data里,以备后用)就好了,所以open函数很简单。但是其中用到了定义在中的container_of宏,源码如下:#define container_of(ptr, type, member) (const typeof( (type *)0)-member ) *_mptr = (ptr);(type *)( (char *)_mptr - offsetof(type,member) );)其实从源码可以看出,其作用就是:通过指针ptr,获得包含ptr所指向数据(是member结构体)的type结构体的指针。即是用指针得到另外一个指针。release方法提供释放内存,关闭设备的功能。应完成的工作如下:(1)释放由open分配的、保存在file-private_data中的所有内容;(2)在最后一次关闭操作时关闭设备。由于前面定义了scull是一个全局且持久的内存区,所以他的release什么都不做。六、read和write read和write方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在中定义的:unsigned long copy_to_user(void _user *to, const void *from,unsigned long count);unsigned long copy_from_user(void *to, const void _user *from, unsigned long count);而值得一提的是以上两个函数和#define _copy_from_user(to,from,n)(memcpy(to, (void _force *)from, n), 0)#define _copy_to_user(to,from,n)(memcpy(void _force *)to, from, n), 0)之间的关系:通过源码可知,前者调用后者,但前者在调用前对用户空间指针进行了检查。至于read和write 的具体函数比较简单,就在实验中验证好了。七、模块实验这次模块实验的使用是友善之臂SBC2440V4,使用Linux内核。模块程序链接:scull模块源程序模块测试程序链接:模块测试程序测试结果:量子大小为6:Tekkaman2440SBC2440V4#cd /lib/modules/ Tekkaman2440SBC2440V4#insmod scull.ko scull_quantum=6Tekkaman2440SBC2440V4#cat /proc/devicesCharacter devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 7 vcs10 misc13 input14 sound81 video4linux89 i2c90 mtd116 alsa128 ptm136 pts180 usb189 usb_device204 s3c2410_serial252 scull253 usb_endpoint254 rtcBlock devices: 1 ramdisk256 rfd 7 loop31 mtdblock93 nftl96 inftl179 mmcTekkaman2440SBC2440V4#mknod -m 666 scull0 c 252 0Tekkaman2440SBC2440V4#mknod -m 666 scull1 c 252 1Tekkaman2440SBC2440V4#mknod -m 666 scull2 c 252 2Tekkaman2440SBC2440V4#mknod -m 666 scull3 c 252 3启动测试程序Tekkaman2440SBC2440V4#./scull_test write error! code=6 write error! code=6 write error! code=6 write ok! code=2 read error! code=6 read error! code=6 read error! code=6 read ok! code=2 0=0 1=1 2=2 3=3 4=4 5=5 6=6 7=7 8=8 9=9 10=10 11=11 12=12 13=13 14=14 15=15 16=16 17=17 18=18 19=19改变量子大小为默认值4000:Tekkaman2440SBC2440V4#cd /lib/modules/Tekkaman2440SBC2440V4#rmmod scullTekkaman2440SBC2440V4#insmod scull.ko启动测试程序Tekkaman2440SBC2440V4#./scull_testwrite ok! code=20read ok! code=200=0 1=1 2=2 3=3 4=45=5 6=6 7=7 8=8 9=910=10 11=11 12=12 13=13 14=1415=15 16=16 17=17 18=18 19=19Tekkaman2440SBC2440V4# 改变量子大小为6,量子集大小为2:Tekkaman2440SBC2440V4#cd /lib/modules/Tekkaman2440SBC2440V4#rmmod scullTekkaman2440SBC2440V4#insmod scull.ko scull_quantum=6 scull_qset=2启动测试程序Tekkaman2440SBC2440V4#./scull_testwrite error! code=6write error! code=6write error! code=6write ok! code=2read error! code=6read error! code=6read error! code=6read ok! code=20=0 1=1 2=2 3=3 4=45=5 6=6 7=7 8=8 9=910=10 11=11 12=12 13=13 14=1415=15 16=16 17=17 18=18 19=19 实验不仅测试了模块的读写能力,还测试了量子读写是否有效。 Linux设备驱动程序学习(2)-调试技术今天进入Linux设备驱动程序(第3版)第四章调试技术的学习。一、内核中的调试支持在前面已经建议过:学习编写驱动程序要构建安装自己的内核(标准主线内核)。最重要的原因之一是:内核开发者已经建立了多项用于调试的功能。但是由于这些功能会造成额外的输出,并导致能下降,因此发行版厂商通常会禁止发行版内核中的调试功能。为了实现内核调试,我在内核配置上增加了几项: Kernel hacking - * Magic SysRq key * Kernel debugging * Debug slab memory allocations * Spinlock and rw-lock debugging: b

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论