块设备原理分析--创建一个内存模拟磁盘的驱动实例

本文简要介绍了磁盘的基本构成;并从一个内存(ram)模拟一个块设备(disk)的简单驱动(ramdisk),来了解块设备驱动原理;最后如何对该块设备的简单使用。



一、磁盘基本构成

了解块设备原理,需要先了解磁盘的基本结构,如下为一个盘面,磁盘最小单位为扇区,在内核里通常默认512B,每个盘面有一道道环形,为磁道。
在这里插入图片描述

多个盘面一起构成一块磁盘,如下每个盘面上相同的磁道组成一个柱面,所以柱面数=当个盘面的磁道数,同时每个盘面对应一个磁头操作读写。
在这里插入图片描述
这样就能计算出磁盘总容量: 磁头(盘面)数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数(512)

块设备驱动与字符驱动不同在于,其数据不是立马写入设备,而是有个request_queue缓存了数据,对读和写进行分类,当积累了一定数量的写/读IO才统一去操作磁盘写入/读出,这样目的是提高磁盘读写效率,而不是每来一个读或者写IO,就做一次磁盘操作。

下面从一个ram(内存)模拟一个块设备的简单驱动,来了解块设备驱动原理。


二、ramdisk源码基本架构

先上源码,源码基于3.10.0-123编写。如下是最简易架构的ramdisk源码。创建一个4MB的内存作为ram盘。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/major.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/highmem.h>
#include <linux/mutex.h>
#include <linux/radix-tree.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/sizes.h>
#include <linux/kernel.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/blkpg.h>

#define DEVICE_NAME "ramdisk_test"
#define ram_size  SZ_4M
static DEFINE_SPINLOCK(ramdisk_lock);
struct gendisk *ramdisk;
static struct request_queue *disk_queue;
void *ram_addr;


static int ramdisk_getgeo(struct block_device *blk_dev, struct hd_geometry *geo)	
{
	geo->cylinders = 32; //磁道数、柱面数
	geo->heads = 2; //磁盘面数
	geo->sectors = ram_size/2/32/512;  //单个磁道上的扇区数量  
	printk(KERN_INFO "capacity %d\n", get_capacity(blk_dev->bd_disk)); //get_capacity(blk_dev->bd_disk)=8292
	return 0;

}
static struct kobject *ramdisk_probe(dev_t dev, int *part, void *data)
{

	printk(KERN_INFO "%s called\n", __FUNCTION__);
	*part = 0;
	return get_disk(ramdisk);
}

/*
列队(disk_queue)中的request处理函数

*/

static void ramdisk_request(struct request_queue *q)
{
	struct request *req;
	printk(KERN_INFO "%s called\n", __FUNCTION__);

	req = blk_fetch_request(q);                        //获取列队(disk_queue)里的request
	while (req) {
		unsigned long offset = blk_rq_pos(req) *512;  //扇区起始地址(即偏移)
		unsigned long len  = blk_rq_cur_bytes(req);  //request长度
		int err = 0;

		printk(KERN_INFO "%s offset:%d len %d\n", __FUNCTION__, offset, len);	
		if((u32)(offset+len) < ram_size)
		{
				if (rq_data_dir(req) == READ)  //列队读请求
				{
					printk(KERN_INFO "%s read len %d\n", __FUNCTION__, len);
					memcpy(req->buffer, (char *)(ram_addr + offset), len);  //列队读(request中的buffer拷贝出来,写入ramdisk的扇区为offset开始的地方)
					
				}
				else
				{
					printk(KERN_INFO "%s write len %d\n", __FUNCTION__, len);
					memcpy((char *)(ram_addr + offset), req->buffer, len);  //列队写
					
				}
		}
		else
		{
			printk(KERN_INFO "fail %s offset:%d len %d\n", __FUNCTION__, offset, len);	
			
		}
	 
		
	done:
		if (!__blk_end_request_cur(req, err))  //end_request 结束
			req = blk_fetch_request(q);
	}
}

static const struct block_device_operations disk_fops =
{
	.owner		= THIS_MODULE,
	.getgeo     =  ramdisk_getgeo,
	//.open		= ramdisk_open,
	//.release	= ramdisk_release,
};

