当前位置:文档之家› 虚拟块设备驱动程序设计与分析

虚拟块设备驱动程序设计与分析

如果只是为了应付考试,这个文档就太啰嗦了,不用看,不过还是可以帮助记忆,考试只会考其中加粗字体的几个函数中的一个,至于是哪个我不能断定,因此要记的还是比较多的,要是能理解就更好了,结合课本和下面的解释应该能大体上弄明白这个虚拟块设备驱动的实现过程,毕竟设备驱动是内核的一部分,光看下面的解释也是还是很头晕的,不过坚持看下去还是有收获的,我也差不多花了半天时间,不过,要是打算……的话就可以直接跳过了。

#define MAJOR_NR 70 //我们创造的虚拟块设备的主设备号#define DEVICE_NAME “bdemo”//我们创造的虚拟块设备的名字,当设备加载成功后可用lsmod命令查看到该设备模块名#define blkdemo_devs 2 //虚拟块设备的个数#define blkdemo_rahead 2 //读取块设备时预读的扇区个数#define blkdemo_size 4 //每个虚拟块设备的大小,单位为KB#define blkdemo_blksize 1024 //设备每个数据块的大小,即block,单位为字节#define blkdemo_hardsect 512 //设备每个扇区的大小,单位为字节struct blkdemo_device { // 这里定义了我们将要创造的虚拟块设备的数据结构int size; // 用来记录真实块设备的容量,即下面data指针所指向数据存储区的大小int use_cnt; // 用来记录正在使用该块设备的程序的个数int hardsect; // 用来保存该块设备每个扇区的大小,单位为字节,即设备的使用计数u8 *data; // 该指针所指向的内存区域就是该块设备真正用来存储数据的区域,在该设备还未被加载函数初始化时,该指针为// 空,即系统还没有为该设备分配内存区域。

};static int blkdemo_sizes[blkdemo_devs]; //用来保存我们创建的所有虚拟块设备的大小,单位为KBstatic int blkdemo_blksizes[blkdemo_devs]; //用来保存我们创建的所有虚拟块设备中每个数据块的大小,单位为字节static int blkdemo_hardsects[blkdemo_devs];//用来保存我们创建的所有虚拟块设备中每个扇区的大小,单位为字节//上面的这三个数组将会在我们加载这些设备时被注册到内核的数据结构中(即让内核中与之相关的一些指针指向它们,让内核能够读取我们所创建的设备的一些重要信息//对于一个新的设备,内核肯定不知道他为何物,要想让内核识别我们自己创造的设备,则必须将该设备的一些信息、使用这个设备的方//法等告诉内核,由于内核早已编译成型,至于如何去告诉内核就早已模式化。

内核中有几个指针数组(书本page81)专门用来完成上面的部分任务:// blk_size[];// blksize_size[];// hardsect_size[];// read_ahead[];//这几个数组都为每一个主设备号留有一个位置,对于2.4的内核,主设备号和次设备号均用8位二进制来表示(即短整型的高八位和//低八位),因此这几个数组都包含有256个元素,每个元素都是与主设备号对应的一个指针,如果主设备号所对应的设备不存在,则该//指针置为空(NULL),其实其中很多指针都为空,因为一般电脑上都没有那么多不同类型的块设备,当然,对于我们所创造的这个块设//备而言,它与系统中所存在的其他块设备的类型都不同,要为其确定一个主设备号,这个没什么硬性的规定,只要找一个没被使用的主//设备号就可以了,这个程序中使用的是70(前面的MOJOR_NR宏)。

上面我们定义了保存有虚拟块设备信息的数组,现在只要将他们的//首地址赋给这几个数组中下标70(主设备号)所对应的指针元素即可。

这一过程是在后面的加载函数中完成的。

