第六章 设备管理实验.docx
- 文档编号:28739921
- 上传时间:2023-07-19
- 格式:DOCX
- 页数:37
- 大小:427.90KB
第六章 设备管理实验.docx
《第六章 设备管理实验.docx》由会员分享,可在线阅读,更多相关《第六章 设备管理实验.docx(37页珍藏版)》请在冰豆网上搜索。
第六章设备管理实验
第六章设备管理实验
【实验目的】
通过本实验的学习,了解Linux操作系统中的设备驱动程序包括哪些组成部分,并能编写简单的字符设备(scull,SimpleCharacterUtilityforLoadingLocalities)和块设备(sbull,SimpleBlockUtilityforLoadingLocalities)的驱动程序以及对所编写设备驱动程序的测试,最终了解Linux操作系统是如何管理设备的。
【准备知识】
一.设备驱动程序的简单介绍
Linux设备驱动程序集成在内核中,实际上是处理或操作硬件控制器的软件。
从本质上讲,驱动程序是常驻内存的低级硬件处理程序的共享库,设备驱动程序就是对设备的抽象处理;也即是说,设备驱动程序是内核中具有高特权级的、常驻内存的、可共享的下层硬件处理例程。
设备驱动程序软件封装了如何控制这些设备的技术细节,并通过特定的接口导出一个规范的操作集合(见图1);内核使用规范的设备接口(字符设备接口和块设备接口)通过文件系统接口把设备操作导出到用户空间程序中。
(由于本实验不涉及网络设备,故在此就不作讨论)
在Linux中,字符设备和块设备的I/O操作是有区别的。
块设备在每次硬件操作时把多个字节传送到主存缓存中或从主存缓存中把多个字节信息传送到设备中;而字符设备并不使用缓存,信息传送是一个字节一个字节地进行的。
Linux操作系统允许设备驱动程序作为可装载内核模块实现,这也就是说,设备的接口实现不仅可以在Linux操作系统启动时进行注册,而且还可以在Linux操作系统启动后装载模块时进行注册。
总之,Linux操作系统支持多种设备,这些设备的驱动程序有如下一些特点:
(1)内核代码:
设备驱动程序是内核的一部分,如果驱动程序出错,则可能导致系统崩溃。
(2)内核接口:
设备驱动程序必须为内核或者其子系统提供一个标准接口。
比如,一个终端驱动程序必须为内核提供一个文件I/O接口;一个SCSI设备驱动程序应该为SCSI子系统提供一个SCSI设备接口,同时SCSI子系统也必须为内核提供文件的I/O接口及缓冲区。
(3)内核机制和服务:
设备驱动程序使用一些标准的内核服务,如内存分配等。
(4)可装载:
大多数的Linux操作系统设备驱动程序都可以在需要时装载进内核,在不需要时从内核中卸载。
(5)可设置:
Linux操作系统设备驱动程序可以集成为内核的一部分,并可以根据需要把其中的某一部分集成到内核中,这只需要在系统编译时进行相应的设置即可。
(6)动态性:
当系统启动且各个设备驱动程序初始化后,驱动程序将维护其控制的设备。
如果该设备驱动程序控制的设备不存在也不影响系统的运行,此时的设备驱动程序只是多占用了一点系统内存罢了。
二.设备驱动程序与外界的接口
每种类型的驱动程序,不管是字符设备还是块设备都为内核提供相同的调用接口,故内核能以相同的方式处理不同的设备。
Linux为每种不同类型的设备驱动程序维护相应的数据结构,以便定义统一的接口并实现驱动程序的可装载性和动态性。
Linux设备驱动程序与外界的接口可以分为如下三个部分:
(1)驱动程序与操作系统内核的接口:
这是通过数据结构file_operations来完成的。
(2)驱动程序与系统引导的接口:
这部分利用驱动程序对设备进行初始化。
(3)驱动程序与设备的接口:
这部分描述了驱动程序如何与设备进行交互,这与具体设备密切相关。
可归结为如下图2:
三.设备驱动程序的组织结构
设备驱动程序有一个比较标准的组织结构,一般可以分为下面三个主要组成部分:
(1)自动配置和初始化子程序
这部分程序负责检测所要驱动的硬件设备是否存在以及是否能正常工作。
如果该设备正常,则对设备及其驱动程序所需要的相关软件状态进行初始化。
这部分程序仅在初始化时被调用一次。
(2)服务于I/O请求的子程序
该部分又可称为驱动程序的上半部分。
系统调用对这部分进行调用。
系统认为这部分程序在执行时和进行调用的进程属于同一个进程,只是由用户态变成了内核态,而且具有进行此系统调用的用户程序的运行环境。
故可以在其中调用与进程运行环境有关的函数。
(3)中断服务子程序
该部分又可称为驱动程序的下半部分。
设备在I/O请求结束时或其它状态改变时产生中断。
中断可以产生在任何一个进程运行时,因此中断服务子程序被调用时不能依赖于任何进程的状态,因而也就不能调用与进程运行环境有关的函数。
因为设备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务子程序时都带有一个或多个参数,以唯一标识请求服务的设备。
四.设备驱动程序的代码
设备驱动程序是一些函数和数据结构的集合,这些函数和数据结构是为实现管理设备的一个简单接口。
操作系统内核使用这个接口来请求驱动程序对设备进行I/O操作。
甚至,我们可以把设备驱动程序看成是一个抽象数据类型,它为计算机中的每个硬件设备都建立了一个通用函数接口。
由于一个设备驱动程序就是一个模块,所以在内核内部用一个file结构来识别设备驱动程序,而且内核使用file_operatuions结构来访问设备驱动程序中的函数。
了解设备驱动程序代码的如下几个部分:
◆驱动程序的注册与注销。
◆设备的打开与释放。
◆设备的读写操作。
◆设备的控制操作。
◆设备的中断和轮询处理。
第一部分字符设备驱动程序的代码
1了解什么是字符设备
2了解字符设备的基本入口点
字符设备的基本入口点也可称为子程序,它们被包含在驱动程序的file_operations结构中。
①open()函数;②release()函数;③read()函数;④write()函数;
⑤ioctl()函数;⑥select()函数。
3字符设备的注册
设备驱动程序提供的入口点在设备驱动程序初始化时向系统登记,以便系统调用。
Linux系统通过调用register_chrdev()向系统注册字符型设备驱动程序。
register_chrdev()定义如下:
#include
#include
intregister_chrdev(unsignedintmajor,constchar*name,structfile_operations*ops);
其中major时设备驱动程序向系统申请的主设备号。
如果它为0,则系统为该驱动程序动态地分配第一个空闲的主设备号,并把设备名和文件操作表的指针置于chrdevs表的相应位置。
name是设备名,ops是对各个调用入口点的说明。
register_chrdev()函数返回0表示注册成功;返回-EINVAL表示申请的主设备号非法,一般主设备号大于系统所允许的最大设备号;返回-EBUSY表示所申请的主设备号正被其它设备驱动程序使用。
如果动态分配主设备号成功,则该函数将返回所分配的主设备号。
如果register_chrdev()操作成功,则设备名就会出现在/proc/devices文件中。
字符设备注册以后,还必须在文件系统中为其创建一个代表节点。
该节点可以是在/dev目录中的一个节点,这种节点都是文件节点,且每个节点代表一个具体的设备。
不过要有主设备号和从设备号两个参数才能创建一个节点。
还可以是在devfs设备文件目录下的一个节点,对于这种节点应根据主设备号给每一种设备都创建一个目录节点,在这个目录下才是代表具体设备的文件节点。
【实验内容】
编写一个简单的字符设备驱动程序。
要求该字符设备包括scull_open()、scull_write()、scull_read()、scull_ioctl()和scull_release()五个基本操作,并编写一个测试程序来测试你所编写的字符设备驱动程序。
【实验指导】
先给出字符设备驱动程序要用到的数据结构定义:
structdevice_struct{
constchar*name;
structfile_operations*chops;
};
staticstructdevice_structchrdevs[MAX_CHRDEV];
typedefstructScull_Dev{
void**data;
intquantum;//thecurrentquantumsize
intqset;//thecurrentarraysize
unsignedlongsize;
unsignedintaccess_key;//usedbysculluidandscullpriv
unsignedintusage;//lockthedevicewhileusingit
structScull_Dev*next;//nextlistitem
}scull;
1字符设备的结构
字符设备的结构即字符设备的开关表。
当字符设备注册到内核后,字符设备的名字和相关操作被添加到device_struct结构类型的chrdevs全局数组中,称chrdevs为字符设备的开关表。
下面以一个简单的例子说明字符设备驱动程序中字符设备结构的定义:
(假设设备名为scull)
****file_operation结构定义如下,即定义chr设备的_fops****
staticintscull_open(structinode*inode,structfile*filp);
staticintscull_release(structinode*inode,structfile*filp);
staticssize_tscull_write(structinode*inode,structfile*filp,constchar*buffer,intcount);
staticssize_tscull_read(structinode*inode,structfile*filp,char*buffer,intcount);
staticintscull_ioctl(structinode*inode,structfile*filp,unsignedlongintcmd,unsignedlongarg);
structfile_operationchr_fops={
NULL,//seek
scull_read,//read
scull_write,//write
NULL,//readdir
NULL,//poll
scull_ioctl,//ioctl
NULL,//mmap
scull_open,//open
NULL,//flush
scull_release,//release
NULL,//fsync
NULL,//fasync
NULL,//checkmediachange
NULL,//revalidate
NULL//lock
};
2字符设备驱动程序入口点
字符设备驱动程序入口点主要包括初始化字符设备、字符设备的I/O调用和中断。
在引导系统时,每个设备驱动程序通过其内部的初始化函数init()对其控制的设备及其自身初始化。
字符设备初始化函数为chr_dev_init(),包含在/linux/drivers/char/mem.c中,它的主要功能之一是在内核中登记设备驱动程序。
具体调用是通过register_chrdev()函数。
register_chrdev()函数定义如下:
#include
#include
intregister_chrdev(unsignedintmajor,constchar*name,structfile_operation*fops);
其中major是为设备驱动程序向系统申请的主设备号。
如果为0,则系统为此驱动程序动态地分配一个主设备号。
name是设备名。
fops是前面定义的file_operation结构的指针。
在登记成功的情况下,如果指定了major,则register_chrdev()函数返回值为0;如果major值为0,则返回内核分配的主设备号。
并且register_chrdev()函数操作成功,设备名就会出现在/proc/devices文件里;在登记失败的情况下,register_chrdev()函数返回值为负。
初始化部分一般还负责给设备驱动程序申请系统资源,包括内存、中断、时钟、I/O端口等,这些资源也可以在open()子程序或别的地方申请。
在这些资源不用的时候,应该释放它们,以利于资源的共享。
用于字符设备的I/O调用主要有:
open()、release()、read()、write()和ioctl()。
open()函数的使用比较简单,当一个设备被进程打开时,open()函数被唤醒:
staticintscull_open(structinode*inode,structfile*filp){
…………
MOD_INC_USE_COUNT;
return0;
}
注意宏MOD_INC_USE_COUNT的使用:
Linux内核需要跟踪系统中每个模块的使用信息,以确保设备的安全使用。
MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT可以检查使用驱动程序的用户数,以保护模块被意外地卸载。
release()函数的使用和open()函数相似:
staticintscull_release(structinode*inode,structfile*filp){
…………
MOD_DEC_USE_COUNT;
return0;
}
当设备文件执行read()调用时,将从设备中读取数据,实际上是从内核数据
队列中读取,并传送给用户空间。
设备驱动程序的write()函数的使用和read()函数相似,只不过是数据传送的方向发生了变化,即按要求的字节数count从用户空间的缓冲区buf复制到硬件或内核的缓冲区中。
有时需要获取或改变正在运行的设备的参数,这时就需要用到ioctl()函数:
staticintscull_ioctl(structinode*inode,structfile*filp,unsignedlongintcmd,unsignedlongarg);
其中参数cmd是驱动程序要执行的命令的特殊代码;参数arg是任何类型的4
字节数,它为特定的cmd提供参数。
在Linux中,内核中的每个设备都有唯一的基本号(basenumber)及和基本号相关的命令范围。
具体的ioctl基本号可参见Documentation/ioctl-number。
Linux中定义了四种ioctl()函数调用:
_IO(base,command)//可以定义所需要的命令,没有数据传送的问题,返回正数
_IOR(base,command,size)//读操作的ioctl控制
_IOW(base,command,size)//写操作的ioctl控制
_IOWR(base,command,size)//读写操作的ioctl控制
当用到的硬件设备能产生中断信号时,需要中断服务子程序。
下面给出几个入口函数流程图的参考设计。
2.1函数scull_open()
2.2函数scull_write()
2.3函数scull_read()
2.4函数scull_ioctl()
2.5函数scull_release()
3字符设备驱动程序的安装
编写完设备驱动程序后,下一项任务是对它进行编译和装入可引导的内核。
对字符驱动程序,可以用下面的步骤来完成:
(2)在chr_dev_init()函数的最后增加调用init_module()子程序的行(chr_dev_init()函数在drivers/char/mem.c中)。
(3)编辑drivers/char目录中的makefile,将driver.o的名称放在OBJS定义的后面,并将driver.c名称放在SRCS定义的后面。
(4)将目录改变到Linux源程序目录的最上层,重新建立和安装内核。
作为一般性预防措施,当改变内核的代码时,应当将计算机上重要的内容作一次备份。
(5)如果用lilo引导系统,最好将新内核作为试验项,在lilo.conf文件中另加一个Linux引导段。
4测试函数
在该字符设备驱动程序编译加载后,再在/dev目录下创建字符设备文件chrdev,使用命令:
#mknod/dev/chrdevcmajorminor,其中“c”表示chrdev是字符设备,“major”是chrdev的主设备号。
(该字符设备驱动程序编译加载后,可在/proc/devices文件中获得主设备号,或者使用命令:
#cat/proc/devices|awk”\\$2==\”chrdev\”{print\\$1}”获得主设备号)。
该测试函数的流程图可如下设计:
第二部分块设备驱动程序的代码
1了解什么是块设备
2块设备驱动程序描述符
块设备驱动程序描述符是一个blk_dev_struct类型的数据结构,器定义如下:
structblk_dev_struct{
//queue_prochastobeatomic
request_queue_trequest_queue;
queue_proc*queue;
void*date;
};
在这个结构中,请求队列request_queue是主体。
对于函数指针queue,当其为非0时,就调用这个函数来找到具体设备的请求队列,这是为考虑具有同一主设备号的多种同类设备而设的一个域,该指针也在初始化时就设置好。
还有一个指针data是辅助queue函数找到特定设备的请求队列。
所有块设备的描述符都存放在blk_dev表中:
structblk_dev_structblk_dev[MAX_BLKDEV];每个块设备都对应着数组中的一项,可以用主设备号进行检索。
每当用户进程对一个块设备发出一个读写请求时,首先调用块设备所公用的函数generic_file_read()和generic_file_write()。
如果数据存在在缓冲区中或缓冲区还可以存放数据,就同缓冲区进行数据交换。
否则,系统会将相应的请求队列结构添加到其对应项的blk_dev_struct中,如下图所示:
如果在加入请求队列结构时该设备没有请求,则马上响应该请求,否则将其追加到请求任务队列尾顺序执行。
3了解块设备的基本入口点
①读写函数;②request()函数(处理请求函数);③ioctl函数;
④check_media_change函数;⑤revalidate函数。
4块设备的注册
和字符设备驱动程序类似,内核里的块设备驱动程序也是由一个主设备号来标识。
不过,对于块设备驱动程序的注册不仅在其初始化的时候进行而且在编译的时候也要进行注册。
在初始化时通过register_blkdev()函数将相应的块设备添加到数组blkdevs[]中,该数组在fs/block_dev.c中定义如下:
staticstruct{
constchar*name;
structblock_device_operations*bdops;
}blkdevs[MAX_BLKDEV];
对块设备驱动程序注册的调用格式为:
intregister_blkdev(unsignedintmajor,constchar*name,structblock_device_operations*bdops);
其中第一个参数是主设备号;第二个参数是设备名;第三个参数是指向具体块设备操作的指针。
如果一切顺利则返回0,否则返回负值。
如果指定的主设备号为0,则该函数会搜索空闲的主设备号分配给该块设备驱动程序并将其作为返回值。
块设备驱动程序的注册示意图:
【实验内容】
编写一个简单的块设备驱动程序。
要求该块设备包括sbull_open()、sbull_ioctl()和sbull_release()等基本操作。
【实验指导】
由于块设备驱动程序的绝大部分都是与设备无关的,故内核的开发者通过把大部分相同的代码放在一个头文件
从而每个块设备驱动程序都必须包含这个头文件。
先给出块设备驱动程序要用到的数据结构定义:
structdevice_struct{
constchar*name;
structfile_operations*chops;
};
staticstructdevice_structblkdevs[MAX_BLKDEV];
structsbull_dev{
void**data;
intquantum;//thecurrentquantumsize
intqset;//thecurrentarraysize
unsignedlongsize;
unsignedintaccess_key;//usedbysbulluidandsbullpriv
unsignedintusage;//lockthedevicewhileusingit
unsignedintnew_msg;
structsbull_dev*next;//nextlistitem
};
externstructsbull_dev*sbull;//deviceinformation
1块设备的结构
块设备的结构即块设备的开关表。
当块设备注册到内核后,块设备的名字和相关操作被添加到device_struct结构类型的blkdevs全局数组中,称blkdevs为块设备的开关表。
下面以一个简单的例子说明块设备驱动程序中块设备结构的定义:
(假设设备名为sbull)
****file_operation结构定义如下,即定义sbull设备的_fops****
structfile_operationblk_fops={
NULL,//seek
block_read,//内核函数
block_write,//内核函数
NULL,//readdir
NULL,//p
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第六章 设备管理实验 第六 设备管理 实验