嵌入式操作系统实验五 设备驱动.doc_第1页
嵌入式操作系统实验五 设备驱动.doc_第2页
嵌入式操作系统实验五 设备驱动.doc_第3页
嵌入式操作系统实验五 设备驱动.doc_第4页
嵌入式操作系统实验五 设备驱动.doc_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

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

文档简介

实验五 字符设备驱动王威 SA12226437 芯片设计班准备Makefile使用了设备驱动课上的编译驱动模块的“万能makefile”只需改一下依赖的文件名即可LINUX_KERNEL:=$(shell uname -r)KERNELDIR = /usr/src/linux-headers-$(LINUX_KERNEL)PWD := $(shell pwd)/KERNELDIR为内核路径,/usr/src/下存放内核,pwd 变量取得当前的路径。CC = $(CROSS_COMPILE)gccobj-m := driver_demo.o /CC为编译器的路径,CROSS_COMPILE为已经设定好的内核交叉编译器路径,如arm-linux-,与后面的 gcc链接即为arm-linux-gcc。定义生成的目标为driver_demo.ko的模块,所依赖的文件时.o文件,编译时发现没有,会自动生成,本机没有配置CROSS_COMPILE所以为空,使用默认编译器gccmoudles:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules/执行make modules命令,make会进入KERNEL_DIR目录执行此目录下的Makefile,然后在返回PWD目录执行自己写的Makefile。modules_install:cp driver_demo.ko $(INSTALLDIR)/执行make install命令,会将驱动拷贝到INSTALLDIR目录下。clean:rm -rf *.o * core .depend .*.cmd *.ko *.mod.c .tmp_versions/执行make clean命令,会将生成的.o及其它中间文件删除。也可以使用PPT上给出的makefile格式(千万注意格式,特别是空格)obj-m:=xxx.oKD ?=/lib/modules/$(shell uname -r)/buildPWD :=$(shell pwd)default:$(MAKE) -C $(KD) M=$(PWD) modulesclean:rm $(obj-m) *.o *.ko Module* module* *.mod.cMakefile的语法机制比较复杂,而且非常重要,比如分布在内核源码各个文件夹中的makefile文件定义了内核的编译规则等等,可以作为一个专项来学习,它的可读性比较差,使用了大量的简化符号,但本质都是gcc命令在makefile规则下的集合。Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 Linux 内核二进制文件。1. 编译模块将5个源文件拷贝到工作文件夹(确保之前的工作文件夹没有makefile文件),执行make命令,进行编译。其中driver_demo.c和test_demo.c、driver_demo2.c和test_demo2.c、是两套实验程序,即本实验的步骤从这两套程序重复两次。其中XX_demo.c严格按照模块编程要求(四个宏两个函数),加上驱动程序的要求(cdev结构体的填充,包括申请设备号并填充,编写file_operation并填充)。执行make后如下:生成如下文件:包含模块driver_demo.ko2. 安装驱动模块即将模块加载到内核,在加载的过程中会向内核注册cdev结构体,即注册一个字符设备。insmod driver_demo.koLsmod显示加载进内核的所有模块cat /proc/devices查看系统中设备相关:cat主要有三大功能:1.一次显示整个文件。$ cat filename2.从键盘创建一个文件。$ cat filename只能创建新文件,不能编辑已有文件.3.将几个文件合并为一个文件。$cat file1 file2 file相关:/proc/devices/下的设备是驱动程序生成的,它可产生一个major供mknod作为参数。 /dev/下的设备是通过mknod加上去的,用户通过此设备名来访问驱动。也就是说proc/device是显示系统拥有的所有外部设备(驱动程序注册),而dev是通过建立节点生成的所有的设备文件,外部设备面向程序员的接口252 memdevice是我们加载驱动模块时向内核注册的设备,它就是一个cdev结构体。系统为我们分配了主设备号252。3、在/dev下建立设备文件(结点):结点的名称根据程序中你定义的,要访问的结点名称而定。建立节点对应的设备号与系统为驱动模块分配的一致即可。test_demo.c里访问的设备文件名称是memdevice0,所以我们建立设备文件(结点)时使用该名称,但设备号要与系统分配的设备号保持一致。然后执行mknod /dev/memdevice0 c 252 0 来建立结点,随后查看/dev下建立的结点。一定要在dev目录下建立节点,linux系统默认从那个目录下寻找驱动文件。结点建立成功。4、编译并运行应用程序,它使用模块中的函数,用以验证驱动的可动性。执行gcc -o test_demo test_demo.c命令,生成test_demo可执行文件。或者直接gcc test_demo.c默认生成a.out验证驱动程序a.out运行结果如下5、驱动模块卸载卸载驱动模块,并删除设备结点。sudo rmmod driver_demo.korm -rf mmdevice0 像删普通文件一样就行另:对于实验的第二个使用driver文件夹下的driver_demo2.c和test_demo2.c这两个文件是陈博老师的设备驱动课上的实验源码。自己写难度太大了,将makefile文件中的编译目标改一下,用这两个文件将实验再做一遍。重复步骤1到步骤5一些小收获:1、 编写makefile文件时一定要注意,首字母一定要大写,Makefile2、 Makefile文件中的目标文件与依赖文件分写两行,依赖文件前面有一个tab空格,而PPT中给的是两个tab空格,会报错。3、 建立文件时可以再工作文件夹右键直接建立一个空文档,然后双击进入编辑。若是在终端里直接用gedit建立的文件, 可能会没有修改权限。4、 重命名:mv abc ABC5、 虚拟机设置一定要“共享粘贴板”,不然很麻烦。6、 VI两个新命令:X删除当前字符。I在光标前添加字符。A是在光标后添加字符。*附:试着分析一下驱动程序源码和验证程序源码。linux博大精深,需要慢慢积累,很多时候问问题,没有一个人能给出全面的回答*Driver_demo.c的源码如下:黑色粗字及彩色为注释/* *File:driver_demo.c *Created:Tue 28 Dec 2010 09:43:37 AM CST *Author:muryo *Description: linux driver example */编译器找头文件默认在/usr/include里找,如果/usr/include里找不到就根据编译参数-I指定的路径里找./usr/src/linux/include/linux是给编译内核用的 /usr/include/linux是给编译应用程序用的 #include #include #include #include #include #include #include #include #include #include / for copy_from_user. functions上面的头文件均在内核源码中,里面有模块编程和驱动编程的内核函数linux-2.6.22/include/linux/xx.h/* 全局变量设置 */#define MEM_DRIVER_MAJOR249 / 静态定义主设备号#define MEM_DRIVER_MAJOR0 / 动态定义主设备号#define MEM_DRIVER_SIZE128 / 定义内存空间大小#define _MEM_CLEAR1/ 定义ioctl命令,清空内存#define _MEM_REVERSE 5 / 定义ioctl命令,将内存中的数据倒置#define _MEM_LOGO3/ 定义ioctl命令,打印菱形/* 设备结构声明 */struct mem_devstruct cdev cdev; /字符设备结构,内核用cdev代表一个字符设备,我们要填充其成员,并在模块加载的时候向内核注册这个结构体,相当于添加了一个设备。其成员如下 unsigned char mem_spaceMEM_DRIVER_SIZE; /设备内存空间;struct mem_dev *dev;static int mem_driver_major = MEM_DRIVER_MAJOR;/* 函数声明 */static int memdriver_init(void);/ 设备初始化函数static void memdriver_exit(void);/ 设备释放函数,这两个函数在insmod和rmmod是执行,放在init和exit宏里。下面的这几个函数非常重要,是file_operation的成员,是系统调用的真正有作用的驱动函数。/设备读取操作函数,static ssize_t memdriver_read(struct file *filp, char _user *buff, size_t count, loff_t *offp);/设备写操作函数static ssize_t memdriver_write(struct file *filp, const char _user *buff, size_t count, loff_t *offp);/设备控制函数驱动程序一般需支持通过Ioctl实现各种控制与参数设置,如串口可设置波特率等多参数cmd变量存放命令,驱动代码根据cmd里面的值进行switch-case处理分支static int memdriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);/设备打开函数static int memdriver_open(struct inode *inode, struct file *filp);/设备关闭函数static int memdriver_release(struct inode *inode, struct file *filp);定义一个file_operation结构体类型的变量memdriver_fops,填充cdevstatic const struct file_operations memdriver_fops = .owner = THIS_MODULE, .open = memdriver_open, .release = memdriver_release, .read = memdriver_read, .write = memdriver_write, .ioctl = memdriver_ioctl,;/初始化设备memdriver_cdev结构函数, 我们定义的memdriver_cdev中包含了cdev和设备内存空间static void memdriver_cdev_setup(struct mem_dev *dev, int index) int err;int devno = MKDEV(mem_driver_major, index);/由主次得到设备号dev_t,index为次设备号cdev结构体的dev_t成员定义了设备号为32位高12位为主设备号低20位为次设备号获取主设备号MAJOR(dev_t dev)获取次设备号MINOR(dev_t dev)通过主设备号和次设备号生成dev_tMKDEV(int major, int minor)linux-2.6.22/include/linux/cdev.hvoid cdev_init (struct cdev *cdev,const struct file_operations *fops); cdev: the structure to initialize fops: the file_operations for this device cdev_init(&dev-cdev, &memdriver_fops); /将file_operation填充到memdriver_cdev中cdev中 dev-cdev.owner = THIS_MODULE; dev-cdev.ops = &memdriver_fops; err = cdev_add(&dev-cdev, devno, 1);利用已经初始化的cdev结构体,和申请了的设备号,向内核注册一个字符设备。cdev_add() add a char device to the system-linux-2.6.22/include/linux/cdev.hint cdev_add (struct cdev * p, dev_t dev, unsigned count); p: the cdev structure for the device dev: the first device number for which this device is responsible count: the number of consecutive minor numbers corresponding to this device cdev_add() adds the device represented by p to the system, making it live immediately. A negative error code is returned on failure. if(err)printk(KERN_NOTICE memdriver: Error %d adding mem_device %dn, err, index);对于做嵌入式或者熟悉linux内核的人来说,对printk这个函数一定不会感到陌生。printk相当于printf的孪生姐妹,她们一个运行在用户态,另一个则在内核态被人们所熟知。printk是在内核中运行的向控制台输出显示的函数,Linux内核首先在内核空间分配一个静态缓冲区,作为显示用的空间,然后调用sprintf,格式化显示字符串,最后调用tty_write向终端进行信息的显示。printk与printf的差异,是什么导致一个运行在内核态而另一个运行用户态?其实这两个函数的几乎是相同的,出现这种差异是因为tty_write函数需要使用fs指向的被显示的字符串,而fs是专门用于存放用户态段选择符的,因此,在内核态时,为了配合tty_write函数,printk会把fs修改为内核态数据段选择符ds中的值,这样才能正确指向内核的数据缓冲区,当然这个操作会先对fs进行压栈保存,调用tty_write完毕后再出栈恢复。总结说来,printk与printf的差异是由fs造成的,所以差异也是围绕对fs的处理。static int memdriver_init(void) int result; dev_t devno = MKDEV(mem_driver_major, 0); if(mem_driver_major)/宏值不为零的时候,用该宏值与index生成的设备号直接申请 result = register_chrdev_region(devno, 1, memdevice);int register_chrdev_region(dev_t first, unsigned int count, char *name);向内核申请设备号,该设备号可用?如果分配成功进行, register_chrdev_region 的返回值是 0。出错的情况下, 返回一个负的错误码。相当于手动申请设备号 else result = alloc_chrdev_region(&devno, 0, 1, memdveice);如果设备号未知,向系统动态申请未被占用的设备号的情况,当调用成功后,会把得到的设备号放入第一个参数dev中mem_driver_major = MAJOR(devno); if(result cdev);调用cdev_del函数从系统注销字符设备 kfree(dev);/释放空间 unregister_chrdev_region(MKDEV(mem_driver_major, 0), 1);/释放设备号static int memdriver_open(struct inode *inode, struct file *filp)try_module_get(dev-cdev.owner); / 设备打开计数器, 打开设备计数器+1 try_module_get 如果模块已经插入内核,则递增该模块引用计数;如果该模块还没有插入内核,则返回0表示出错include/linux/module.h ,在lsmod后,数字即引用数 printk(KERN_NOTICE memdriver: Device Open succeed!n); return 0;static int memdriver_release(struct inode *inode, struct file *filp)module_put(dev-cdev.owner); / 释放设备,计数器-1模块在被使用时,是不允许被卸载的。 2.4内核中,模块自身通过 MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT宏来管理自己被使用的计数。 2.6内核提供了更健壮、灵活的模块计数管理接口 try_module_get(&module), module_put(&module)取代2.4中的模块使用计数管理宏;模块的使用计数不必由自身管理,而且在管理模块使用计数时考虑到 SMP与PREEMPT机制的影响。 int try_module_get(struct module *module); 用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中。 void module_put(struct module *module); 减少模块使用计数。 try_module_get与module_put 的引入与使用与2.6内核下的设备模型密切相关。模块是用来管理硬件设备的,2.6内核为不同类型的设备定义了struct module *owner 域,用来指向管理此设备的模块。 printk(KERN_NOTICE memdriver: Device Release succeed!n); return 0;static ssize_t memdriver_read(struct file *filp, char _user *buff, size_t count, loff_t *offp) if(count MEM_DRIVER_SIZE)count = MEM_DRIVER_SIZE; /* * 将内核空间数据拷贝到用户空间 */ if(copy_to_user(buff, dev-mem_space, count) printk(KERN_NOTICE memdriver: Copy data to user error!n);return -EFAULT; else printk(KERN_NOTICE memdriver: Copy %d byte to user!n, count); return count;通常情况下,应用程序通过内核接口访问驱动程序,因此,驱动程序需要和应用程序交换数据。Linux将存储器分为“内核空间”和“用户空间”。操作系统和驱动程序在内核空间运行,应用程序在用户空间运行,两者不能简单地使用指针传递数据。因为Linux系统使用了虚拟内存机制,用户空间的内存可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。Linux内核提供了多个函数和宏用于内核空间和用户空间传递数据。主要有:access_ok(),copy_to_user(),copy_from_user,put_user,get_user。1.access_ok()函数原型:int access_ok(int type,unsigned long addr,unsigned long size)函数access_ok()用于检查指定地址是否可以访问。参数type为访问方式,可以为VERIFY_READ(可读),VERIFY_WRITE(可写)。addr为要操作的地址,size为要操作的空间大小(以字节计算)。函数返回1,表示可以访问,0表示不可以访问。2.copy_to_user()和copy_from_user()函数原型:unsigned long copy_to_user(void *to,const void *from,unsigned long len) unsigned long copy_from_user(void *to,const void *from,unsigned long len)这两个函数用于内核空间与用户空间的数据交换。copy_to_user()用于把数据从内核空间拷贝至用户空间,copy_from_user()用于把数据从用户空间拷贝至内核空间。第一个参数to为目标地址,第二个参数from为源地址,第三个参数len为要拷贝的数据个数,以字节计算。这两个函数在内部调用access_ok()进行地址检查。返回值为未能拷贝的字节数。3.get_user()和put_user()函数原型:int get_user(x,p) int put_user(x,p)这是两个宏,用于一个基本数据(1,2,4字节)的拷贝。get_user()用于把数据从用户空间拷贝至内核空间,put_user()用于把数据从内核空间拷贝至用户空间。x为内核空间的数据,p为用户空间的指针。这两个宏会调用access_ok()进行地址检查。拷贝成功,返回0,否则返回-EFAULT。4.还有两个函数_copy_to_user()和_copy_from_user(),功能与copy_to_user()和copy_from_user()相同,只是不进行地址检查。还有两个宏_get_user()和_put_user(),功能与get_user()和put_user()相同,也不进行地址检查。/* * 数据倒置函数 */static void do_write(int num) int i; char tmp;/ printk(arg = %dn, num); for(i = 0; i 1); i+, num-) tmp = dev-mem_spacenum-1;dev-mem_spacenum-1 = dev-mem_spacei;dev-mem_spacei = tmp; static ssize_t memdriver_write(struct file *filp, const char _user *buff, size_t count, loff_t *offp) if(count MEM_DRIVER_SIZE)count = MEM_DRIVER_SIZE; /* * 将用户空间的数据拷贝到内核空间 */ if(copy_from_user(dev-mem_space, buff, count) printk(KERN_NOTICE memdriver:Copy data from user error!n);return -EFAULT; else printk(KERN_NOTICE memdriver:Copy %d bytes from user!n, count); return count;/* * 使用printk函数打印菱形 */static void do_logo(int height) int i, a, b, j; int h = height; for(i=1; i=h; i+) for(a = 1; a = (h-i); a+) printk( );for(b = 1; b = 1; j-) for(a = 1; a = (h-j); a+) printk( );for(b = 1; b mem_space, 0, MEM_DRIVER_SIZE); printk(KERN_NOTICE memdriver:Clear all memory!n); break;case _MEM_REVERSE: printk(KERN_NOTICE memdriver:Reverse the datan); printk(arg = %dn, arg); do_write(arg); break; case _MEM_LOGO: do_logo(10); break; default: return -EINVAL; return 0;MODULE_AUTHOR(muryo);MODULE_LICENSE(GPL);module_init(memdriver_init);module_exit(memdriver_exit);/模块编程要素,四个宏,两个函数*test_demo.c的源码如下:黑色粗字及彩色为注释/*/* *File:test_demo.c *Created:Tue 28 Dec 2010 02:17:49 PM CST *Author:muryo *Description: test all interfaces of driver_demo */#include #include #include #define BUF_SIZE32#define _MEM_CLEAR1/清除内存#define _MEM_REVERSE 5/数据倒置#define _MEM_LOGO3/打印菱形static void display(char *buf, int size) int i; int line = 0; printf(n*n); printf(%d:t, line); for(i = 0; i size; i+) printf(%dt, bufi);if(i+1) % 4 = 0) printf(n); printf(%d:t, line); line+; printf(n*n);int main(int argc, char *argv) int fd, i; int size = BUF_SIZE; char bufBUF

温馨提示

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

评论

0/150

提交评论