操作系统课程设计 内核模块编程和设备驱动程序Word下载.docx
《操作系统课程设计 内核模块编程和设备驱动程序Word下载.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计 内核模块编程和设备驱动程序Word下载.docx(28页珍藏版)》请在冰点文库上搜索。
2.2模块编程
写一个模块,必须有一定的多进程编程基础.因为编的程序不是以一个独立的程序的来运行的.另外,因为,模块需要在内核模式下运行,会碰到内核空间和用户空间数据交换的问题.一般的数据复制函数无法完成这一个过程.因此系统已入了一些非凡的函数以用来完成内核空间和用户空间数据的交换.这些函数有:
voidput_user、memcpy_tofs等等,需要说明的是.模块编程和内核的版本有很大的关系.假如版本不通可能造成,内核模块不能编译,或者.在运行这个模块时,出现不可测结果.如:
系统崩溃等.
对于每一个内核模块来说.必定包含两个函数:
intinit_module:
这个函数在插入内核时启动,在内核中注册一定的功能函数,或者用它的代码代替内核中某些函数的内容.因此,内核可以安全的卸载.
intcleanup_module:
当内核模块卸载时调用.将模块从内核中清除.
2.3内核模块与应用程序对比
应用程序是一个进程,编程从主函数main()开始,主函数main返回即是进程结束,使用glibc的库.
驱动程序是一系列内核函数,函数入口和出口不一样,使用Linux内核的函数,这些函数由内核在适当的时候来调用,这些函数可以用来完成硬件访问等操作.
2.4设备的分类
设备一般分为字符设备(chardevice)、块设备(blockdevice)、网络设备(networkdevice).
图1:
设备的分类
字符设备特点:
像字节流一样来存取的设备(如同文件)
通过/dev下的文件系统结点来访问
通常至少需要实现open,close,read,和write等系统调用
只能顺序访问的数据通道,不能前后移动访问指针.
特例:
比如framebuffer设备就是这样的设备,应用程序可以使用mmap或lseek访问图像的各个区域
块设备特点:
块设备通过位于/dev目录的文件系统结点来存取
块设备和字符设备的区别仅仅在于内核内部管理数据的方式
块设备有专门的接口,块设备的接口必须支持挂装(mount)文件系统.
应用程序一般通过文件系统来访问块设备上的内容
图2:
块设备驱动
图3:
网络设备驱动
linux中的大部分驱动程序,是以模块的形式编写的.这些驱动程序源码可以修改到内核中,也可以把他们编译成模块形式,在需要的时候动态加载.
一个典型的驱动程序,大体上可以分为这么几个部分:
1,注册设备
在系统初启,或者模块加载时候,必须将设备登记到相应的设备数组,并返回设备的主驱动号,例如:
对快设备来说调用refister_blkdec将设备添加到数组blkdev中.并且获得该设备号.并利用这些设备号对此数组进行索引.对于字符驱动设备来说,要使
用module_register_chrdev来获得祝设备的驱动号.然后对这个设备的所有调用都用这
个设备号来实现.
图4:
内核模块调用过程
2,定义功能函数
对于每一个驱动函数来说.都有一些和此设备密切相关的功能函数.那最常用的块设备或者字符设备来说.都存在着诸如openreadwriteioctrol这一类的操作.当系统社用这些调用时.将自动的使用驱动函数中特定的模块.来实现具体的操作.而对于特定的设备.上面的系统调用对应的函数是一定的.如:
在块驱动设备中.当系统试图读取这个设备时),就会运行驱动程序中的block_read这个函数.打开新设备时会调用这个设备驱动程序的device_open这个函数.
3,卸载模块
在不用这个设备时,可以将它卸载.主要是从/proc中取消这个设备的文件.可用特定的函数实现.
3设备驱动程序实现框架
4数据结构设计与主要功能函数
(1)字符设备描述结构体:
structcdev{
structkobjectkobj;
/*内嵌的kobject对象*/
structmodule*owner;
/*所属模块*/
conststructfile_operations*ops;
/*文件操作结构体*/
structlist_headlist;
/*双向循环链表*/
dev_tdev;
/*设备号32位高12位为主设备号,低20位为次设备号*/
unsignedintcount;
/*设备数量*/
};
(2)设备描述结构体
structmem_dev
{
char*data;
/*数据*/
unsignedlongsize;
/*长度*/
表1主要功能函数列表
主要函数列表
功能说明
intmem_open(structinode*inode,structfile*filp)
文件打开
intmem_release(structinode*inode,structfile*filp)
文件释放
staticssize_tmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
读文件
staticssize_tmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)
写文件
staticloff_tmem_llseek(structfile*filp,loff_toffset,intwhence)
文件定位
staticintmemdev_init(void)
设备驱动模块加载
staticvoidmemdev_exit(void)
卸载设备
5字符设备驱动程序的实现
下载安装LINUX内核,需要下载和本机一样版本的内核源码.本设备驱动程序是在linux-3.0.12内核下进行的.
5.1安装编译内核所需要的软件并编译内核.
使用以下命令安装需要的软件:
sudoapt-getinstallbuild-essentialautoconfautomakecvssubversionkernel-packagelibncurses5-dev
图5:
安装所需软件
在http:
//www.kernel.org/pub/linux/kernel/v3.0/下载内核linux-3.0.12.tar.bz2
将内核放置/usr/src目录下使用命令tar解压
sudotarjxvflinux-3.0.12.tar.bz2
图6:
解压内核
使用以下命令配置系统
cdlinux-3.0.12
cp/boot/config-`uname-r`./.config#拷贝目前系统的配置文件
makemenuconfig
终端会弹出一个配置界面
最后有两项:
loadakernelconfiguration...(.config)、saveakernelconfiguration...(.config)
选择loadakernelconfiguration保存,然后在选择saveakernelconfiguration再保存退出,并退出配置环境.
图7:
配置系统参数
接下来开始编译
make#这步需要比较长时间
makebzImage#执行结束后,可以看到在当前目录下生成了一个新的文件:
vmlinux,其属性为-rwxr-xr-x.
makemodules#/*编译模块*/
makemodules_install#这条命令能在/lib/modules目录下产生一个目录
图8:
make内核
图9:
makebzImage
图10:
makemodules
图11:
makemodules_install
为系统的include创建链接文件
cd/usr/include
rm-rfasmlinuxscsi
ln-s/usr/src/linux-3.0.12/include/asm-genericasm
ln-s/usr/src/linux-3.0.12/include/linuxlinux
ln-s/usr/src/linux-3.0.12/include/scsiscsi
5.2编写字符设备驱动程序并调试编译.
在/root下建一个目录,如:
cd/root
mkdirfirstdriver
touchmemdev.c#建立驱动程序文件
touchmemdev.h#头文件
touchMakefile#编写Makefile
注:
所有源码见附录。
编译驱动程序模块
在/root/firdriver目录下执行make
make-C/lib/modules/3.0.0-12-generic/buildM=/root/firstdrivermodules
图12:
make驱动程序
ls查看当前目录的内容
root@cloudswave-VirtualBox:
~/firstdriver#ls
Makefilememdev.hmemdev.mod.cmemdev.oModule.symvers
memdev.cmemdev.komemdev.mod.omodules.order
这里的memdev.ko就是生成的驱动程序模块.
通过insmod命令把该模块插入到内核
~/firstdriver#insmodmemdev.ko
查看插入的memdev.ko驱动
图13:
可以看到memdev驱动程序被正确的插入到内核当中,主设备号为88,该设备号为memdev.h中定义的#defineMEMDEV_MAJOR88.
如果这里定义的主设备号与系统正在使用的主设备号冲突,比如主设备号定义如下:
#defineMEMDEV_MAJOR254,那么在执行insmod命令时,就会出现如下的错误:
insmod:
errorinserting'
memdev.ko'
:
-1Deviceorresourcebusy
5.3.测试驱动程序
1,首先应该在/dev/目录下创建与该驱动程序相对应的文件节点,使用如下命令创建:
/dev#mknodmemdev0c880
使用ls查看创建好的驱动程序节点文件
/dev#ls-almemdev0
图14:
驱动程序节点文件
2,编写如下应用程序memtest.c,来对驱动程序进行测试.
编译并执行该程序
~/firstdriver#gcc-omemtestmemtest.c
~/firstdriver#./memtest
图15:
程序测试驱动
手动测试驱动的方法:
~/firstdriver#echo'
hahashiwo'
>
/dev/memdev0
~/firstdriver#cat/dev/memdev0
图16:
手动测试驱动
6.小结:
LINUX使用make编译驱动程序模块的过程.
Linux内核是一种单体内核,但是通过动态加载模块的方式,使它的开发非常灵活、方便.那么,它是如何编译内核的呢?
我们可以通过分析它的Makefile入手.以下是一个简单的hello内核模块的Makefile.
ifneq($(KERNELRELEASE),)#KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,内核发行的版本号
obj-m:
=hello.o#$(obj-m)表示对象文件(objectfiles)编译成可加载的内核模块.
else
KERNELDIR:
=/lib/modules/$(shelluname-r)/build
PWD:
=$(shellpwd)#M=PWD,返回当前目录执行Makefile文件
default:
$(MAKE)-C$(KERNELDIR)M=$(PWD)modules
clean:
rm-rf*.o*.mod.c*.mod.o*.ko
endif
当我们写完一个hello模块,编写类似以上的Makefile.然后用命令make编译.假设我们把hello模块的源代码放在/home/examples/hello/下.当我们在这个目录运行make时,make是怎么执行的呢?
首先,由于make后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行.于是default成为make的目标.make会执行make-C/lib/modules/3.0.0-12-generic/buildM=/home/examples/hello/modules是一个指向内核源代码/usr/src/linux的符号链接.可见,make执行了两次.第一次执行时是读hello模块的源代码所在目录/home/examples/hello/下的Makefile.第二次执行时是执/usr/src/linux/下的Makefile.
7结束语
本文给出了一个字符设备驱动与内核模块编程的完整实例,可以从中掌握内核编译、内核编程基础、设备驱动程序开发基础,优点是比较详细的给出了驱动开发的流程,并且把每一步的操作进行了详细的说明包括要执行的终端命令.最后还分析了驱动编译的过程.这样有利于初学者了解学习设备驱动的开发.有待进一步改进之处在于:
此设备驱动程序针对的是字符设备,实现的功能比较简单,以后有时间可根据这次的开发流程,参考api编写块设备和网络设备的驱动程序.
参考文献
[1]AbrahamSilberschatz操作系统概念(第七版)影印版高等教育出版社,2007
[2]费翔林Linux操作系统实验教程高等教育出版社,2009
[3](美)博韦等(DanielP.Bovet)编著深入理解LINUX内核北京:
中国电力出版社,2008
[4]JonahanCorbet编著Linux设备驱动程序北京:
中国电力出版社,2005
附录
字符设备驱动程序源码:
/******************************************************************************
*Name:
memdev.c
*字符设备驱动程序的框架结构,该字符设备并不是一个真实的物理设备,
*而是使用内存来模拟一个字符设备
********************************************************************************/
#include<
linux/module.h>
linux/types.h>
linux/fs.h>
linux/errno.h>
linux/mm.h>
linux/sched.h>
linux/init.h>
linux/cdev.h>
asm/io.h>
asm/system.h>
asm/uaccess.h>
linux/slab.h>
#include"
memdev.h"
staticmem_major=MEMDEV_MAJOR;
module_param(mem_major,int,S_IRUGO);
structmem_dev*mem_devp;
/*设备结构体指针*/
structcdevcdev;
/*文件打开函数*/
intmem_open(structinode*inode,structfile*filp)
structmem_dev*dev;
/*获取次设备号*/
intnum=MINOR(inode->
i_rdev);
if(num>
=MEMDEV_NR_DEVS)
return-ENODEV;
dev=&
mem_devp[num];
/*将设备描述结构指针赋值给文件私有数据指针*/
filp->
private_data=dev;
return0;
}
/*文件释放函数*/
/*读函数*/
unsignedlongp=*ppos;
unsignedintcount=size;
intret=0;
structmem_dev*dev=filp->
private_data;
/*获得设备结构体指针*/
/*判断读位置是否有效*/
if(p>
=MEMDEV_SIZE)
if(count>
MEMDEV_SIZE-p)
count=MEMDEV_SIZE-p;
/*读数据到用户空间*/
if(copy_to_user(buf,(void*)(dev->
data+p),count))
{
ret=-EFAULT;
}
else
*ppos+=count;
ret=count;
printk(KERN_INFO"
read%dbytes(s)from%d\n"
count,p);
returnret;
/*写函数*/
/*分析和获取有效的写长度*/
/*从用户空间写入数据*/
if(copy_from_user(dev->
data+p,buf,count))
written%dbytes(s)from%d\n"
/*seek文件定位函数*/
loff_tnewpos;
switch(whence){
case0:
/*SEEK_SET*/
newpos=offset;
break;
case1:
/*