邢台那个驾校b2多少钱:linux_设备驱动程序
来源:百度文库 编辑:九乡新闻网 时间:2024/07/14 08:49:42
第六章 设备管理实验
【实验目的】
通过本实验的学习,了解Linux操作系统中的设备驱动程序包括哪些组成部分,并能编写简单的字符设备(scull,Simple Character Utility for LoadingLocalities)和块设备(sbull,SimpleBlock Utility for Loading Localities)的驱动程序以及对所编写设备驱动程序的测试,最终了解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
int register_chrdev(unsigned int major, constchar *name, struct file_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()五个基本操作,并编写一个测试程序来测试你所编写的字符设备驱动程序。
【实验指导】
先给出字符设备驱动程序要用到的数据结构定义:
struct device_struct{
const char *name;
struct file_operations *chops;
};
static struct device_structchrdevs[MAX_CHRDEV];
typedef struct Scull_Dev {
void **data;
int quantum; // the current quantum size
int qset; // the current array size
unsigned long size;
unsigned int access_key; // used by sculluid and scullpriv
unsigned int usage; // lock the device while usingit
structScull_Dev *next; // next listitem
} scull;
1 字符设备的结构
字符设备的结构即字符设备的开关表。当字符设备注册到内核后,字符设备的名字和相关操作被添加到device_struct结构类型的chrdevs全局数组中,称chrdevs为字符设备的开关表。下面以一个简单的例子说明字符设备驱动程序中字符设备结构的定义:(假设设备名为scull)
**** file_operation结构定义如下,即定义chr设备的_fops ****
static intscull_open(struct inode *inode,struct file *filp);
static int scull_release(struct inode *inode,struct file *filp);
static ssize_t scull_write(struct inode *inode,struct file *filp,const char *buffer,int count);
staticssize_t scull_read(struct inode *inode,struct file *filp,char *buffer,int count);
static intscull_ioctl(struct inode *inode,struct file *filp,unsigned long int cmd,unsigned long arg);
struct file_operation chr_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, // check media change
NULL, // revalidate
NULL // lock
};
2 字符设备驱动程序入口点
字符设备驱动程序入口点主要包括初始化字符设备、字符设备的I/O调用和中断。在引导系统时,每个设备驱动程序通过其内部的初始化函数init()对其控制的设备及其自身初始化。字符设备初始化函数为chr_dev_init(),包含在/linux/drivers/char/mem.c中,它的主要功能之一是在内核中登记设备驱动程序。具体调用是通过register_chrdev()函数。register_chrdev()函数定义如下:
#include
#include
int register_chrdev(unsigned intmajor,const char *name,struct file_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()函数被唤醒:
static int scull_open(struct inode*inode,struct file *filp){
…… ……
MOD_INC_USE_COUNT;
return 0;
}
注意宏MOD_INC_USE_COUNT的使用: Linux内核需要跟踪系统中每个模块的使用信息,以确保设备的安全使用。MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT可以检查使用驱动程序的用户数,以保护模块被意外地卸载。
release()函数的使用和open()函数相似:
static int scull_release(structinode *inode,struct file*filp) {
…… ……
MOD_DEC_USE_COUNT;
return 0;
}
当设备文件执行read()调用时,将从设备中读取数据,实际上是从内核数据
队列中读取,并传送给用户空间。设备驱动程序的write()函数的使用和read()函数相似,只不过是数据传送的方向发生了变化,即按要求的字节数count从用户空间的缓冲区buf复制到硬件或内核的缓冲区中。
有时需要获取或改变正在运行的设备的参数,这时就需要用到ioctl()函数:
static int scull_ioctl(struct inode*inode,struct file *filp,unsigned long int cmd,unsigned long arg);
其中参数cmd是驱动程序要执行的命令的特殊代码;参数arg是任何类型的4
字节数,它为特定的cmd提供参数。在Linux中,内核中的每个设备都有唯一的基本号(base number)及和基本号相关的命令范围。具体的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.h自定义头文件和scull.c文件复制到包含字符设备驱动程序源代码的目录drivers/char子目录中。
(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/chrdev c major minor ,其中“c”表示chrdev是字符设备,“major”是chrdev的主设备号。(该字符设备驱动程序编译加载后,可在/proc/devices文件中获得主设备号,或者使用命令: #cat /proc/devices | awk ”\\$2==”chrdev\”{ print\\$1}” 获得主设备号)。
该测试函数的流程图可如下设计:
第二部分 块设备驱动程序的代码
1 了解什么是块设备
2 块设备驱动程序描述符
块设备驱动程序描述符是一个blk_dev_struct类型的数据结构,器定义如下:
struct blk_dev_struct {
//queue_proc has to be atomic
request_queue_t request_queue ;
queue_proc *queue ;
void *date ;
};
在这个结构中,请求队列request_queue是主体。对于函数指针queue ,当其为非0时,就调用这个函数来找到具体设备的请求队列,这是为考虑具有同一主设备号的多种同类设备而设的一个域,该指针也在初始化时就设置好。还有一个指针data是辅助queue函数找到特定设备的请求队列。
所有块设备的描述符都存放在blk_dev表中:struct blk_dev_struct blk_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中定义如下:
static struct {
const char *name;
struct block_device_operations *bdops;
} blkdevs[MAX_BLKDEV];
对块设备驱动程序注册的调用格式为:
int register_blkdev(unsigned int major,const char *name,struct block_device_operations *bdops);
其中第一个参数是主设备号;第二个参数是设备名;第三个参数是指向具体块设备操作的指针。如果一切顺利则返回0,否则返回负值。如果指定的主设备号为0,则该函数会搜索空闲的主设备号分配给该块设备驱动程序并将其作为返回值。
块设备驱动程序的注册示意图:
【实验内容】
编写一个简单的块设备驱动程序。要求该块设备包括sbull_open()、sbull_ioctl()和sbull_release()等基本操作。
【实验指导】
由于块设备驱动程序的绝大部分都是与设备无关的,故内核的开发者通过把大部分相同的代码放在一个头文件
struct device_struct {
const char *name;
struct file_operations *chops;
};
staticstruct device_struct blkdevs[MAX_BLKDEV];
struct sbull_dev {
void **data;
int quantum; // the current quantum size
int qset; // the current array size
unsigned long size;
unsigned int access_key; // used by sbulluid andsbullpriv
unsigned int usage; // lock the device while using it
unsigned int new_msg;
struct sbull_dev *next; // next listitem
};
extern struct sbull_dev *sbull; // device information
1 块设备的结构
块设备的结构即块设备的开关表。当块设备注册到内核后,块设备的名字和相关操作被添加到device_struct结构类型的blkdevs全局数组中,称blkdevs为块设备的开关表。下面以一个简单的例子说明块设备驱动程序中块设备结构的定义:(假设设备名为sbull)
**** file_operation结构定义如下,即定义sbull设备的_fops ****
struct file_operation blk_fops ={
NULL, // seek
block_read, // 内核函数
block_write, // 内核函数
NULL, // readdir
NULL, // poll
sbull_ioctl, // ioctl
NULL, // mmap
sbull_open, // open
NULL, // flush
sbull_release, // release
block_fsync, // 内核函数
NULL, // fasync
sbull_check_media_change, // check media change
NULL, // revalidate
NULL // lock
};
块设备的fops是通过缓冲区和用户程序进行数据交换。从上面结构中可以看
出,所有的块驱动程序都调用内核block_read(),block_write(),block_fsync()函数,所以在块设备驱动程序入口中不包含这些函数,只需包括ioctl(), open()和release()函数。
2 块设备驱动程序入口点
块设备驱动程序入口点主要包括初始化块设备、块设备的I/O调用和中断。块设备的I/O调用ioctl()、open()、release()与字符设备类似。
块设备与字符设备最大的不同在于设备的读写操作。块设备使用通用block_read()和block_write()函数来进行数据读写。这两个通用函数向请求表中增加读写请求,这样内核可以对请求顺序安排优先级(通过ll_rw_block())。由于是对内存缓冲区而不是对设备进行操作,因而它们能加快读写请求。如果内存中没有要读入的数据或者需要将写请求写入设备,那么就需要真正地执行数据传输。这是通过数据结构blk_dev_struct中的request_fn来完成的(见include/linux /blkdev.h)。
struct blk_dev_struct {
void (*request_fn)(void);
struct request *current_request;
struct request plug;
struct tq_struct plug_tq;
};
struct request {
…… ……
kdev_t rq_dev;
intcmd; // 读或写
int errors;
unsignedlong sector;
char*buffer;
structrequest *next;
…… ……
};
对于具体的块设备,函数指针request_fn当然是不同的。块设备的读写操作都是由request()函数完成。所有的读写请求都存储在request结构的链表中。request()函数利用CURRENT宏检查当前的请求:
#define CURRENT(blk_dev[MAJOR_NR].current_request)
接下来看一看sbull_request的具体使用:
void sbull_request(void) {
unsigned long offset,total;
Begin:
INIT_REQUEST:
offset = CURRENT -> sector * sbull_hard;
total = CURRENT -> current_nr_sectors * sbull_hard;
// access beyond end of the device
if(total + offset > sbull_size * 1024) {
// error in request
end_request(0);
goto Begin;
}
if(CURRENT -> cmd == READ) {
memcpy(CURRENT -> buffer,sbull_storage + offset,total);
}
else if(CURRENT -> cmd == WRITE) {
memcpy(sbull_storage + offset,CURRENT -> buffer,total);
}
else {
end_request(0);
}
//successful
end_request(1);
// let INIT_REQUEST return when we are done
goto Begin;
}
request()函数从INIT_REQUEST宏命令开始(它也在blk.h中定义),它对请求队列进行检查,保证请求队列中至少有一个请求在等待处理。如果没有请求(即CURRENT = 0),INIT_REQUEST宏命令将使request()函数返回,任务结束。
假定队列中至少有一个请求,request()函数现在应处理队列中的第一个请求,当处理完请求后,request()函数将调用end_request()函数。如果成功地完成了读写操作,应该用参数值1调用end_request()函数;如果读写操作不成功,以参数值0调用end_request()函数。如果队列中还有其他请求,将CURRENT指针设为指向下一个请求。执行end_request()函数后,request()函数回到循环的起点,对下一个请求重复上面的处理过程。
块设备的初始化过程要比字符设备复杂,它既需要像字符设备一样在引导内核时完成一定的工作,还需要在内核编译时增加一些内容。块设备驱动程序初始化时,由驱动程序的init()完成。为了引导内核时调用init(),需要在blk_dev_init()函数中增加一行代码: sbull_init();。
块设备驱动程序初始化的工作主要包括:
(1)检查硬件是否存在;
(2)登记主设备号;
(3)将fops结构的指针传递给内核;
(4)利用register_blkdev()函数对设备进行注册:
if(register_blkdev(sbull_MAJOR,“sbull”,&sbull_fops)){
printk(“Registeringblock device major:%d failed\n”,sbull_MAJOR);
return –EIO;
};
(5)将request()函数的地址传递给内核:
blk_dev[sbull_MAJOR].request_fn = DEVICE_REQUEST;
(6)将块设备驱动程序的数据容量传递给缓冲区:
#define sbull_HARDS_SIZE 512
#define sbull_BLOCK_SIZE 1024
static int sbull_hard = sbull_HARDS_SIZE;
static int sbull_soft = sbull_BLOCK_SIZE;
hardsect_size[sbull_MAJOR] = &sbull_hard;
blksize_size[sbull_MAJOR] = &sbull_soft;
在块设备驱动程序内核编译时,应把下列宏加到blk.h文件中:
#define MAJOR_NR sbull_MAJOR
#define DEVICE_NAME “sbull”
#define DEVICE_REQUEST sbull_request
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
2.1 函数sbull_open()
2.2 函数sbull_ioctl()
2.3 函数sbull_release()
3 相关问题
(1)睡眠与唤醒
在Linux中,当设备驱动程序向设备发出读写请求后,就进入睡眠状态。
void sleep_on(struct wait_queue **ptr);
void interruptible_sleep_on(struct wait_queue**ptr);
在设备完成请求后需要通知CPU时,会向CPU发出一个中断请求,然后CPU根据中断请求决定调用相应的设备驱动程序。
void wake_up(struct wait_queue **ptr);
void wake_up_interruptible(struct wait_queue**ptr);
(2)缓冲区的使用
块设备驱动程序直接与缓冲区打交道,因而需要用到有关缓冲区的一些操作。例如,函数getblk()用于分配缓冲区,breles()用于释放缓冲区等:
struct buffer_head *getblk(kdev_t,int block,intsize);
void breles(struct buffer_head *buf);。
【附录:参考程序代码】
第一部分 字符设备驱动程序
1.1 函数scull_open()
int scull_open(struct inode *inode,struct file *filp) {
MOD_INC_USE_COUNT; // 增加该模块的用户数目
printk(“This chrdev is in open\n”);
return 0;
}
1.2 函数scull_write()
int scull_write(struct inode *inode,struct file *filp,const char*buffer,int count) {
if(count < 0)
return –EINVAL;
if(scull.usage || scull.new_msg)
return –EBUSY;
scull.usage = 1;
kfree(scull.data);
data = kmalloc(sizeof(char)*(count+1),GFP_KERNEL);
if(!scull.data) {
return –ENOMEM;
}
copy_from_user(scull.data,buffer,count + 1);
scull.usage = 0;
scull.new_msg = 1;
return count;
}
1.3 函数scull_read()
int scull_read(struct inode *inode,struct file *filp,char *buffer,int count) {
intlength;
if(count < 0)
return –EINVAL;
if(scull.usage)
return –EBUSY;
scull.usage = 1;
if(scull.data == 0)
return 0;
length = strlen(scull.data);
if(length < count)
count = length;
copy_to_user(buf,scull.data,count+ 1);
scull.new_msg = 0;
scull.usage = 0;
return count;
}
1.4 函数scull_ioctl()
#include
#define SCULL_MAJOR 0
#define SCULL_MAGIC SCULL_MAJOR
#define SCULL_RESET _IO(SCULL_MAGIC,0) // resetthe data
#define SCULL_QUERY_NEW_MSG _IO(SCULL_MAGIC,1) // check for newmessage
#define SCULL_QUERY_MSG_LENGTH _IO(SCULL_MAGIC,2) //get messagelength
#define IOC_NEW_MSG 1
static int usage,new_msg; // control flags
static char*data;
int scull_ioctl(struct inode *inode,struct file *filp,unsigned long int cmd,unsigned long arg) {
intret=0;
switch(cmd) {
case SCULL_RESET:
kfree(data);
data = NULL;
usage = 0;
new_msg = 0;
break;
case SCULL_QUERY_NEW_MSG:
if(new_msg)
return IOC_NEW_MSG;
break;
case SCULL_QUERY_MSG_LENGTH:
if(data == NULL){
return 0;
}
else {
return strlen(data);
}
break;
default:
return –ENOTTY;
}
return ret;
}
1.5 函数scull_release()
voidscull_release(struct inode *inode,struct file *filp) {
MOD_DEC_USE_COUNT; // 该模块的用户数目减1
printk(“This chrdev is inrelease\n”);
return0;
#ifdef DEBUG
printk(“scull_release(%p,%p)\n”,inode,filp);
#endif
}
1.6 测试函数
在该字符设备驱动程序编译加载后,再在/dev目录下创建字符设备文件chrdev,使用命令: #mknod /dev/chrdev c major minor ,其中“c”表示chrdev是字符设备,“major”是chrdev的主设备号。(该字符设备驱动程序编译加载后,可在/proc/devices文件中获得主设备号,或者使用命令: #cat /proc/devices | awk ”\\$2==”chrdev\”{ print\\$1}” 获得主设备号)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include “chardev.h” // 见后面定义
void write_proc(void);
void read_proc(void);
main(int argc,char **argv) {
if(argc == 1) {
puts(“syntax: testprog[write|read]\n”);
exit(0);
}
if(!strcmp(argv[1],“write”)) {
write_porc();
}
elseif(!strcmp(argv[1],“read”)) {
read_proc();
}
else {
puts(“testprog:invalid command!\n”);
}
return 0;
}
void write_proc() {
int fd,len,quit = 0;
char buf[100];
fd = open(“/dev/chrdev”,O_WRONLY);
if(fd <= 0) {
printf(“Erroropening device for writing!\n”);
exit(1);
}
while(!quit) {
printf(“\n Please writeinto:”);
gets(buf);
if(!strcmp(buf,“exit”))
quit = 1;
while(ioctl(fd,DYNCHAR_QUERY_NEW_MSG))
usleep(100);
len = write(fd,buf,strlen(buf));
if(len < 0) {
printf(“Error writing to device!\n”);
close(fd);
exit(1);
}
printf(“\n There are %d bytes written todevice!\n”,len);
}
close(fd);
}
void read_proc(){
intfd,len,quit = 0;
char*buf = NULL;
fd=open(“/dev/chrdev”,O_RDONLY);
if(fd< 0) {
printf(“Error openingdevice for reading!\n”);
exit(1);
}
while(!quit){
printf(“\n Please readout:”);
while(!ioctl(fd,DYNCHAR_QUERY_NEW_MSG))
usleep(100);
// get the msg length
len = ioctl(fd,DYNCHAR_QUERY_MSG_LENGTH,NULL);
if(len) {
if(buf != NULL)
free(buf);
buf = malloc(sizeof(char)*(len+1));
len = read(fd,buf,len);
if(len < 0) {
printf(“Error reading from device!\n”);
}
else {
if(!strcmp(buf,“exit”) {
ioctl(fd,DYNCHAR_RESET); // reset
quit = 1;
}
else
printf(“%s\n”,buf);
}
}
}
free(buf);
close(fd);
}
// 以下为chrdev.h定义
#ifndef _DYNCHAR_DEVICE_H
#define _DYNCHAR_DEVICE_H
#include
#define DYNCHAR_MAJOR 42
#define DYNCHAR_MAGIC DYNCHAR_MAJOR
#define DYNCHAR_RESET_IO(DYNCHAR_MAGIC,0) // reset the data
#define DYNCHAR_QUERY_NEW_MSG _IO(DYNCHAR_MAGIC,1)// check for new message
#define DYNCHAR_QUERY_MSG_LENGTH_IO(DYNCHAR_MAGIC,2) // get message length
#define IOC_NEW_MSG 1
#endif
第二部分 块设备驱动程序
保存设备信息的数据结构:
typedef struct Sbull_Dev{
void * *data;
int quantum; // the current quantum size
int qset; // thecurrent array size
unsigned long size;
unsigned int new_msg;
unsigned int usage; // lock thedevice while using it
unsigned int access_key; // used by sbulluid and sbullpriv
struct Sbull_Dev *next; // next listitem
};
externstruct sbull_dev *sbull; // deviceinformation
2.1 函数sbull_open()
intsbull_open(struct inode *inode,struct file *filp) {
int num = MINOR(inode -> i_rdev);
if(num >= sbull -> size)
return –ENODEV;
sbull -> size = sbull -> size + num;
if(!sbull -> usage) {
check_disk_change(inode -> i_rdev);
if(!*(sbull-> data))
return –ENOMEM;
}
sbull -> usage++;
MOD_INC_USE_COUNT;
return 0;
}
2.2 函数sbull_ioctl()
#include
#include
int sbull_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) {
int err;
struct hd_geometry *geo = (struct hd_geometry*)arg;
PDEBUG(“ioctl 0x%x 0x%lx\n”,cmd,arg);
switch(cmd) {
case BLKGETSIZE:
// Return the device size,expressed insectors
if(!arg)
return –EINVAL; // NULL pointer:not valid
err = verify_area(VERIFY_WRITE,(long *)arg,sizeof(long));
if(err)
return err;
put_user(1024*sbull_sizes[MINOR(inode -> i_rdev)
/sbull_hardsects[MINOR(inode-> i_rdev)],
(long *)arg);
return 0;
case BLKFLSBUF: //flush
if(!suser())
return –EACCES; // only root
fsync_dev(inode -> i_rdev);
return 0;
case BLKRRPART: // re-readpartition table: can’t do it
return –EINVAL;
RO_IOCTLS(inode -> i_rdev,arg);
// the default ROoperations,宏RO_IOCTLS(kdev_t dev,
unsigned long where)在blk.h中定义
}
return –EINVAL; //unknown command
}
2.3 函数sbull_release()
void sbull_release(struct inode *inode,struct file *filp) {
sbull -> size = sbull -> size+ MINOR(inode -> i_rdev);
sbull-> usage--;
MOD_DEC_USE_COUNT;
printk("Thisblkdev is in release!\n");
return0;
#ifdef DEBUG
printk("sbull_release(%p,%p)\n",inode,filp);
#endif
}
2.4 函数sbull_request()
extern structrequest *CURRENT;
void sbull_request(void) {
while(1) {
INIT_REQUEST();
printk(“request %p: cmd %i sec %li (nr.%li),next %p\n”,
CURRENT,
CURRENT -> cmd,
CURRENT -> sector,
CURRENT -> current_nr_sectors);
end_request(1); // 请求成功
}
}