一步一步走进块驱动之第八章

第八章

本教程修改自赵磊的网上的一系列教程.本人觉得该系列教程写的非常不错.以风趣幽默的语言将块驱动写的非常详细,对于入门教程,应该属于一份经典了. 本人在这对此系列教程最后附上对Linux 2.6.36版本的代码.并编译运行成功. 该教程所有版权仍归作者赵磊所有,本人只做附录代码的添加,并为对原文修改.有不懂的地方,可以联系我 97164811@qq.com 或者给我留言  

+---------------------------------------------------+

|                 写一个块设备驱动                  

+---------------------------------------------------+

作者:赵磊                                        

| email: zhaoleidd@hotmail.com                      

+---------------------------------------------------+

文章版权归原作者所有。                            

大家可以自由转载这篇文章,但原版权信息必须保留。  

如需用于商业用途,请务必与原作者联系,若因未取得  

授权而收起的版权争议,由侵权者自行负责。          

+---------------------------------------------------+


本章的目的是让读者继续休息,因此决定仍然搞一些简单的东西。
比如:给我们的驱动程序模块加上模块参数,这样在加载模块时,可以通过参数设定块设备的大小。


给我们模块加参数的工作不难,这牵涉到1个宏:
module_param_named(name, value, type, perm)
        name是参数的名称
        value是参数在模块中对应的变量
        type是参数的类型
        perm是参数的权限
如,在模块中添加
int disk_size = 1024;
module_param_named(size, disk_size, int, S_IRUGO);
可以给模块加上名称为"size"的参数,如果在加载模块是使用insmod thismodule size=100,那么在模块代码中disk_size的值就是100。
相反,如果加载模块时没有指定参数,那么模块代码中disk_size的值仍是默认的1024。
S_IRUGO指定了这个参数的值在模块加载以后可以被所有人通过/sys/module/[module_name]/parameters/看到,但无法修改。
好了,有关module_param_named就介绍到这里,细节可以google或者看linux/include/linux/moduleparam.h。


然后我们就要给这个模块加个参数,用来在加载时指定块设备的大小。
参数的名字都已经想好了,就叫size吧,类型嘛,32位无符号整数最大能设定到4G,而我们的野心看起来可能更大一些,
为了让这个模块支持4G以上的虚拟磁盘(当然是内存足够的情况下),我们打算使用64位无符号整型。这样能够设定的最大值为16777216T,应该够了吧。


然后我们试图找出module_param_named的参数中与unsigned long long对应的type来。
结果是:google了,没找到;看linux/include/linux/moduleparam.h了,还是没找到。
结论是:目前的linux(2.6.28)还不支持unsigned long long类型的模块参数。
更新一些的内核中会不会有是将来的事,尽快搞定这一章的功能却是现在面临的问题。


然后我们就开始找解决方案:
1:给内核打个补丁,看样子不错,但至少今天之类完成不了我们的程序了
   并且这样一来,我们的程序只能在今后的内核中运行,而失去对旧版linux的兼容性。
2:指定设置磁盘大小的单位为M。这样可设置的最大的数字就成了4G*1M,也就是4096T。
   这个主意看似不错。而且看样子10年内机器的内存应该到不了这个容量。
3:用字符串来指定大小
   这倒是可以解决所有问题,并且我们可以支持16M、1G之类的设定,让我们的程序看起来比较花哨。
   缺点应该是我们需要在程序中自己去解析传入的字符串了,幸运的是,实际的解析代码比想象的容易一些。
因此,我们采用第3个方案,向模块中添加一个名称为size、类型为字符串的参数,并且支持解析以K,M,G,T为单位的设定。


第1步:
  向程序中添加以下参数申明。
  static char *simp_blkdev_param_size = "16M";
  module_param_named(size, simp_blkdev_param_size, charp, S_IRUGO);
  char *simp_blkdev_param_size用于存储设定的磁盘大小,我们把磁盘大小的默认值指定为16M。
  目前我们不允许用户在模块加载后改变磁盘大小,将来嘛,有可能增加这一功能,看起来很眩。
第2步:
  原来的程序使用
  #define SIMP_BLKDEV_BYTES      (16*1024*1024)
  定义磁盘大小,而现在我们不需要这一行了。
  同时,我们需要一个unsigned long long变量来存储用户设定的磁盘大小,因此我们增加这个变量:
  static unsigned long long simp_blkdev_bytes;
  然后把程序中所有使用SIMP_BLKDEV_BYTES的位置换成使用simp_blkdev_bytes变量。
