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

第四章

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

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

|                 写一个块设备驱动                  

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

| 作者:赵磊                                        

| email: [email protected]                      

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

| 文章版权归原作者所有。                            

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

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

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

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

上一章结束时说过,本章会准备一些不需要动脑子的内容,现在我们开始履行诺言。

看上去简单的事情实际上往往会被弄得很复杂,比如取消公仆们的招待费用问题;

看上去复杂的事情真正做起来也可能很简单,比如本章中要让我们的块设备支持分区操作。

谈到分区,不懂电脑的人想到了去找“专家”帮忙;电脑入门者想到了“高手”这个名词;

渐入佳境者想到了fdisk;资深级玩家想到了dm;红点玩家想到了隐藏的系统恢复区;

程序员想到了分区表;病毒制造者想到了把分区表清空......

作为块设备驱动程序的设计者,我们似乎需要想的比他们更多一些,

我们大概需要在驱动程序开始识别块设备时访问设备上的分区表,读出里面的数据进行分析,

找出这个块设备中包含哪一类的分区(奇怪吧,但真相是分区表确实有很多种,只是我们经常遇到的大概只有ibm类型罢了)、

几个分区,每个分区在块设备上的区域等信息,再在驱动程序中对每个分区进行注册、创建其管理信息......

读到这里,正在系鞋带准备溜之大吉的同学们请稍等片刻听我说完,

虽然实际上作者也鼓励同学们多作尝试,甚至是这种无谓的尝试,但本章中的做法却比上述的内容简单得多。

因为这一回linux居然帮了我们的忙,并且不是I/O调度器的那种倒忙。

打开linux代码,我们会在fs/partitions/目录中发现一些文件,这些友好的文件将会默默无闻地帮我们的大忙。

而我们需要做的居然如此简单,还记得alloc_disk()函数吗?

我们一直用1作参数来调用它的,但现在,我们换成64,这意味着设定块设备最大支持63个分区。

然后......不要问然后,因为已经做完了。

当然,如果要让代码看起来漂亮一些的话,我们可以考虑用一个宏来定义最大分区数。

也就是,在文件的头部增加:

/* usable partitions is SIMP_BLKDEV_MAXPARTITIONS - 1 */

#define SIMP_BLKDEV_MAXPARTITIONS      (64)

然后把

simp_blkdev_disk = alloc_disk(1);

改成

simp_blkdev_disk = alloc_disk(SIMP_BLKDEV_MAXPARTITIONS);

好了,真的改好了。

上一章那样改得太多看起来会让读者不爽,那么这里改得太少,是不是也同样不爽?

大概有关部门深信老百姓接受不了有害物质含量过少的食品,因此制定了食品中三聚氰胺含量的标准。

于是,今后我们大概会制定出一系列标准,比如插入多深才能叫强奸什么的。

为了达到所谓的标准,我们破例补充介绍一下alloc_disk()函数:

这个函数的原型为:

struct gendisk *alloc_disk(int minors);

用于申请一个gendisk结构,并做好一些初始化工作。

minors用于指定这个设备使用的次设备号数量,因为第一个次设备号已经用于表示整个块设备了,

因此余下的minors-1个设备号用于表示块设备中的分区,这就限制了这个块设备中的最大可访问分区数。

我们注意“最大可访问分区数”这个词:

“最大”虽然指的是上限,但并不意味这是唯一的上限。

极端情况下如果这个块设备只有2个磁道,那么无论minors多大,块设备本身充其量也只能建立2个分区。

这时再谈minors值能到达多少简直就是扯淡,就像腐败不根除,建多少经济适用房都是白搭一样。

“可访问”指的是通过驱动程序可以访问的分区数量,这是因为我们只有那么多次设备号。

但这个数字并不妨碍用户在块设备上面建多少个区。比如我们把minors设定为4,那么最大可访问的分区数量是3,

足够变态的用户完全可以在块设备上建立几十个分区,只不过结果是只能使用前3个分区而已。

现在我们可以试试这个程序了。

与以往相同的是,我们编译和加载这个模块:

# make

make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step04 modules

make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'

  CC [M]  /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.o

  Building modules, stage 2.

  MODPOST

  CC      /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.mod.o

  LD [M]  /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.ko

make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'

# 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): 

关于fdisk我们不打算在这里介绍,因为我们试图让这篇文档看起来专家一些。

使用n命令创建第一个主分区:

Command (m for help): n

Command action

   e   extended

   p   primary partition (1-4)

p

Partition number (1-4): 1

First cylinder (1-2, default 1): 1