static int blksize = blkdemo_blksize;struct blkdemo_device blkdemo_dev[blkdemo_devs];//这里才真正创建了我们虚拟块设备对应的结构体变量(一个全局数组),//每个元素为对应一个虚拟块设备虚拟块设备的打开函数(open()):int blkdemo_open(struct inode *inode, strcut file *filp){ //设备文件对应的节点(inode)结构中包含有对应的设备号int num;num = DEVICE_NR(inode->i_rdev);//用DEVICE_NR宏可求出该节点所对应设备的次设备号,所以num即为次设备号if (!blkdemo_dev[num].use_cnt) { //如果该设备的使用计数为0,则说该设备没有被任何程序使用,当虚拟块设备没有被//任何程序使用时,内核先前为该设备所分配的存储区很可能已经被释放掉了,甚至对于可移动设备而言,有可能该设备都被拔掉了(当//然,我们的虚拟块设备是不可能的),因此,在打开该设备时要进行严格的检查,不然会导致设备打开出错而造成系统崩溃。

check_disk_change(inode->i_rdev);//首先检查该块设备是否发生了变化,比如已经被移除了(该设备不可能,所以//此处没有用if来判断,只是形式的调用了一下该函数。

if (!blkdemo_dev[num].data)//然后判断该设备的数据存储区域是否已经被释放掉了return –ENOMEM; //如果是,则返回,告知系统该设备无法打开,-ENOMEM是一个内核中定义的宏,它代表的意思是//“error,no memory”。

}//如果上述情况均未发生,一切正常,则打开设备,对于这个虚拟的块设备,其实没有什么好打开的,不过还是意思一下:blkdemo_dev[num].use_cnt++; //将设备的使用计数加1,表示又多了个程序使用该设备。

MOD_INC_USE_COUNT; //并且将内核所管理的模块使用计数也加1,好让内核也知道多了一个程序使用该虚拟设备模块。

模块使//用计数是内核管理模块时要用的,只有当一个设备的模块使用计数为0时才能卸载该模块,这个值也可以通过lsmod命令查看到return(0);//返回0,表示设备已成功打开}虚拟块设备的释放函数(release()):int blkdemo_release(struct inode *inode, struct file *filp){//释放并不代表将此设备从内核中移除了,他是对调用它的程序而言的,只表示这个程序不再使用该设备了int num;num = DEVICE_NR(inode->i_rdev);//求出设备的次设备号blkdemo_dev[num].use_cnt--;//既然使用该设备的程序少了一个,则应该将该设备的使用计数减1MOD_DEC_USE_COUNT;// 并且将内核所管理的模块使用计数也减1return(0);//返回0,表示设备释放成功}虚拟块设备的请求函数(request()):void blkdemo_request(request_queue_t *q){ //块设备和字符设备在数据的读写是有区别的。

对于块设备,程序对数据读写的请求一般不会立即得到回应,程序首先要提出对数据//的请求,此时内核会动态的分配一个request结构(page74,图7-1中有request结构的抽象描述),并将请求的详细信息记录到//这个request结构中,然后将这个结构按照某些规则插入到该设备的请求队列中,当系统处于较为合适的状态时,内核就会对队列上//的所有请求进行集中处理。

采用这些繁琐的方法都由真实块设备的物理特性决定的,因为大部分块设备都和硬盘类似,读取数据时要进//行寻道等一些复杂的命令操作,而这是一个相当耗时的过程,如果每当有程序请求数据时内核就立即去操作磁盘,那么系统大部分宝贵//的时间都被消耗在了等待磁盘响应上了,因此内核中构建了一套专门操作块设备的方法,来对请求的数据进行集中处理,以提高磁盘的//吞吐量和系统的整体性能,request()函数的任务就是按顺序处理这条请求队列,直到队尾,//除非出现意外错误而返回。

struct request *req;int res = 1; //用来记录对当前请求的处理是否成功,成功则置1,失败则清0,供后面的end_request()函数使用。