第3步:
  在模块加载时对模块参数进行解析,设置simp_blkdev_bytes变量的值。
  我们增加一个函数进行解析工作:

int getparam(void)
{
        char unit;
        char tailc;


        if (sscanf(simp_blkdev_param_size, "%llu%c%c", &simp_blkdev_bytes,
                &unit, &tailc) != 2) {
                return -EINVAL;
        }


        if (!simp_blkdev_bytes)
                return -EINVAL;


        switch (unit) {
        case 'g':
        case 'G':
                simp_blkdev_bytes <<= 30;
                break;
        case 'm':
        case 'M':
                simp_blkdev_bytes <<= 20;
                break;
        case 'k':
        case 'K':
                simp_blkdev_bytes <<= 10;
                break;
        case 'b':
        case 'B':
                break;
        default:
                return -EINVAL;
        }


        /* make simp_blkdev_bytes fits sector's size */
        simp_blkdev_bytes = (simp_blkdev_bytes + (1<<9) - 1) & ~((1ULL<<9) - 1);


        return 0;
}
然后在simp_blkdev_init()中调用这个函数:
ret = getparam();
if (IS_ERR_VALUE(ret))
        goto err_getparam;
当然,err_getparam的位置读者应该能猜出来了。


这样一来,工作大概就完成了,让我们看看结果:
使用默认值:
# insmod simp_blkdev.ko
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.


Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)


Command (m for help): p


Disk /dev/simp_blkdev: 16 MB, 16777216 bytes
1 heads, 32 sectors/track, 1024 cylinders
Units = cylinders of 32 * 512 = 16384 bytes


           Device Boot      Start         End      Blocks   Id  System


Command (m for help): q


#
设定成20M:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=20M
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.




The number of cylinders for this disk is set to 1280.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)


Command (m for help): p


Disk /dev/simp_blkdev: 20 MB, 20971520 bytes
1 heads, 32 sectors/track, 1280 cylinders
Units = cylinders of 32 * 512 = 16384 bytes


           Device Boot      Start         End      Blocks   Id  System


Command (m for help): q


#
变态一下,还是设定成20M,但用k作单位:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=20480k
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.




The number of cylinders for this disk is set to 1280.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)


Command (m for help): p


Disk /dev/simp_blkdev: 20 MB, 20971520 bytes
1 heads, 32 sectors/track, 1280 cylinders
Units = cylinders of 32 * 512 = 16384 bytes


           Device Boot      Start         End      Blocks   Id  System


Command (m for help): q


#


看样子结果不错。
这一章中基本上没有提到什么比较晦涩的知识,而且看样子通过这一章的学习,大家也应该休息好了。
如果读者现在感觉到精神百倍,那么这一章的目的应该就达到了。


<未完,待续>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/genhd.h>		//add_disk
#include <linux/blkdev.h>		//struct block_device_operations
#include <linux/hdreg.h>

#define _DEBUG_

#define BLK_PAGE_ORDER			2
#define	BLK_PAGE_SIZE		(PAGE_SIZE << BLK_PAGE_ORDER)
#define BLK_PAGE_SHIFT			(PAGE_SHIFT + BLK_PAGE_ORDER)
#define BLK_PAGE_MASK			(~(BLK_PAGE_SIZE - 1))

#define BLK_DISK_NAME 			"block_name"
#define BLKDEV_DEVICEMAJOR      	COMPAQ_SMART2_MAJOR
#define BLKDEV_BYTES        		(16*1024*1024)
#define MAX_PARTITIONS			64

static int MAJOR_NR = 0;
static struct gendisk *g_blkdev_disk;
static struct request_queue *g_blkdev_queue;
struct radix_tree_root blk_dev_data;
static unsigned long long g_blk_size = 1;
static char*	defaule_size = "16M";

