1、操作系统课程设计 内核模块编程和设备驱动程序课程设计题目 内核模块编程和设备驱动程序 学生姓名 朱小波 学号 * 专 业 计算机科学与技术 班级 20091121 指导教师 张莉莉 完成日期 2012年 1月 5日Linux内核模块编程与设备驱动程序摘 要:本文给出了一个linux字符设备驱动程序的例子,其包括了内核模块编程.其主要功能是:在内存虚拟一个字符设备,并由编写的驱动程序加载到系统,完成字符的输入与输出功能.此设备驱动程序可以用作linux实践教学的实例.关键词: 字符设备驱动;内核模块编程;虚拟;模拟1 前 言驱动程序是应用程序和硬件设备的一个接口,linux设备驱动程序属于内核的
2、一部分,熟练驱动程序和内核模块开发需要硬件知识,了解操作系统的实现,需要了解内核基础知识,了解内核中的并发控制和同步以及复杂的软件结构框架.本文论述了如何在linux下实现一个简单的字符设备驱动程序,主要完成了内核树的建立、内核的编译、字符设备的模拟、字符设备的驱动、字符设备驱动程序的测试等.本文首先阐述了设备驱动程序和内核模块编程的基础知识,然后给出了实现一个设备驱动程序的总体框架,最后根据框架一步步详细完成了一个字符设备驱动程序,包括终端命令和源程序的编写.做好设备驱动程序可以更好的了解硬件和操作系统,本设备驱动程序可以作为操作系统实验课程的实例.2 设备驱动程序和内核模块编程相关基础知识
3、linux内核是一个整体是结构.因此向内核添加任何东西.或者删除某些功能 ,都十分困难.为了解决这个问题. 引入了内核机制.从而可以可以动态的想内核中添加或者删除模块.模块不被编译在内核中,因而控制了内核的大小.然而模块一旦被插入内核,它就和内核其他部分一样.这样一来就会增加一部分系统开销.同时,假如模块出现问题.,也许会带来系统的崩溃. 2.1模块的实现机制: 启动时,由函数 void inti_modules 来初始化模块,.因为启动事很多时候没有模块.这个函数往往把内核自 身当作一个虚模块. 如由系统需要,则调用一系列以sys 开头的函数,对模块进行操作. 如:sys_creat_mod
4、ules,sys_inti_modules , sys_deldte_modules等等. 这里会用到一些模块的数据就结构,在/usr/scr/linux/include/linux/module.h 中.块的加入有两种方法:一是手动加入:如:insmod modulename.另一种是根据需要,动态的加载模块.如你执行命令: $mount -t msdos /dev/hdd /mnt/d 时.系统便自动加载 FAT模块,以支持MSDOS的文件系统. 2.2 模块编程 写一个模块,必须有一定的多进程编程基础.因为编的程序不是以一个独立的程序的来运行的.另外,因为,模块需要在内核模式下运行,会碰
5、到内核空间和用户空间数据交换的问题.一般的数据复制函数无法完成这一个过程.因此系 统已入了一些非凡的函数以用来完成内核空间和用户空间数据的交换. 这些函数有:void put _user、memcpy_tofs 等等,需要说明的是.模块编程和内核的版本有很大的关系. 假如版本不通可能造成,内核模块不能编译,或者.在运行这个模块时,出现不可测结果.如:系统崩溃等. 对于每一个内核模块来说.必定包含两个函数 :int init_module :这个函数在插入内核时启动,在内核中注册一定的功能函数,或者用它的代码代替内核中某些函数的内容.因此,内核可以安全的卸载. int cleanup_modul
6、e:当内核模块卸载时调用.将模块从内核中清除. 2.3内核模块与应用程序对比应用程序是一个进程,编程从主函数main()开始,主函数main返回即是进程结束,使用glibc的库.驱动程序是一系列内核函数,函数入口和出口不一样,使用Linux内核的函数,这些函数由内核在适当的时候来调用,这些函数可以用来完成硬件访问等操作.2.4设备的分类设备一般分为字符设备(char device)、块设备(block device)、网络设备(network device).图1:设备的分类字符设备特点:像字节流一样来存取的设备( 如同文件 )通过/dev下的文件系统结点来访问通常至少需要实现 open, c
7、lose, read, 和 write 等系统调用只能顺序访问的数据通道,不能前后移动访问指针.特例:比如framebuffer设备就是这样的设备,应用程序可以使用mmap或lseek访问图像的各个区域块设备特点:块设备通过位于 /dev 目录的文件系统结点来存取块设备和字符设备的区别仅仅在于内核内部管理数据的方式块设备有专门的接口,块设备的接口必须支持挂装(mount)文件系统.应用程序一般通过文件系统来访问块设备上的内容图2:块设备驱动图3:网络设备驱动linux中的大部分驱动程序,是以模块的形式编写的.这些驱动程序源码可以修改到内核中,也可以把他们编译成模块形式,在需要的时候动态加载.
8、一个典型的驱动程序,大体上可以分为这么几个部分: 1,注册设备在系统初启,或者模块加载时候,必须将设备登记到相应的设备数组,并返回设备的主驱动号,例如:对快设备来说调用 refister_blkdec将设备添加到数组blkdev中.并且获得该设备号.并利用这些设备号对此数组进行索引.对于字符驱动设备来说,要使用 module_register_chrdev来获得祝设备的驱动号.然后对这个设备的所有调用都用这 个设备号来实现.图4:内核模块调用过程2,定义功能函数 对于每一个驱动函数来说.都有一些和此设备密切相关的功能函数.那最常用的块设备或者字符设备来说.都存在着诸如 open read wr
9、ite ioctrol这一类的操作.当系统社用这些调用时.将自动的使用驱动函数中特定的模块.来实现具体的操作.而对于特定的设备.上面的系统调用对应的函数是一定的. 如:在块驱动设备中.当系统试图读取这个设备时),就会运行驱动程序中的block_read 这个函数. 打开新设备时会调用这个设备驱动程序的device_open 这个函数. 3,卸载模块 在不用这个设备时,可以将它卸载.主要是从/proc 中取消这个设备的文件.可用特定的函数实现. 3 设备驱动程序实现框架4 数据结构设计与主要功能函数(1)字符设备描述结构体:struct cdev struct kobject kobj; /*内
10、嵌的kobject对象*/ struct module *owner; /*所属模块*/ const struct file_operations *ops; /*文件操作结构体*/ struct list_head list; /*双向循环链表*/ dev_t dev; /*设备号 32位 高12位为主设备号,低20位为次设备号*/ unsigned int count; /*设备数量*/;(2) 设备描述结构体struct mem_dev char *data; /*数据*/ unsigned long size; /*长度*/;表 1 主要功能函数列表主要函数列表功能说明int mem_
11、open(struct inode *inode, struct file *filp) 文件打开int mem_release(struct inode *inode, struct file *filp) 文件释放static ssize_t mem_read(struct file *filp, char _user *buf, size_t size, loff_t *ppos)读文件static ssize_t mem_write(struct file *filp, const char _user *buf, size_t size, loff_t *ppos)写文件static
12、 loff_t mem_llseek(struct file *filp, loff_t offset, int whence)文件定位static int memdev_init(void)设备驱动模块加载static void memdev_exit(void)卸载设备5 字符设备驱动程序的实现下载安装LINUX内核,需要下载和本机一样版本的内核源码.本设备驱动程序是在linux-3.0.12内核下进行的.5.1 安装编译内核所需要的软件并编译内核.使用以下命令安装需要的软件:sudo apt-get install build-essential autoconf automake cv
13、s subversion kernel-package libncurses5-dev图5:安装所需软件在http:/www.kernel.org/pub/linux/kernel/v3.0/ 下载内核linux-3.0.12.tar.bz2将内核放置/usr/src目录下 使用命令tar解压 sudo tar jxvf linux-3.0.12.tar.bz2图6:解压内核使用以下命令配置系统cd linux-3.0.12cp /boot/config-uname -r ./.config #拷贝目前系统的配置文件make menuconfig终端会弹出一个配置界面最后有两项:load a
14、kernel configuration. (.config)、save a kernel configuration. (.config)选择load a kernel configuration保存,然后在选择save akernel configuration再保存退出,并退出配置环境.图7:配置系统参数接下来开始编译make #这步需要比较长时间make bzImage #执行结束后,可以看到在当前目录下生成了一个新的文件: vmlinux, 其属性为-rwxr-xr-x.make modules #/* 编译 模块 */make modules_install #这条命令能在/lib
15、/modules目录下产生一个目录图8:make内核图9:make bzImage图10:make modules图11:make modules_install为系统的include创建链接文件cd /usr/includerm -rf asm linux scsiln -s /usr/src/linux-3.0.12/include/asm-generic asmln -s /usr/src/linux-3.0.12/include/linux linuxln -s /usr/src/linux-3.0.12/include/scsi scsi5.2 编写字符设备驱动程序并调试编译.在/r
16、oot下建一个目录,如:cd /rootmkdir firstdrivertouch memdev.c #建立驱动程序文件touch memdev.h #头文件touch Makefile #编写Makefile注:所有源码见附录。编译驱动程序模块在/root/firdriver目录下执行makemake -C /lib/modules/3.0.0-12-generic/build M=/root/firstdriver modules图12:make 驱动程序ls查看当前目录的内容 rootcloudswave-VirtualBox:/firstdriver# lsMakefile memd
17、ev.h memdev.mod.c memdev.o Module.symversmemdev.c memdev.ko memdev.mod.o modules.order这里的memdev.ko就是生成的驱动程序模块.通过insmod命令把该模块插入到内核rootcloudswave-VirtualBox:/firstdriver# insmod memdev.ko查看插入的memdev.ko驱动图13:查看插入的memdev.ko驱动可以看到memdev驱动程序被正确的插入到内核当中,主设备号为88,该设备号为memdev.h中定义的#define MEMDEV_MAJOR 88.如果这里
18、定义的主设备号与系统正在使用的主设备号冲突,比如主设备号定义如下:#define MEMDEV_MAJOR 254,那么在执行insmod命令时,就会出现如下的错误:rootcloudswave-VirtualBox:/firstdriver# insmod memdev.koinsmod: error inserting memdev.ko: -1 Device or resource busy5.3.测试驱动程序 1,首先应该在/dev/目录下创建与该驱动程序相对应的文件节点,使用如下命令创建:rootcloudswave-VirtualBox:/dev# mknod memdev0 c
19、88 0使用ls查看创建好的驱动程序节点文件 rootcloudswave-VirtualBox:/dev# ls -al memdev0图14:驱动程序节点文件 2,编写如下应用程序memtest.c,来对驱动程序进行测试. 编译并执行该程序rootcloudswave-VirtualBox:/firstdriver# gcc -o memtest memtest.crootcloudswave-VirtualBox:/firstdriver# ./memtest图15:程序测试驱动手动测试驱动的方法:rootcloudswave-VirtualBox:/firstdriver# echo
20、haha shi wo /dev/memdev0rootcloudswave-VirtualBox:/firstdriver# cat /dev/memdev0图16:手动测试驱动6.小结:LINUX使用make编译驱动程序模块的过程.Linux内核是一种单体内核,但是通过动态加载模块的方式,使它的开发非常灵活、方便.那么,它是如何编译内核的呢?我们可以通过分析它的Makefile入手.以下是 一个简单的hello内核模块的Makefile.ifneq ($(KERNELRELEASE),) #KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,内核发行的版本号ob
21、j-m:=hello.o #$(obj-m)表示对象文件(object files)编译成可加载的内核模块.elseKERNELDIR:=/lib/modules/$(shell uname -r)/buildPWD:=$(shell pwd) #M=PWD,返回当前目录执行Makefile文件default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modulesclean: rm -rf *.o *.mod.c *.mod.o *.koendif当我们写完一个hello模块,编写类似以上的Makefile.然后用命令make编译.假设我们把hello模块的源代码放
22、在/home/examples/hello/下.当我们在这个目录运行make时,make是怎么执行的呢? 首先,由于make后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行.于是default成为make的目标.make会执行make-C/lib/modules/3.0.0-12-generic/build M=/home/examples/hello/modules是一个指向内核源代码/usr/src/linux的符号链接.可见,make执行了两次.第一次执行时是读hello模块的源代码所在目录/home/examples/hello/下的Make
23、file.第二次执行时是执/usr/src/linux/下的Makefile. 7 结 束 语本文给出了一个字符设备驱动与内核模块编程的完整实例,可以从中掌握内核编译、内核编程基础、设备驱动程序开发基础,优点是比较详细的给出了驱动开发的流程,并且把每一步的操作进行了详细的说明包括要执行的终端命令.最后还分析了驱动编译的过程.这样有利于初学者了解学习设备驱动的开发.有待进一步改进之处在于:此设备驱动程序针对的是字符设备,实现的功能比较简单,以后有时间可根据这次的开发流程,参考api编写块设备和网络设备的驱动程序.参 考 文 献1 Abraham Silberschatz 操作系统概念(第七版)影
24、印版 高等教育出版社,2007 2 费翔林 Linux操作系统实验教程 高等教育出版社,20093 (美)博韦等 (DanielP. Bovet) 编著 深入理解LINUX内核 北京:中国电力出版社,20084 Jonahan Corbet编著 Linux设备驱动程序 北京:中国电力出版社,2005附录字符设备驱动程序源码:/*Name: memdev.c* 字符设备驱动程序的框架结构,该字符设备并不是一个真实的物理设备,* 而是使用内存来模拟一个字符设备*/#include #include #include #include #include #include #include #incl
25、ude #include #include #include #include #include memdev.hstatic mem_major = MEMDEV_MAJOR;module_param(mem_major, int, S_IRUGO);struct mem_dev *mem_devp; /*设备结构体指针*/struct cdev cdev;/*文件打开函数*/int mem_open(struct inode *inode, struct file *filp) struct mem_dev *dev; /*获取次设备号*/ int num = MINOR(inode-i_
26、rdev); if (num = MEMDEV_NR_DEVS) return -ENODEV; dev = &mem_devpnum; /*将设备描述结构指针赋值给文件私有数据指针*/ filp-private_data = dev; return 0;/*文件释放函数*/int mem_release(struct inode *inode, struct file *filp) return 0;/*读函数*/static ssize_t mem_read(struct file *filp, char _user *buf, size_t size, loff_t *ppos) uns
27、igned long p = *ppos; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp-private_data; /*获得设备结构体指针*/ /*判断读位置是否有效*/ if (p = MEMDEV_SIZE) return 0; if (count MEMDEV_SIZE - p) count = MEMDEV_SIZE - p; /*读数据到用户空间*/ if (copy_to_user(buf, (void*)(dev-data + p), count) ret = - EFAULT; else
28、*ppos += count; ret = count; printk(KERN_INFO read %d bytes(s) from %dn, count, p); return ret;/*写函数*/static ssize_t mem_write(struct file *filp, const char _user *buf, size_t size, loff_t *ppos) unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct mem_dev *dev = filp-private_data
29、; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p = MEMDEV_SIZE) return 0; if (count MEMDEV_SIZE - p) count = MEMDEV_SIZE - p; /*从用户空间写入数据*/ if (copy_from_user(dev-data + p, buf, count) ret = - EFAULT; else *ppos += count; ret = count; printk(KERN_INFO written %d bytes(s) from %dn, count, p); return ret;/* seek文件定位函数 */static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) loff_t newpos; switch(whence) case 0: /* SEEK_SET */ newpos = offset; break; case 1: /*
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1