Last cylinder or +size or +sizeM or +sizeK (1-2, default 2): 1

Command (m for help):

如果细心一些的话,在这里可以看出一个小麻烦,就是:这块磁盘一共只有2个磁道。

因此,我们只好指定第一个分区仅占用1个磁道。毕竟,还要为第2个分区留一些空间。

然后建立第二个分区:

Command (m for help): n

Command action

   e   extended

   p   primary partition (1-4)

p

Partition number (1-4): 2

First cylinder (2-2, default 2): 2

Command (m for help):

这一步中由于只剩下1个磁道,fdisk便不再问我们Last cylinder,而是自作主张地把最后一个磁道分配给新的分区。

这时我们的分区情况是:

Command (m for help): p

Disk /dev/simp_blkdev: 16 MB, 16777216 bytes

255 heads, 63 sectors/track, 2 cylinders

Units = cylinders of 16065 * 512 = 8225280 bytes

           Device Boot      Start         End      Blocks   Id  System

/dev/simp_blkdev1               1           1        8001   83  Linux

/dev/simp_blkdev2               2           2        8032+  83  Linux

Command (m for help):

写入分区,退出fdisk:

Command (m for help): w

The partition table has been altered!

Calling ioctl() to re-read partition table.

Syncing disks.

#

然后我们在这两个分区中创建文件系统

# mkfs.ext3 /dev/simp_blkdev1

mke2fs 1.39 (29-May-2006)

Filesystem label=

OS type: Linux

Block size=1024 (log=0)

Fragment size=1024 (log=0)

2000 inodes, 8000 blocks

400 blocks (5.00%) reserved for the super user

First data block=1

Maximum filesystem blocks=8388608

1 block group

8192 blocks per group, 8192 fragments per group

2000 inodes per group

Writing inode tables: done

Creating journal (1024 blocks): done

Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 27 mounts or

180 days, whichever comes first.  Use tune2fs -c or -i to override.

# mkfs.ext3 /dev/simp_blkdev2

mke2fs 1.39 (29-May-2006)

Filesystem label=

OS type: Linux

Block size=1024 (log=0)

Fragment size=1024 (log=0)

2008 inodes, 8032 blocks

401 blocks (4.99%) reserved for the super user

First data block=1

Maximum filesystem blocks=8388608

1 block group

8192 blocks per group, 8192 fragments per group

2008 inodes per group

Writing inode tables: done

Creating journal (1024 blocks): done

Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 23 mounts or

180 days, whichever comes first.  Use tune2fs -c or -i to override.

#

然后mount设两个设备:

# mount /dev/simp_blkdev1 /mnt/temp1

# mount /dev/simp_blkdev2 /mnt/temp2

#

看看结果:

# mount

/dev/hda1 on / type ext3 (rw)

proc on /proc type proc (rw)

sysfs on /sys type sysfs (rw)

devpts on /dev/pts type devpts (rw,gid=5,mode=620)

tmpfs on /dev/shm type tmpfs (rw)

none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)

/dev/simp_blkdev1 on /mnt/temp1 type ext3 (rw)

/dev/simp_blkdev2 on /mnt/temp2 type ext3 (rw)

#

然后读/写:

# cp /etc/init.d/* /mnt/temp1/

# cp /etc/passwd /mnt/temp2

# ls /mnt/temp1/

NetworkManager            avahi-dnsconfd      dund       ipmi        lost+found     netfs     portmap          rpcsvcgssd      vncserver

NetworkManagerDispatcher  bluetooth           firstboot  iptables    lvm2-monitor   netplugd  psacct           saslauthd       winbind

acpid                     capi                functions  irda        mcstrans       network   rdisc            sendmail        wpa_supplicant

anacron      conman  

# ls /mnt/temp2

lost+found  passwd

#

收尾工作:

# umount /dev/temp1

# umount /dev/temp2

# rmmod simp_blkdev

#

看起来本章应该结束了,但为了耽误大家更多的时间,我们来回忆一下刚才出现的小麻烦。

我们发现这块磁盘只有2个磁道,由于分区是以磁道为边界的,因此最大只能创建2个分区。

不过谢天谢地,好歹我们能够证明我们的程序是支持“多个”分区的......尽管只有2个。

那么为什么系统会认为我们的块设备只有2个磁道呢?其实这不怪系统,因为我们根本没有告诉系统我们的磁盘究竟有多少个磁道。

因此系统只好去猜、猜、猜,结果就猜成2个磁道了。

好吧,说的细节一些,传统的磁盘使用8个位表示盘面数、6个位表示每磁道扇区数、10个位表示磁道数,因此盘面、每磁道扇区、磁道的最大数值分别为255、63和1023。