int getsize(void)
{
	char flag;
	char tail = 'N';

#ifdef _DEBUG_
	printk(KERN_WARNING "parameter = %s\n",defaule_size);
#endif		
	
	if(sscanf(defaule_size,"%llu%c%c",&g_blk_size,&flag,&tail) != 2){
		return -EINVAL;
	}
	
	if(!g_blk_size)
		return -EINVAL;
	
	switch(flag){
	case 'g':
	case 'G':
		g_blk_size <<= 30;
		break;
	case 'm':
	case 'M':
		g_blk_size <<= 20;
		break;
	case 'k':
	case 'K':
		g_blk_size <<= 10;
		break;
	}
	
	g_blk_size = (g_blk_size + (1<<9) - 1) & ~((1ULL<<9) - 1);
	//此处为字节对齐.(1<<9 - 1) = 255 = 0xFF
	
#ifdef _DEBUG_
	printk(KERN_WARNING "size = %llu tail = %c\n",g_blk_size,tail);
#endif			
	return 0;
}


static int blkdev_make_request(struct request_queue *q, struct bio *bio)
{
	struct bio_vec *bvec;
	int i;
	unsigned long long disk_offset = 0;

	if ((bio->bi_sector << 9) + bio->bi_size > g_blk_size) {
		printk(KERN_ERR BLK_DISK_NAME
				": bad request: block=%llu, count=%u\n",
				(unsigned long long)bio->bi_sector, bio->bi_size);
		bio_endio(bio, -EIO);
		return 0;
	}

	disk_offset = bio->bi_sector << 9;

	bio_for_each_segment(bvec, bio, i) {
		unsigned int count_current = 0;
		unsigned int count_done = 0;
		void *iovec_mem;
		void *dsk_mem = NULL;
		iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
		count_done = 0;
		while(count_done < bvec->bv_len){
			count_current = min(bvec->bv_len - count_done,
						(unsigned int)(BLK_PAGE_SIZE - ((disk_offset + count_done) & ~BLK_PAGE_MASK)));
			//bvec->bv_len - count_done指的是余下需要传送的数据总量,
			//PAGE_SIZE -(dsk_offset + count_done) % PAGE_SIZE指的是从当前块设备偏移开始、不超越页边界时所能传送的数据的最大值。
			//如果bvec->bv_len - count_done > PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE,
			//说明这一次将传送从当前块设备偏移到其所在内存页的页尾之间的数据,
			//余下的数据位于后续的页面中,将在接下来的循环中搞定,
			//如果bvec->bv_len - count_done <= PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE,
			//那么可喜可贺,这将是当前bio_vec的最后一次传送,完成后就可以回家洗澡了。
			dsk_mem = radix_tree_lookup(&blk_dev_data,(disk_offset + count_done) >> BLK_PAGE_SHIFT);
			//(disk_offset + count_done) >> PAGE_SHIFT 如果在传送的数据是跨页的情况下,
			//那么这个disk_offset + count_done 正好为页对齐地址,也就是下一面的地址.
			//当前要传送的数据的后半段数据,放到了下一个页的的前面,正好完成了数据的后半段搬移
			if(!dsk_mem){
				printk(KERN_ERR BLK_DISK_NAME
						": search memory failed: %llu\n",
                        (disk_offset + count_done) >> BLK_PAGE_SHIFT);
                kunmap(bvec->bv_page);
				bio_endio(bio,-EIO);
				return 0;
			}
			dsk_mem += (disk_offset + count_done) & ~BLK_PAGE_MASK;
			switch(bio_rw(bio)){
			case READ:
			case READA:
				memcpy(iovec_mem + count_done,dsk_mem,count_current);
				break;
			case WRITE:
				memcpy(dsk_mem,iovec_mem + count_done,count_current);
				break;
			}
			count_done += count_current;
			//当前完成的字节数,这次必须加上,一方面是根据此值来获得min中计算数据,另一方面,那就是hang住啦~~while的循环条件的原因
		}
		kunmap(bvec->bv_page);
		disk_offset += bvec->bv_len;
	}		
	
	bio_endio(bio, 0);

	return 0;
}

int gendisk_getgeo(struct block_device *pblk_dev, struct hd_geometry *phd_geo)
{
	/*
	 * capacity        	heads       sectors   	cylinders
	 * 0~16M        	1        	1        	0~32768
	 * 16M~512M  		1        	32        	1024~32768
	 * 512M~16G  		32        	32        	1024~32768
	 * 16G~...       	255        	63        	2088~...
	 */
	if (g_blk_size < 16 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 1;
	} else if (g_blk_size < 512 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 32;
	} else if (g_blk_size < 16ULL * 1024 * 1024 * 1024) {
		phd_geo->heads = 32;
		phd_geo->sectors = 32;
	} else {
		phd_geo->heads = 255;
		phd_geo->sectors = 63;
	}

	phd_geo->cylinders = g_blk_size >> 9 / phd_geo->heads / phd_geo->sectors;
	
	return 0;
}

