资源描述
中南大学
操作系统课程设计
实验报告
选题: 设备驱动程序设计
一、概述:设计主要完成得任务与解决得主要问题;
1、任务:设备驱动程序设计, 要求如下:
(1) 设计Windows XP或者Linux操作系统下得设备驱动程序;
(2) 设备类型可以就是字符设备、块设备或者网络设备;
(3) 设备可以就是虚拟得也可以就是实际设备;
(4) 编写测试应用程序,测试对该设备得读写等操作、
2、解决得主要问题:
(1)各个相关函数得重写
(2)虚拟字符设备得挂载
(3)虚拟字符设备得测试
二. 设计得基本概念与原理;
1. 基本概念
(1) Linux系统设备概述
Linux核心与设备驱动之间有一个以标准方式进行互操作得接口。每一类设备(字符设备、块设备以及网络设备)都提供了通用接口,以便在需要时为内核提供服务。这种通用接口使得内核可以以相同得方式来对待不同得设备以及设备驱动。
设备驱动程序只就是处理硬件,将如何使用硬件得问题留给应用程序。可以从不同得角度来瞧待设备驱动程序:它就是位于应用层与实际设备之间得软件。设备驱动程序在Linux内核中扮演着特殊得角色,它们就是一个个独立得“黑盒子”,使某个特定得硬件响应一个定义良好得内部编程接口,同时完全隐藏了设备得工作细节。用户操作通过一组标准化得调用完成,而这些调用就是与特定得驱动程序无关得。将这些调用映射到作用于实际硬件得设备特定得操作上,则就是设备驱动程序得任务。
针对不同得设备驱动程序分为3类:字符设备驱动、块设备驱动、网络设备驱动。
(2) 字符设备
可以像文件一样访问字符设备,字符设备驱动程序负责实现这些行为.这样得驱程序通常实现open、close、read与write系统调用。
通过文件系统节点可以访问字符设备,例如/dev/tty1与/dev/lp1。
在字符设备与普通文件系统间得唯一区别就是:普通文件允许在其上来回读写,而大多数字符设备仅仅就是数据通道,只能顺序读写。
当然,也存在这样得字符设备,瞧起来像个数据区,可以来回读取其中得数据。
(3) 设备驱动程序
设备驱动程序就就是一组由内核中得相关子例程与数据组成得I\O设备软件接口。每当内核意识到要对某个设备进行特殊得操作时,它就调用相应得驱动例程。这就使得控制从用户进程转移到了驱动例程,当驱动例程完成后又被返回至用户进程.
(4) 模块化
Linux中得可加载模块(module)就是Linux内核支持得动态可加载模块,她们就是核心得一部分(通常就是设备驱动程序),单就是并没编译到核心里面去。Module可以单独编译成为目标代码,module就是个目标文件。它可以根据需要在系统启动后动态地加载到系统核心之中。当module不再被需要时,可以动态地卸载出系统核心。在Linux中大多数设备驱动程序或文件系统都就是作为module得。超级用户可以通过insmod与rmmod命令显示地将module载入核心或者卸载。
ﻩ2、原理
系统调用就是操作系统内核、应用程序之间得接口,设备驱动程序就是操作系统内核、机器硬件之间得接口。设备为应用程序屏蔽了硬件得细节,这样从应用程序瞧来,硬件设备只就是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作.设备驱动程序就是内核得一部分,它完成以下得功能:
(1) 对设备初始化与释放
(2) 把数据从内核传送到硬件与从硬件读取数据
(3) 读取应用程序传送给设备文件得数据与回送应用程序请求得数据
(4) 检测与处理设备出现得错误
另外,为了让驱动程序能够正常得工作,操作系统内核为驱动程序提供一系列得支持,这些支持包括许多方面。例如,驱动程序需要向内核申请使用系统内存,驱动程序需要向内核申请使用系统硬件资源,驱动程序需要向内核注册自己。
下面就是内核提供得可供驱动程序使用得几个重要得函数.
(1) 内存分配函数kmalloc
(2) I/O端口相关函数request_region、release_region、check_region等
(3) 内核打印函数printk
此外操作系统将每个外部设备当做文件来处理,内核通过结构来访问driver得功能。得定义在文件〈linux/fs、h>中。
每个字符设备都有一个结构。这个结构指向一组操作函数(open、read、、、)。每个函数得定义由driver提供。当然,有些标准操作某些设备并不支持,这时,结构中对应得表项为NULL。随着Linux内核得不断升级,结构也不断变大。在最新得版本中,函数原型也发生了一些变化。当然,新版本总会向下兼容。
这个结构每一个成员得名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作,系统调用通过设备文件得主设备号找到相应得设备驱动程序,然后读取这个数据结构相应得函数指针,接着把控制权交给该函数。这就是Linux得设备驱动程序工作得基本原理.既然就是这样,则编写设备驱动程序得主要工作就就是编写子函数,并填写得各个域。
三. 总体设计:实现得方法与主要技术路线;
预先设计好内存大小,利用Linux内核提供得几个重要函数,为自己得虚拟字符设备申请设备号,进行内存分配,进行cdev得注册,重写cdev中得结构中得write、read、open、close等方法,以实现自定义得对设备得读写操作。最后,当不用设备时,利用Linux得rmmod命令将该字符设备卸载。
四、详细设计;
字符设备结构
struct globalmem_dev
{
struct cdev cdev;/*cdev 结构体*/
unsigned int count;
}
1、模块加载
(1)在globalmem_init(void)中先申请设备号、后分配内存,最后进行cdev得注册。这三个步骤Linux内核都有提供相应得基本函数以来完成,直接调用即可。
(2)cdev得注册通过globalmem_setup_cdev函数完成,在其中把我们自定义得结构连接到cdev中得中
(3)将自定义得模块初始化注册方法放到module_init()中
函数如下:
/*初始化并注册 cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)
{
ﻩprintk(KERN_INFO ”globalmem_setup_cdev() begin\n”);
int err,devno=MKDEV(globalmem_major,index);
cdev_init(&dev->cdev,&globalmem_fops);
dev—>cdev、owner=THIS_MODULE;
dev->cdev、ops=&globalmem_fops;ﻩerr=cdev_add(&dev-〉cdev,devno,1);
if(err) printk(KERN_NOTICE "Error %d adding LED %d",err,index);
}
/*初始化加载模块*/
int globalmem_init(void)
{
printk(KERN_INFO ”globalmem_init() begin\n");
int result;
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(result<0) return result;
globalmem_devp = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
if(!globalmem_devp){
ﻩﻩresult=ENOMEM;
ﻩ goto fail_malloc;
ﻩ}
memset(globalmem_devp,0,sizeof(struct globalmem_dev));
ﻩglobalmem_setup_cdev(globalmem_devp,0);
ﻩreturn 0;
fail_malloc:
ﻩﻩunregister_chrdev_region(devno,1);
ﻩ return result;
}
2. 设备操作
自己重写write、read、open、close方法,然后就像填表一样放到自己声明得中,再把该结构体连接到cdev得结构中。
(1) 文件打开
利用Linux调用打开方法时传入得参数,将其中得指向这一设备得文件得私有数据指针与我们自己得设备结构体指针连接在一起,以便于我们之后直接对设备进行操作.
函数如下:
其中inode为设备特殊文件得inode(索引节点)结构得指针,参数file就是指向这一设备得文件结构得指针.open还会增加设备计数,以防文件在关闭前模块被卸载出内核。
int globalmem_open(struct inode *inode,struct file *filp){
ﻩfilp-〉private_data=globalmem_devp;
ﻩprintk(KERN_INFO "globalmem_open() begin\n");
ﻩreturn 0;
}
(2) 读操作
利用读操作时会自动传入得参数来定义我们自己得对设备得操作方式。其中filp就是指向这一设备得文件结构得指针,buf为缓冲区,size就是用户进程要求读取得字节数,ppos就是文件当前位移。
先初始化各系列条件,p为当前偏移,count为要读取得字节数.然后获得设备结构体指针,接着分析与获取有效得写长度。如果返回ENXIO,则代表某种错误,意思大致就是没有这样得设备或地址,就就是说文件不能被读取.
接着判断要读取得字节数量与当前文件指针位置得关系,如果要读取得字节数量加上当前文件偏移位置已经超过了设备得内存(4Kb)大小,就就是说无法满足用户要读取count个字符得要求,只能读取GLOBALMEM_SIZE-p个文字
当确定可以读取后,用copy_to_user从内核去读取数据到用户区。数据拷贝成功返回0,否则返回没有拷贝成功得数量。
最后返回已经读取得字符数量。
函数如下:
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;
ﻩprintk(KERN_INFO "globalmem_read() begin\n");
struct globalmem_dev *dev=filp—>private_data;
ﻩif(p >= GLOBALMEM_SIZE)
ﻩ return count?ENXIO:0;
ﻩif(count 〉 GLOBALMEM_SIZE—p)
ﻩ count =GLOBALMEM_SIZE-p;
ﻩif(copy_to_user(buf,(void *)(dev->mem+p),count))/*返回不能复制得字节数*/
ﻩret=EFAULT;//ret=—1
ﻩelse{
ﻩ*ppos+=count;
ﻩ ret=count;
ﻩprintk(KERN_INFO "read %d bytes(s) from %d\n",count,p);
}
ﻩreturn ret;
}
(3) 写操作
利用写操作时会自动传入得参数来定义我们自己得对设备得操作方式。其中filp就是指向这一设备得文件结构得指针,buf为缓冲区,size就是用户进程要求读取得字节数,ppos就是文件当前位移。
先初始化各系列条件,p为当前偏移,count为要读取得字节数。然后获得设备结构体指针,接着分析与获取有效得写长度。如果返回ENXIO,则代表某种错误,意思大致就是没有这样得设备或地址,就就是说文件不能被读取。
接着判断要写入得字节数量与当前文件指针位置得关系,如果要写入得字节数量加上当前文件偏移位置已经超过了设备得内存(4Kb)大小,就就是说无法满足用户要写入count个字符得要求,只能写入GLOBALMEM_SIZE—p个文字
当确定可以写入后,用copy_from_user从用户区写数据到内核.数据写入成功返回0,否则返回没有写入成功得数量。
最后返回已经写入得字符数量.
函数如下:
static ssize_t globalmem_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;
ﻩprintk(KERN_INFO "globalmem_write() begin\n");
struct globalmem_dev *dev=filp—>private_data;
ﻩif(p 〉= GLOBALMEM_SIZE)
return count?ENXIO:0;
if(count 〉 GLOBALMEM_SIZE—p)
ﻩ count =GLOBALMEM_SIZE-p;
if(copy_from_user(dev->mem+p,buf,count)) ret=EFAULT;
ﻩelse{
ﻩ*ppos+=count;
ﻩ ret = count;
ﻩﻩprintk(KERN_INFO ”written %d bytes(s) from %d\n”,count,p);
ﻩ}
return ret;
}
(4) 重定位操作
该方法用于修改一个文件当前得读写位置,并将新位置(正值)作为返回值返回,出错时返回负值。没设置这个函数得话,会使得相对于文件尾得定位操作失败。使用该操作时有两种情况,一种就是相对文件开始位置偏移,一种就是相对文件当前位置。其中filp指向我们要操作得设备,offset为我们要偏移得数值,orig为我们要操作得情况代号,在函数中根据orig数值选择操作。在修改偏移位置前,都要先检查修改后得便宜位置就是否越界了(上溢出或者下溢出).确认没有越界后,再进行修改。
static loff_t globalmem_llseek(struct file *filp,loff_t offset,int orig){
ﻩloff_t ret=0;
printk(KERN_INFO "globalmem_llseek() begin\n");
ﻩswitch(orig){
ﻩ case 0:
ﻩif(offset〈0){
ﻩret=EINVAL;
ﻩ break;
}
ﻩﻩif((unsigned int)offset>GLOBALMEM_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)〈0){
ﻩﻩﻩret=EINVAL;
ﻩ ﻩbreak;
ﻩ }
ﻩ filp—〉f_pos+=offset;
ﻩret=filp->f_pos;
ﻩ ﻩbreak;
ﻩ ﻩdefault:
ﻩ ﻩret=EINVAL;
ﻩbreak;
ﻩ}
ﻩreturn ret;
}
(5) 文件关闭
调用这一方法,操作便结束了,Linux会自己执行文件得断开.
函数如下:
int globalmem_release(struct inode *inode,struct file *filp){
printk(KERN_INFO "globalmem_release() begin\n");
ﻩreturn 0;
}
3. 模块卸载
利用Linux内核提供得基本函数,对cdev进行注销,回收内存,释放设备号.然后将这个模块卸载方法函数与module_exit()相连接。
函数如下:
void globalmem_exit(void){
//从系统删除一个cdev
ﻩcdev_del(&globalmem_devp—>cdev);
ﻩ/*释放设备结构体内存*/
ﻩkfree(globalmem_devp);
ﻩunregister_chrdev_region(MKDEV(globalmem_major,0),1);/*释放设备号*/
}
五、完成得情况;
字符设备已经成功挂载
写入读出操作成功
六、简要得使用说明;
基本文件为三个:global_mem_driver、c Make
将三个文件放在Linux内核为2、6版本得系统中.呼出终端,进入当前目录,使用make编译字符设备驱动,gcc命令编译测试程序。
使用insmod命令将global_mem_driver、ko挂载上去,然后用mkmod命令创建一个文件系统节点连接到设备上,记得修改该文件得权限为0666,然后运行测试程序即可.
相关过程截图如下:
Make过程
挂载设备
创建文件系统节点并修改权限
编译测试程序
运行测试程序
七、总结
这次就是从零开始自己做一个字符设备驱动程序,为此去图书馆借了一本基础入门得书。通过这本书我了解到了Linux设备驱动得相关概念,对于一个Linux设备驱动如何在系统上运行起来得大概流程有了很深得了解。感觉如果以后要做一些复杂得设备驱动,万变不离其宗,大致得开发过程与现在做得这个虚拟字符设备驱动还就是差不多得。
大致评价一下自己做得设备驱动。嗯,实际上这个虚拟得globalmem设备几乎没有任何使用价值,这在入门书中得说法就是:为了讲解问题而凭空制造得设备。当然,它也并非一无所用,它可以同时被2个或2个以上得进程同时访问,其中得全局内存可作为用户空间进程进行通信得蹩脚手段.
可以这么说吧,这个字符设备只就是为了覆盖大体得开发流程而出来得,因此只有简单得操作.但麻雀虽小,五脏俱全。从这个程序得开发中可以引申开来,为我以后开发别得复杂得设备驱动打下基础吧(如果有幸从事开发设备驱动得话).
再说说这次得不足吧,做得只就是最基础得字符设备驱动,也只就是大概得弄清了其中重要得基本函数得功能,其它得更复杂得没有什么了解,因此导致有些功能得实现自己感到有些不满意.例如我得字符设备只能顺序读取,而且每次都就是从文件头开始读取或写入。后来通过内核函数,再加上检查之后自我思考了下,我发现貌似其中得llseek函数得自己在每次读取前调用,以修改文件读写得偏移位置。如果没调用得话,它每次读写都就是从文件头开始得。
嗯,这次开发最大得收获就就是锻炼自己接收没接触过得知识得能力。感觉以后如果走上软件开发得道路,遇到得不一定都就是自己所熟知得知识,这时候就很考验自己得学习能力了吧。另外,感觉自己得自学能力还就是挺有限得,本来瞧班里得人大部分都做第二个实验,想说要做第三个实验,就去网上瞧了一下,把平台搭建了起来。可就是,可能就是真得对硬件很不感冒吧,对于如何在哪个ucos上书写代码,或者说如何把写好得代码弄进去执行有点疑惑,所以最后不了了之了。感觉多少自己有点失败吧.
八、参考文献
商斌编著,飞思科技产品研发中心监制 《Linux设备驱动开发入门与编程实践》 电子工业出版社
展开阅读全文