《2022年第一个驱动程序 .pdf》由会员分享,可在线阅读,更多相关《2022年第一个驱动程序 .pdf(8页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Linux 下第一个驱动程序因为在 Ubuntu 环境下写的文章和做的实验,没有安装linux 下比较好用的截图工具,所以没有附带太多截屏,还望海涵,不过该描述的都到位了,希望对你有用。曾经还一直处于应用程序开发的我,以为驱动开发者是那么的厉害,以为只有牛人才能走到这一步,随着知识的积累,发现并非如此,驱动开发并不像想象中那么特别,俗话说术业有专攻,开发者只是使用的工具不同,且从事的领域不同,产品不同罢了。只要能作出好的产品,你就是一个 ” 牛人 ” 。从这里开始进行系统化的驱动学习,主线是Linux 设备驱动开发详解,之前大致看过这本书,起初感觉有些晦涩,但看了两本内核的书籍以后,重新回来读
2、起来就比较顺流了,一口气读了好几章 (主要是前几章是知识介绍性文章)。所以这里顺便推荐两本内核的书籍:Linux 内核设计与实现,我看的是第二版,写的非常棒,简单易懂,要在介绍内核原理与实现机制,广度到了,深度不够,所以最好还得配合下边这本书一块儿看。Understanding Linux kernel 深入理解 linux 内核,这本书的第三版是基于2.6内核的, 06 年出版。我看的是英文原版的,所以看得速度比较满,不过正好和Linux 设备驱动开发详解对接上了,刚看过Understanding Linux kernel中的同步异步,应该是第五章那里,然后 Linux 设备驱动开发详解就在
3、第七章也讲到了,这样,可能那么多机制:锁,读写锁,顺序锁,信号量,读写信号量一下出来这么多东西的话有些接受不了,但如果你之前看了 Linux 内核设计与实现后,最起码不会感觉恐慌,其实学习新的知识就是这样,一回生两回熟,再难理解的东西,功夫到了,也就理解了。好的,开始第一个驱动程序的学习,实例来自Linux 设备驱动开发详解,这里是创建了一个虚拟的字符设备globalmem,也就是一片内核空间的内存区域,来实现内核空间和用户空间的信息传递。 (确实,我举不出来比这更好的例子来作第一个例子了,不过请相信,哪怕就是这个例子也是我一个字母一个字母敲出来的,并未直接取材自隋书的源码,主要是看我的注释,
4、和我遇到的问题以及解决它的全过程,成功者找方法,失败者找借口!呵呵) 不废话,上代码,别心急,看注释。下边是驱动的源码,附上了详尽的注释:globalmem.c * #include #include #include #include #include #include #include #include #include #include #define GLOBALMEM_SIZE 0 x1000 /*4k 的空间 */ #define MEM_CLEAR 0 x1 /*清空全局内存 */ #define GLOBALMEM_MAJOR 250 /* 预设的主设备号*/ static i
5、nt globalmem_major = GLOBALMEM_MAJOR; /*用面向对象思想对cdev重新封装,以便于方便我们的操作*/ struct globalmem_dev 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 8 页 - - - - - - - - - struct cdev cdev; unsigned char memGLOBALMEM_SIZE; ; struct globalmem_dev *globalmem_devp; /*声明一个全局的设
6、备结构体*/ /*用来注册到file_operations 结构中 open */ int globalmem_open(struct inode *inode,struct file *filp) filp-private_data = globalmem_devp; /* 当有多个同类设备时,用私有变量访问很有必要也很方便*/ return 0; /*用来注册到file_operations 结构中 release */ int globalmem_release(struct inode *inode,struct file *filp) return 0; /*用来注册到file_op
7、erations 结构中 ioctl */ static int globalmem_ioctl(struct inode *inodep,struct file *filp,unsigned int cmd,unsigned long arg) struct globalmem_dev *dev = filp-private_data; /*从私有数据获取设备结构体指针*/ switch (cmd) case MEM_CLEAR: memset(dev-mem,0,GLOBALMEM_SIZE); printk(KERN_INFOglobalmem is set to zeron); bre
8、ak; default: return -EINV AL; return 0; /*用来注册到file_operations 结构中 read */ static ssize_t globalmem_read(struct file *filp,char _user *buf,size_t size,loff_t *ppos) unsigned long p = *ppos; /*获取到当前全局内存的*/ unsigned int count = size; /*获取到要读取数据的大小*/ int ret = 0; /*用来记录返回值 */ struct globalmem_dev *dev
9、= filp-private_data; /*从私有变量获取到设备结构体指针*/ /*分析和获取有效的读长度,就是看给的要读取的长度是否合法*/ if(p=GLOBALMEM_SIZE) return 0; if(countGLOBALMEM_SIZE-p) /*如果要读取的量比剩余的还多,只给它可读到的量*/ count=GLOBALMEM_SIZE-p; /*一切就绪后就开始往用户空间读了,这里的读指的是用户空间的读,就是说从内核拷贝到用户空间,* 因为内核是万能的有着所有的权限,主动权在于它,并不是说用户想读就可以读的,因为该空间是在内核状态下分配出来的*/ if(copy_to_use
10、r(buf,(void*)(dev-mem+p),count) /*从 mem的偏移量p 处拷贝 count 个数据到 buf 所指区 */ ret = -EFAULT; /* 拷贝失败,返回 -EFAULT*/ else /* 拷贝成功,返回重新拷贝的数据量并重新计算的偏移量*/ *ppos+=count; ret = count; printk(KERN_INFO read %u bytes(s) from %lun,count,p); return ret; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 -
11、 - - - - - - 第 2 页,共 8 页 - - - - - - - - - /*用来注册到file_operations 结构中 write */ static ssize_t globalmem_write(struct file *filp,char _user *buf,size_t size,loff_t *ppos) unsigned long p = *ppos; /*获取到当前全局内存的*/ unsigned int count = size; /*获取到要读取数据的大小*/ int ret = 0; /*用来记录返回值 */ struct globalmem_dev
12、*dev = filp-private_data; /*从私有变量获取到设备结构体指针*/ /*分析和获取有效的写长度,就是看给的要写的长度是否合法*/ if(p=GLOBALMEM_SIZE) return 0; if(countGLOBALMEM_SIZE-p) /*如果要读取的量比剩余的还多,只给它可读到的量*/ count=GLOBALMEM_SIZE-p; /*一切就绪后就开始往共享空间里写了,这里的写指的就是说从用户空间拷贝到内核,* 因为内核是万能的有着所有的权限,主动权在于它,并不是说用户想写就可以写的,因为该空间是在内核状态下分配出来的*/ if(copy_from_user
13、(dev-mem+p,buf,count) /*往 mem 的偏移量 p 处拷贝 count 个数据 (从 buf 所指区 )*/ ret = -EFAULT; /* 拷贝失败,返回 -EFAULT*/ else /* 拷贝成功,返回重新拷贝的数据量并重新计算的偏移量*/ *ppos+=count; ret = count; printk(KERN_INFO read %u bytes(s) from %lun,count,p); return ret; /*seek 文件定位函数 */ static loff_t globalmem_llseek(struct file *filp,loff
14、_t offset,int orig) loff_t ret = 0; switch (orig) case 0: /*相对于文件开始位置*/ if(offsetGLOBALMEM_SIZE) ret = -EINVAL; break; filp-f_pos=(unsigned int)offset; ret=filp-f_pos; break; case 1: /*相对于文件当前位置*/ if(filp-f_pos+offset)GLOBALMEM_SIZE) ret = -EINVAL; break; if(filp-f_pos+offset)f_pos+=offset; ret=filp
15、-f_pos; break; default: ret = -EINVAL; break; return ret; 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 8 页 - - - - - - - - - /*文件操作结构体*/ static const struct file_operations globalmem_fops= .owner = THIS_MODULE, .llseek = globalmem_llseek, .read = globalmem_re
16、ad, .write = globalmem_write, .ioctl = globalmem_ioctl, .open = globalmem_open, .release = globalmem_release, ; /*对设备进行初始化的函数*/ static void globalmem_setup_cdev(struct globalmem_dev *dev,int index) int err,devno = MKDEV(globalmem_major,index); /*MKDEV宏来生成设备号主设备号占12 位,从设备号占20 位*/ cdev_init(&dev-cdev,
17、&globalmem_fops); dev-cdev.owner = THIS_MODULE; err = cdev_add(&dev-cdev,devno,1); if(err) printk(KERN_NOTICE ERROR %d adding globalmem %d,err,index); /*模块相关函数 */ /*为了便于开发和派错,建议上来先定义这些模块相关的初始化和卸载函数,你完全可以* 只做一个空的函数实现,意在每写一个功能函数就编译(make)一次,这样会很有利于开发的*/ /*驱动加载函数 */ int globalmem_init(void) int result;
18、dev_t devno =MKDEV(globalmem_major,0); /*申请设备号 */ if(globalmem_major) result = register_chrdev_region(devno,1,globalmem); else /* 动态申请 */ result = alloc_chrdev_region(&devno,0,1,globalmem); globalmem_major = MAJOR(devno); if(resultcdev); /*注销掉设备 */ kfree(globalmem_devp); /* 释放掉分配的内存,好借好还再借不难*/ unreg
19、ister_chrdev_region(MKDEV(globalmem_major,0),1); /*释放设备号 */ MODULE_AUTHOR(Jun ); MODULE_LICENSE(DUAL BSD/GPL); module_init(globalmem_init); module_exit(globalmem_exit); Makefile 的写法和前面一篇文章的helloworld 的模块的 Makefile 写法一致,换个模块的名字而已,这里是:obj-m += globalmem.o # XXX.o 对应于你的XXX.c 同时也是你的模块名称all: make -C /usr
20、/src/linux-headers-2.6.32-27-generic M=$(shell pwd) modules # 这里通过 uname -r 命令获取系统信息,同时拼装出内核源码树的路径;# pwd 获取当前文件夹,这就要求着在你进行make的时候要在源码目录下。clean: make -C /usr/src/linux-headers-2.6.32-27-generic M=$(shell pwd) clean # 原理同上准备好后开始进行编译,就是make一下就 OK ;make时又遇到了这样的问题:make: Nothing to be done for all. Make c
21、learn 时也会出现:make: Nothing to be done for clearn. 网上说的都是一编译好了,只是没有修改源文件,所以没有进行再编译(我知道这也是 make存在的理由,它的一方面功能就是这样的,避免重复编译未修改的文件),但显示是它确实不存在这个问题,我小纠结了一下,并且也没有其他任何编译时的报错提示。为了做测试,我又试着编译之前的helloworld 的模块,终于报错了,哈哈哈。问题在于,make中的make -C /usr/src/linux-headers-$(shell uname -r) M=$(shell pwd) modules ,该命令是动态的通过
22、shell命令的 uname来获取当前内核源码路径,并进行连接编译驱动的。而我下载并构建的内核是: 2.6.32-30-generic,而此时系统装载的是 (也就是 uname r 命令得到的内核版本):2.6.30-27-generic , 而该版本的内核源码树我已经手动删除,所以我的源码路径和通过uname -r组装出来的是不一致的,所以我把Makefile 中的命令手动修改成了:make -C /usr/src/linux-headers- 2.6.32-30-generic M=$(shell pwd) modules ,直接指定路径(这样的可维护性下降了,不过我们的工程太小,可以忽略
23、这个问题)。OK ,try again,make 通过了,目录下编译出来了那群你梦寐以求想看到的文件们,他们这时显得是如此的可爱。这里还要说的是,模块只是一种把自己的代码动态加载到内核的手段,这也就说明了为什么我的驱动模块的Makefile文件为何和helloworld模块的Makefile文件是一模一样的(确实,模块的名字是不一样的,你知道我是什么意思),所以对于驱动来说,模块就是一条小船,它把代码运载到了kernel所在的海域,作为一个载体把驱动的代码带到了内核空间,从而使得你在用户空间调用某些系统操作时,OS可以找到对应的代码来完成你的请求。名师资料总结 - - -精品资料欢迎下载 -
24、- - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 8 页 - - - - - - - - - 好的,编译好了,就该装载并测试了。$ insmod globalmem.ko #“ Ubuntu下必要时记着加sudo,因为我已经开启了ubuntu的su”呵呵,又来错误了。看来又要成长了,要善待你碰到的没一个失败,毕竟她是成功他妈。装载时报错了:insmod: error inserting globalmem.ko: -1 Invalid module format 还是因为我系统现装载的内核和对模块进行编译的内核版本不一
25、致造成的,所以,我得把在构建的内核源码树中版本较新的内核安装到系统中去才行。这里安装新内核的方法,可以借鉴这篇博文 (哈哈,我越来越爱西邮的学生了,如果你想考研或者招聘,西邮出来的做linux的都是非常棒的。额,作广告了,呵呵):http:/ our module ,try again。$ insmod globalmem.ko well done,weve already make it.好的,接下来在用户空间测试一下。驱动是针对设备而言的(虽然这里的设备并非是实实在在的,只是一片内存),而linux下的设备又都是抽象成文件来看待的(毕竟unix最早是从一个文件系统演变过来的,也就是这一壮举
26、,使得驱动的开发容易多了,统一的抽象带来了非常大的方便)。所以我们要把这个设备文件创建出来。$ mknod /dev/globalmem c 250 0 #创建一个主设备号为250次设备号是0的字符设备文件globalmem到dev下。然后就可以开始测试了:$echo “ Hello jun” /dev/globalmem #写“hello jun”到设备提示:bash: /dev/globalmem: Permission denied 权限不够, ls -l 发现:crw-r-r- 1 root root 250, 0 Oct 20 15:47 /dev/globalmem 你要么用 su
27、do 来 echo ,要么把文件权限该一下,这个自由留给你了。呵呵$cat /dev/globalmem #显示设备文件的内容显示如下:名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 8 页 - - - - - - - - - rootjun-desktop:/home/jun/driver/ch6# cat /dev/globalmem hello jun 好的,大功告成!后记说明:1、做驱动的话,建议可以在原生的linux环境下,其实也挺方便的。2、推荐一个 c/c+
28、 的 IDE 吧 Code:Blocks ,挺好用的集成开发环境,只是第一次用它就喜欢上了,不过和SCIM输入法稍有冲突,注释的时候要输入中文,如果中文文字删减时,会出现打不上中文的情况,要调成英文状态再调回中文状态才能继续。另外,它带有语句联想功能,当然是只支持用户空间c 函数库德联想,不支持kernel中的函数或结构。个人感觉要比Vi 用着来的有效率些,呵呵3、对于 echo 是“! ”的问题。截屏中可以看到,要输出! 还要进行转义字符转义。4、从文章可以看出,作者我确实够笨,每次记录都会碰见如此多的看似比较弱智的问题,好在找到了解决办法,详尽写实的记录比较符合本人博客的特色。5、编写和编
29、译该驱动的环境并非前边文章介绍的虚拟机环境,是本机硬盘上的Ubuntu10.04,在我机器上有些时日了,忘了内核版本才凸显了上边遇到的很多问题, unlikely(你的开发过程中不会遇到类似的装载内核和编译内核不协调的情况 ); 不过你可能会在其他地方遇到,遇到是千万别说没见过而束手无策。6、提前把第一个驱动实验的记录过程发blog 了,驱动相关的知识还没有内容介名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 8 页 - - - - - - - - - 绍,可能和前边知识不太衔接,得花谢时间尽快补上。不过希望读者和我同步一起不断积累内核体系的相关知识,非常方便理解的。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 8 页 - - - - - - - - -