struct block_device_operations fop = {
	.owner = THIS_MODULE,
	.getgeo = gendisk_getgeo,
};

void delete_diskmem(void)
{
	int i = 0;
	void *p = NULL;
	for(i = 0; i < (g_blk_size + BLK_PAGE_SIZE - 1) / BLK_PAGE_SIZE; i++){
		p = radix_tree_lookup(&blk_dev_data,i);
		radix_tree_delete(&blk_dev_data,i);
		free_pages((unsigned long)p,BLK_PAGE_ORDER);
	}
}

int alloc_diskmem(void)
{
	int ret = 0;
	int i = 0;
	void *p = NULL;
	//初始化基树,基树一般用于内存页管理
	INIT_RADIX_TREE(&blk_dev_data,GFP_KERNEL);
	//(g_blk_size + PAGE_SIZE - 1) / PAGE_SIZE 防止页面不对齐的情况下,增加一页.这样剩余的数据也能用.不然不对齐超过部分不能使用了
	for(i = 0; i < (g_blk_size + BLK_PAGE_SIZE - 1) / BLK_PAGE_SIZE; i++){
		p = (void*)__get_free_pages(GFP_KERNEL,BLK_PAGE_ORDER);
		if(NULL == p){
			ret = -ENOMEM;
			goto err_get_page;
		}
		ret = radix_tree_insert(&blk_dev_data,i,p);
		if(IS_ERR_VALUE(ret)){
			goto err_insert;
		}
	}
#ifdef _DEBUG_
	printk(KERN_WARNING "page size = %d\n",i);
#endif	
	return 0;

err_insert:
	free_pages((unsigned long)p,BLK_PAGE_ORDER);	//插入失败,先释放该页面
err_get_page:
	delete_diskmem();
	return ret;
}

static int __init initialization_function(void)
{
	int ret = 0;
	
	getsize();
	
	MAJOR_NR = register_blkdev(0, BLK_DISK_NAME);
	if(MAJOR_NR < 0)
	{		
		return -1;
	}
	
	g_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
	if(NULL == g_blkdev_queue){
		ret = -ENOMEM;		
		goto err_alloc_queue;
	}
	
	blk_queue_make_request(g_blkdev_queue, blkdev_make_request);
	
	g_blkdev_disk = alloc_disk(MAX_PARTITIONS);
	if(NULL == g_blkdev_disk){
		ret = -ENOMEM;		
		goto err_alloc_disk;
	}
	
	//申请页面空间
	ret = alloc_diskmem();
	if(IS_ERR_VALUE(ret)){
		goto err_alloc_disk;
	}
	
	strcpy(g_blkdev_disk->disk_name,BLK_DISK_NAME);
	g_blkdev_disk->major = MAJOR_NR;
	g_blkdev_disk->first_minor = 0;
	g_blkdev_disk->fops = &fop;
	g_blkdev_disk->queue = g_blkdev_queue;
	
	set_capacity(g_blkdev_disk, g_blk_size>>9);
	
	add_disk(g_blkdev_disk);
#ifdef _DEBUG_
	printk(KERN_WARNING "ok\n");
#endif
	return ret;
	
err_alloc_disk:
	blk_cleanup_queue(g_blkdev_queue);
err_alloc_queue:
	unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
	return ret;
}

static void __exit cleanup_function(void)
{
	del_gendisk(g_blkdev_disk);					//->add_disk
	delete_diskmem();							//->alloc_diskmem
	put_disk(g_blkdev_disk);					//->alloc_disk
	blk_cleanup_queue(g_blkdev_queue);				//->blk_init_queue
	unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
}

//注册模块加载卸载函数
module_init(initialization_function);				//指定模块加载函数
module_exit(cleanup_function);					//指定模块卸载函数

module_param_named(size, defaule_size, charp, S_IRUGO);

//模块信息及许可证
MODULE_AUTHOR("LvApp");							//作者
MODULE_LICENSE("Dual BSD/GPL");					//许可证
MODULE_DESCRIPTION("A simple block module");			//描述
MODULE_ALIAS("block");					    //别名


本人是在参考教程之后修改的教程内容.如有不同.可能有遗漏没有修改.造成对读者的迷惑,在此致歉~~ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值