int num;int size;u8 *ptr;while (1) {INIT_REQUEST;//测试当前的请求是否有效req = CURRENT;//CURRENT指针由内核中的end_request()函数管理,它指向请求队列中当前要处理的request结构。

num = DEVICE_NR(req->rq_dev);//获取所请求的设备的次设备号ptr = blkdemo_dev[num].data + req->sector * blkdemo_dev[num].hardsect;// 设备数据存储区的首地址 + 请求的首个扇区 * 该设备每个扇区的字节大小,最后,ptr指向所要请求的数据size = req->current_nr_sectors * blkdemo_dev[num].hardsect;// 当前请求的扇区总数 * 该设备每个扇区的字节大小,因此size为当前请求所请求的总字节数if (ptr + size > blkdemo_dev[num].data + blkdemo_dev[num].size) { //判断所请求数据的地址是否超出//了该设备的数据存储区的范围。

超出范围后会导致内存溢出,造成系统崩溃,决不能容忍。

printk(KERN_WARNNING “blkdemo: request past end of device\n”);//向控制台或日志打印出警告信息res = 0; //请求失败,res置0}//如果正常则下面打印出当前请求的详细信息(仅调试时使用,可以不写)printk(“<1> request %p: cmd %i sec %li (nr.%li)\n”, req,//<1>和KERN_ALERT是等价的,这是内核中定req->cmd, req->sector, req->current_nr_sectors);//义的8种日志级别宏之一(page43)switch (cmd) { //判断当前请求要对所请求的数据做何种操作case READ://如果是读,则,memcpy(req->buffer,ptr, size);//把从ptr开始的size字节复制到发出该请求的程序所提供的缓冲区中去res = 1;//完成了请求,res置1break;case WRITE://如果是写,则,memcpy(ptr, req->buffer, size);//把发出该请求的程序的缓冲区中的数据复制到该设备中ptr所指向的内存区res = 1; //完成了请求,res置1break;default: //未知请求res = 0;//无法完成,res置0}end_request(res);//根据请求是否成功来调整CURRENT指针变量的值,为处理请求队列中的下一个请求作准备}}struct block_device_operations blkdemo_bdops = { //初始化虚拟块设备操作函数接口open: blkdemo_open,release: blkdemo_release,};虚拟块设备的加载函数:static int __init blkdemo_init(void)//__init为加载函数标志,用此标志修饰的函数只能在模块被插入内核由内核调用{int i;int ret;ret = devfs_register_blkdev(MAJOR_NR, DEVICE_NAME, &blkdemo_bdops);//注册块设备,该函数成功时返回主设//备号,失败时返回负值,当参数中主设备号MAJOR_NR为0时,自动为设备分配主设备号,非0时使用MAJOR_NR指定的主设备号if (ret < 0) {//如果返回值小于0,说明设备注册失败printk(KERN_WARNNING “devfs_register_blkdev() failed\n”);//打印出警告信息return(ret);//返回错误代号}if (MAJOR_NR == 0)//如果MAJOR_NR为0,则blkdemo_major = ret; //使用系统自动分配的主设备号else //否则blkdemo_major = MAJOR_NR;//直接使用我们指定的主设备号blk_init_queue(BLK_DEFAULT_QUEUE(blkdemo_major), blkdemo_request);//内核为每个主设备号都保留了一个请求//队列,也是通过一个数组实现的,BLK_DEFAULT_QUEUE能返回该默认的请求队列,blk_init_queue()函数通过创建一个请求队列//头将该队列和处理该请求队列的request()关联起来// 下面的就开始将我们所创造的虚拟块设备的信息告诉系统:read_ahead[blkdemo_major] = blkdemo_rahead; //告诉系统该类型块设备的预读扇区数for (i = 0; i < blkdemo_devs; i++)blkdemo_sizes[i] = blkdemo_size;//确定每个块设备的大小,以KB为单位blk_size[blkdemo_major] = blkdemo_sizes;//告诉系统保存有这些块设备的大小的数组的内存首地址for (i = 0; i < blkdemo_devs; i++)blkdemo_blksizes[i] = blkdemo_blksize;//确定每个块设备的每个数据块的大小,以字节为单位blksize_size[blkdemo_major] = blkdemo_blksizes;//告诉系统保存有这些块设备的每个数据块大小的数组的首地址for (i = 0; i < blkdemo_devs; i++)blkdemo_hardsects[i] = blkdemo_hardsect; //确定每个块设备的每个扇区的大小,以字节为单位hardsect_size[blkdemo_major] = blkdemo_hardsects;//告诉系统保存有这些块设备的每个扇区大小的数组的首地址for (i = 0; i < blkdemo_devs; i++)register_disk(NULL, MKDEV(blkdemo_major, i), 1, &blkdemo_bdops,//注册每个块设备分区。

相关主题