这也是传说中启动操作系统时的1024柱面(磁道)和硬盘容量8G限制的根源。

现代磁盘采用线性寻址方式突破了这一限制,从本质上说,如果你的机器还没生锈,那么你的硬盘无论是内部结构还是访问方式都与常识中的盘面、每磁道扇区、磁道无关。

但为了与原先的理解兼容,对于现代磁盘,我们在访问时还是假设它具有传统的结构。目前比较通用的假设是:所有磁盘具有最大数目的(也就是恒定的)盘面和每磁道扇区数,而磁盘大小与磁道数与成正比。

因此,对于一块80G的硬盘,根据假设,这块磁盘的盘面和每磁道扇区数肯定是255和63,磁道数为:80*1024*1024*1024/512(字节每扇区)/255(盘面数)/63(每磁道扇区数)=10043(小数部分看作不完整的磁道被丢弃)。

话归原题,在驱动程序中我们指定了磁盘大小为16M,共包含16*1024*1024/512=32768个扇区。假设这块磁盘具有最大盘面和每磁道扇区数后,它的磁道数就是:32768/255/63=2。

我们看起开应该很happy,因为系统太看得起我们了,竟然把我们的块设备看成现代磁盘进行磁道数的换算处理。

不过我们也可能unhappy,因为这造成磁盘最大只能被分成2个区。(至于为什么分区以磁道作为边界,可以想象一下磁盘的结构)

但我们的磁盘只有区区16M啊,所以最好还是告诉系统我们的磁盘没有那么多的盘面数和每磁道扇区数,这将让磁道数来得多一些。

在下一章中,我们打算搞定这个问题。

<未完,待续>

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

#define _DEBUG_

#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;
unsigned char blkdev_data[BLKDEV_BYTES];

static int blkdev_make_request(struct request_queue *q, struct bio *bio)
{
	struct bio_vec *bvec;
	int i;
	void *dsk_mem;

	if ((bio->bi_sector << 9) + bio->bi_size > BLKDEV_BYTES) {
		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;
	}

	dsk_mem = blkdev_data + (bio->bi_sector << 9);

	bio_for_each_segment(bvec, bio, i) {
		void *iovec_mem;

		switch (bio_rw(bio)) {
		case READ:
		case READA:
				iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
				memcpy(iovec_mem, dsk_mem, bvec->bv_len);
				kunmap(bvec->bv_page);
				break;
		case WRITE:
				iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
				memcpy(dsk_mem, iovec_mem, bvec->bv_len);
				kunmap(bvec->bv_page);
				break;
		default:
				printk(KERN_ERR BLK_DISK_NAME
						": unknown value of bio_rw: %lu\n",
						bio_rw(bio));
				bio_endio(bio, -EIO);
				return 0;
		}
		dsk_mem += bvec->bv_len;
	}

	bio_endio(bio, 0);

	return 0;
}
/*
struct hd_geometry {
      unsigned char heads;		//磁头数255		//有几个磁头,也叫盘面数
      unsigned char sectors;		//扇区数64		//一个柱面有几个扇区
      unsigned short cylinders;	//柱面			//有多少个磁道的数量
      unsigned long start;
};
*/
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 (BLKDEV_BYTES < 16 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 1;
	} else if (BLKDEV_BYTES < 512 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 32;
	} else if (BLKDEV_BYTES < 16ULL * 1024 * 1024 * 1024) {
		phd_geo->heads = 32;
		phd_geo->sectors = 32;
	} else {
		phd_geo->heads = 255;
		phd_geo->sectors = 63;
	}

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

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

static int __init initialization_function(void)
{
	int ret = 0;
	
	printk(KERN_WARNING "register_blkdev\n");
	MAJOR_NR = register_blkdev(0, BLK_DISK_NAME);
	if(MAJOR_NR < 0)
	{		
		return -1;
	}
	
	printk(KERN_WARNING "blk_alloc_queue\n");
	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);
	
	printk(KERN_WARNING "alloc_disk\n");
//	g_blkdev_disk = alloc_disk(1);
	g_blkdev_disk = alloc_disk(MAX_PARTITIONS);
	if(NULL == g_blkdev_disk){
		ret = -ENOMEM;		
		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, BLKDEV_BYTES>>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
	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_AUTHOR("LvApp");								//作者
MODULE_LICENSE("Dual BSD/GPL");						//许可证
MODULE_DESCRIPTION("A simple block module");				//描述
MODULE_ALIAS("block");								//别名



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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章