int major;
static int __init ramdisk_init(void)
{
    int ret;
	printk(KERN_INFO "%s called\n", __FUNCTION__);

    major = register_blkdev(0, DEVICE_NAME);   //注册一个块设备,获取设备号

    disk_queue = blk_init_queue(ramdisk_request, &ramdisk_lock); //初始化列队 request_queue,列队处理函数为ramdisk_request
	if (!disk_queue)
		goto out_queue;

	ramdisk = alloc_disk(1);  //分配一个gendisk(次设备号个数=分区数=1)
	ramdisk->queue = disk_queue; //为gendisk赋值,绑定
    ramdisk->major = major;
    ramdisk->first_minor = 0;  //该值0---次设备号个数,都是该块设备 
    ramdisk->fops = &disk_fops;
    sprintf(ramdisk->disk_name, DEVICE_NAME);
	
	set_capacity(ramdisk, ram_size/512); //设置块设备容量(单位:扇区)
    add_disk(ramdisk);     //添加gendisk
	
    blk_register_region(MKDEV(major, 0), 1, THIS_MODULE,   //注册设备号[0-1)
				ramdisk_probe, NULL, NULL);


	ram_addr = kzalloc(ram_size, GFP_KERNEL);

	if(!ram_addr)
	{
		kfree(ram_addr);
		goto out_queue;

	}
	
	printk(KERN_INFO "%s end\n", __FUNCTION__);
    return 0;
	

out_queue:
    put_disk(ramdisk);
out_disk:
    unregister_blkdev(major, DEVICE_NAME);
err:
    return -1;
}


static void __exit ramdisk_exit(void)
{
	
	blk_unregister_region(MKDEV(major, 0), 1);
	unregister_blkdev(major, DEVICE_NAME);
	put_disk(ramdisk);
   //del_gendisk(ramdisk);
    blk_cleanup_queue(disk_queue);

	kfree(ram_addr);
	return;
}


module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");

2.1 源码分析:

一个块设备的框架主要实现流程如下:

  1. 注册块设备,获取设备号 register_blkdev
  2. 申请设置列队(主要是绑定列队处理函数) blk_init_queue
  3. 分配gendisk结构体并赋值(包括绑定列队,设备号,fop等) alloc_disk
  4. 设置盘容量(扇区个数) set_capacity
  5. 添加创建磁盘 add_disk

如果要创建一个4MB内存块作为该块设备,则需要:

  1. ram_addr = kzalloc(ram_size, GFP_KERNEL); //申请4MB内存
  2. 然后编写列队处理函数ramdisk_request,是实现数据读写的核心。

ramdisk_request处理函数实现流程:

  1. 内核根据电梯调度算法,获取request_queue列队中的quest。
  2. 获取quest的扇区所在地址(即偏移地址),request的长度
  3. 判断对列队是读还是写,对磁盘(ram)做相应的memcpy 操作(req->buffer与ram之间的拷贝)。

如上,可以看出request_queue列队实现读写的关键,该处理函数里,内核根据电梯调度算法,获取到request的IO,然后做批量读或批量写。

需要注意的是:函数里需要对request长度作下限制访问:if((u32)(offset+len) < ram_size),否则长度可能会超过4M,造成内存越界。
在这里插入图片描述
从调试的底层打印可以观察到,往块设备挂载路径下读写文件数据,只有少量的读写话并不会立马执行列队处理函数(除非执行了sync才会被立马执行),这个跟块设备的原理有关,这些少量数据会被先存到buffer,当块设备ramdisk_request时机成熟时,由电梯调度算法把request取出。


三、ramdisk的使用

  1. 加载驱动后,可以看看到块设备被创建(/dev/ramdisk_test
[root@localhost mnt]# ls /dev/ramdisk_test  -l
brw-rw----. 1 root disk 252, 0 Feb  2 05:47 /dev/ramdisk_test
  1. 给磁盘格式化文件系统后,会调用到ramdisk_getgeo几何函数,获取容量分配磁盘信息。包括:磁盘面数(geo->heads ),磁道/柱面数(geo->cylinders),单磁道的扇区数量(geo->sectors)。

[root@localhost ramdisk]# mkfs.ext4 /dev/ramdisk_test  #格式化为ext4
mke2fs 1.42.9 (28-Dec-2013)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
1024 inodes, 4096 blocks
204 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=4194304
1 block group
8192 blocks per group, 8192 fragments per group
1024 inodes per group

Allocating group tables: done
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

[root@localhost ramdisk]# mkfs.fat /dev/ramdisk_test  #或格式化为fat
mkfs.fat 3.0.20 (12 Jun 2013)

注:如果没有加入ramdisk_getgeo函数,在格式时候有可能会报错(提示获取不到几何信息),报错如下:


[root@localhost ramdisk]# mkfs.fat /dev/ramdisk_test mkfs.fat 3.0.20
(12 Jun 2013) unable to get drive geometry, using default 255/63 ```
  1. fdisk可以查看ramdisk的容量,看出整个盘就一个分区,容量4MB。
[root@localhost mnt]# fdisk -l /dev/ramdisk_test

Disk /dev/ramdisk_test: 4 MB, 4194304 bytes, 8192 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x00000000

            Device Boot      Start         End      Blocks   Id  System

  1. 挂载并查看分区,后面就可以在/mnt/下存取文件了。
[root@localhost /]# mount /dev/ramdisk_test /mnt/

[root@localhost /]# mount |grep ramdisk
/dev/ramdisk_test on /mnt type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
[root@localhost /]#

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值