专题4参考设备驱动.docx
《专题4参考设备驱动.docx》由会员分享,可在线阅读,更多相关《专题4参考设备驱动.docx(14页珍藏版)》请在冰豆网上搜索。
专题4参考设备驱动
【实验目的】
通过本实验的学习,了解Linux操作系统中的设备驱动程序包括哪些组成部分,并能编写简单的字符设备(scull,SimpleCharacterUtilityforLoadingLocalities)的驱动程序以及对所编写设备驱动程序的测试,最终了解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_operations结构来访问设备驱动程序中的函数。
了解设备驱动程序代码的如下几个部分:
◆驱动程序的注册与注销。
◆设备的打开与释放。
◆设备的读写操作。
◆设备的控制操作。
◆设备的中断和轮询处理。
第一部分字符设备驱动程序的代码
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字符设备驱动程序的安装
编写完设备驱动程序后,下一项任务是将它编译成内核模块并将其加载到内核,可以用下面的步骤来完成:
(1)将scull.c编译成内核模块:
#>gcc–cscull.c–D__KERNEL__-DMODULE-I/usr/src/linux/include
(2)将编译生成的内核模块scull.o加载到内核:
#>insmodscull.o
4测试函数
在该字符设备驱动程序编译加载后,再在/dev目录下创建字符设备文件chrdev,使用命令:
#mknod/dev/chrdevcmajorminor,其中“c”表示chrdev是字符设备,“major”是chrdev的主设备号。
(该字符设备驱动程序编译加载后,可在/proc/devices文件中获得主设备号,或者使用命令:
#cat/proc/devices|awk”\\$2==\”chrdev\”{print\\$1}”获得主设备号)。
该测试函数的流程图可如下设计: