作者:徐老师,华清远见嵌入式学院讲师。
块设备基本概念
系统中能够随机访问固定大小数据片的设备被称之为块设备。这些数据片就称作块。块设备文件一般都是以安装文 件系统的方式使用,这也是块设备通常的访问方式。块设备的方式访问方式是随机的,也就是可以在访问设备时,随意的从一个位置跳转到另外一个位置。块设备的 访问位置必须能够在介质的不同区间前后移动。所以事实上内核不必提供专门的子系统来管理字符设备,但对于块设备的管理就必须要有一个专门的提供服务的子系 统。
块设备与字符设备的区别
块设备与字符设备的区别 |
|
块设备 |
字符设备 |
以固定大小为单位访问 |
以字节为单位访问 |
一般不直接操作设备文件,而是以安装文件系统方式进行访问 |
通常直接访问设备文件 |
支持随机访问 |
只允许顺序访问(流的形式) |
块与扇区
块设备中最小的可寻址单位是扇区,扇区大小一般是2的整数倍。最常见的大小是512字节。扇区的大小是块设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比扇区更小的单位进行寻址和操作。
块是文件系统的一种抽象,只能基于块来访问文件系统。物理磁盘寻址是按照扇区的级别进行的,内核访问的所有磁盘操作又都是按照块进行的。扇区是设备的最小可寻址单位,所以快不能比扇区还小,只能数倍与扇区大小。
内核对块大小的要求是:必须是扇区大小的整数倍,并且小于页面的大小,所以块的大小通常是512字节、1K或者4K。
簇
文件系统是操作系统与驱动器之间的接口,当操作系统请求从磁盘里读取一个文件时,会请求相应的文件系统打开 文件。扇区是磁盘最小的物理存储单元,但由于操作系统无法对 数目众多的扇区进行寻址,所以操作系统就将相邻的扇区组合在一起,形成一个簇,然后再对簇进行管理。每个簇可以包括 2、4、8、16、32 或 64 个扇区。显然,簇是操作系统所使 用的逻辑概念,而非磁盘的物理特性。
为了更好地管理磁盘空间和更高效地从硬盘读取数据,操作系统规定一个簇中只能放置一个文件的内容,因此文件 所占用的空间,只能是簇的整数倍;而如果文件实际大小小于一簇,它也要占一簇的空间。所以,一般情况下文件所占空间要略大于文件的实际大小,只有 在少数情况下,即文件的实际大小恰好是簇的整数倍时,文件的实际大小才会与所占空间完 全一致。
簇是指可分配的用来保存文件的最小磁盘空间,计算机中所有的信息都保存在簇中。簇越小,保存信息的效率就越 高。在 FAT16 文件系统中,每个分区最多有 65525 个簇,簇大小默认值为 32KB;在 FAT32 文件系统中使用的簇比 FAT16 小,默认为 4KB。
柱面和磁头
硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两 面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储 128×2 的 N 次方(N=0.1.2.3)字节信息。在 DOS 中每扇区是 128×2 的 2 次方=512 字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中, 不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁盘有两个面,每个 面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些参数可以得到硬盘的容量,基计算公式为:
存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数
Linux 块设备处理模型
Mapping Layer
Linux内核的文件系统组件,主要是磁盘文件系统,同时也包括块设备文件等。
Generic block layer
隐藏硬件细节,提供block设备的抽象视图。用来完成块设备的相关核心功能。
I/O scheduler layer
I/O调度器层,主要用于对块设备请求队列中的请求进行调度,以最大程度优化硬件操作的性能。(比如I/O调度器可能会对请求队列中的某些请求进行合并或者调整各请求件的顺序,以尽可能减少磁盘磁头移动的距离。)
提高 I/O 调度器的效率也是影响整个系统对块设备上数据管理效率的一个方面。
Block Device Driver
设备驱动程序,完成和硬件的具体交互。
块设备相关数据结构
gendisk结构体
内核使用 gendisk 结构来表示一个独立的磁盘设备,内核还使用 gendisk 结构来表示分区,在此结构中,很多程序必须由驱动程序来进行初始化。
该结构体定义<linux/genhd.h>中。
struct gendisk {
int major;//磁盘的主设备号
int first_minor;//起始次设备号
int minors;//次设备号数量,也称之为分区数量,如果该值为1,表示无法分区。
char disk_name[DISK_NAME_LEN];//设备名称
struct disk_part_tbl *part_tbl;//磁盘的分区表信息
struct hd_struct part0;//第一个分区的分区表信息
const struct block_device_operations *fops;//块设备操作集合
struct request_queue *queue;//磁盘的I/O请求队列
void * private_data;//磁盘或分区私有数据,与file结构体的private_data类似
}
gendisk相关函数:
分配磁盘
struct gendisk 是一个动态分配的结构,它需要特别的内核操作来初始化。驱动不能自己分配这个结构,必须调用:
struct gendisk *alloc_disk(int minors);
其中,minors参数是这个磁盘使用的次编号数量,一般也就是磁盘分区的数量,此后minors不能被修改。
释放磁盘
当不需要一个磁盘时, 它应该被释放,使用如下函数释放gendisk:
void del_gendisk(struct gendisk *gd);
引用计数
gendisk中包含一个kobject成员, 它是一个可被引用计数的结构体。通过get_disk()和put_disk()函数可用来操作引用计数。驱动通常不需要做这个。
struct kobject *get_disk(struct gendisk *disk);
void put_disk(struct gendisk *disk);
注册磁盘
gendisk结构体被分配之后,系统还不能使用这磁盘,需要调用如下函数来注册这个磁盘设备:
void add_disk(struct gendisk *gd);
特别要注意的是,对add_disk()的调用必须在驱动程序的初始化工作完成并能响应磁盘的请求之后。
设置gendisk容量
void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元。
block_device_operations 结构体
块设备也有类似于字符设备操作集合的块设备操作集合用于控制设备的操作,但是由于大多数情况下对块设备的使用时以安装文件系统的方式进行访问的,用户应用程序一般不会直接访问块设备的设备文件。
该结构体描述了磁盘操作的有关接口函数。需要包含<linux/blkdev.h>头文件。
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
与字符设备驱动类似,当设备被打开和关闭时将调用它们
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
ioctl()系统调用的实现,块设备包含大量的标准请求,这些标准请求由 Linux 块设备层 处理,因此大部分块设备驱动的 ioctl()函数相当短。
int (*media_changed) (struct gendisk *gd);
被内核调用来检查是否驱动器中的介质已经改变,如果是,则返回一个非 0 值,否则返 回 0。
这个函数仅适用于支持可移动介质的驱动器,通常需要在驱动中增加一个表示介 质状态是否改变的标志变量,非可移动设备的驱动不需要实现这个方法。 struct gendisk 参数是内核表示一个磁盘的数据结构。
int (*revalidate_disk) (struct gendisk *gd);
被调用来响应一个介质改变,驱动进行必要的工作,使新介质准备好可使用。这个函数 返回一个 int 值, 但是值被内核忽略。
int (*getgeo)(struct block_device *, struct hd_geometry *);
该函数根据驱动器的几何信息填充一个 hd_geometry 结构体,hd_geometry 结构体包含 磁头、扇区、柱面等信息。
struct module *owner;
一个指向拥有这个结构的模块的指针,常常被初始化为 THIS_MODULE。
block_device_operations 结构中没有实际读或写数据的函数,在块 I/O 子系统中,这些操作由请求函数处理。
request 和 bio 结构体
在 Linux 块设备驱动中,使用request结构体来表示等待进行的I/O请求。它是由I/O调度算法将连续的bio 合并成一个request。所以bio 结构体是真正对应上层传递的I/O请求。
struct request {
struct list_head queuelist;//请求链表
struct request_queue *q;//请求所属请求队列
unsigned int cmd_flags;//命令标示,包含I/O请求的数据流向是读还是写。
unsigned int __data_len;//要操作的数据字节数
sector_t __sector;//当前要访问的扇区
struct bio *bio;//request请求中第一个bio请求
struct bio *biotail;//request中最后一个bio请求
char *buffer;//指向缓冲区的指针,数据应该被传送到或者来自这个缓冲区。
};
struct bio {
sector_t bi_sector;//要传输的第一个扇区
struct bio *bi_next;//下一个bio
unsigned long bi_flags;//bi_flags;//状态、命令等
unsigned long bi_rw;//地位表示READ/WRITE,高位表示优先级
unsigned short bi_vcnt;//bio_vec数量
unsigned short bi_idx;//当前bcl_vec索引
unsigned int bi_size;//当前bio总共操作的数据大小
struct bio_vec *bi_io_vec;//实际的vec列表
};
struct bio_vec {
struct page *bv_page;//要操作的页指针
unsigned int bv_len;//传输的字节数
unsigned int bv_offset;//偏移位置
};
request、bio、bio_vec的关系如下:
他们的关系有点错综复杂,需要慢慢理解。
request/bio相关函数
初始化请求队列
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinclock_t *lock);
该函数第一个参数是请求处理函数的指针,第二个参数是控制访问队列权限的自旋锁,该函数内部会分配一个reque_queue 结构体大小的内存,成功返回创建并初始化好的请求队列首地址,失败返回NULL。该函数一般在块设备驱动的加载函数中调用。
清除请求队列
void blk_cleanup_queue(struct request_queue *q);
该函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中使用。
分配“请求队列”
struct request_queue *blk_alloc_queue(int gfp_mask);
对于FLASH、RAM盘等完全随机访问的非机械设备来说,无法从高级的请求队列逻辑中获益。这时候应该使用上述函数分配一个“请求队列”,并使用如下函数来绑定请求队列和“制造请求”函数
void blk_queue_mask_request(struct request_queue *q, make_request_fn *mfn);
这是由于使用请求队列对于一个机械的磁盘设备而言的确有助于提高系统的性能,但对于非机械硬盘来说,无法从请求队列中得到益处。对于这些设备,Linux内核支持“无队列”的操作模式,为使用这种模式,驱动必须提供一个“制造请求”函数,而不是一个请求函数。
typedef int (make_request_fn) (struct request_queue *q, struct bio *bio);
提取请求
struct request *blk_fetch_request(struct request_queue *q)
该函数用于返回下一个要处理的请求(由I/O调度器决定)。如果没有请求则返回NULL。
该函数不会清除请求,它仍将这个请求保留在队列上,但是标识它为活动的,这个标识将阻止I/O调度器合并其他的请求到已开始执行的请求。由于该函数不从队列中清除请求,因此连续调用两次,可能会返回同一个请求结构体。
清除请求
__blk_end_request(struct request *rq, int error, unsigned int nr_bytes);
该函数用于从队列中请求一个请求。如果驱动中同时从同一个队列中操作了多个请求,它必须以这种方式将他们从队列中移除。
第一个参数传递已经处理结束的request请求,第二个请求用于传递是否存在错误,如果没有传递0,第三个参数传递处理的字节数。
获取请求操作的当前扇区
sector_t blk_rq_ops(const struct request *rq);
获取请求操作的扇区数
unsigned int blk_rq_cur_sectors(const struct request *rq);
获取请求的IO访问方式
rq_data_dir(struct request * rq);返回1代表写, 返回0代表读
获取bio的IO访问方式
bio_data_dir(struct bio *bio);
该函数用于获得数据传输的方向是READ还是WRITE
获取当前bio的页指针
struct page *bio_page(struct bio *bio);
获取当前bio的对应的当前内页偏移
int bio_offset(struct bio *bio);
返回bio_vec要传输的扇区数
int bio_cur_sectors(struct bio *bio);
返回当前数据缓冲区的内存虚拟地址
char *bio_data(struct bio *bio);
返回给定的bio的第i个缓冲区的虚拟地址。
char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);
该函数的反操作是:
void __bio_kunmap_atomic(char *addr, enum km_type type);
….
还有很多的函数
Linux 块设备驱动模型
ramdisk为例
struct sbull_dev {
int size;//块设备大小,字节数
u8 *data;//设备数据空间首地址
spinlock_t lock;//互斥自旋锁
struct request_queue *queue;//设备请求队列
struct gendisk *gd;//通用磁盘结构体
};
1.模块三要素
1) 模块许可申明
2) 加载函数
3) 卸载函数
2.注册块设备驱动
1) 注册块设备设备号
2) 资源申请(ramdisk 内存申请)
3) 初始化自旋锁
4) 指定请求模式
5) 初始化设备结构体成员
6) 注册块设备
ramdisk实例
/*
* Sample disk driver for 2.6.35.
*/
//#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/timer.h>
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
//#include <linux/buffer_head.h> /* invalidate_bdev */
#include <linux/bio.h>
MODULE_LICENSE("Dual BSD/GPL");
static int sbull_major = 0;
module_param(sbull_major, int, 0);
static int hardsect_size = 512;
module_param(hardsect_size, int, 0);
static int nsectors = 8*1024*2; /* How big the drive is */
module_param(nsectors, int, 0);
static int ndevices = 1;
module_param(ndevices, int, 0);
/*
* The different "request modes" we can use.
*/
enum {
RM_SIMPLE = 0, /* The extra-simple request function */
RM_FULL = 1, /* The full-blown version */
RM_NOQUEUE = 2, /* Use make_request */
};
//static int request_mode = RM_FULL;
//static int request_mode = RM_SIMPLE;
static int request_mode = RM_NOQUEUE;
module_param(request_mode, int, 0);
/*
* Minor number and partition management.
*/
#define SBULL_MINORS 16
//#define MINOR_SHIFT 4
//#define DEVNUM(kdevnum) (MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT
/*
* We can tweak our hardware sector size, but the kernel talks to us
* in terms of small sectors, always.
*/
#define KERNEL_SECTOR_SIZE 512
/*
* The internal representation of our device.
*/
struct sbull_dev {
int size; /* Device size in bytes */
u8 *data; /* points to the space of device */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
};
static struct sbull_dev *Devices = NULL;
/*
* Handle an I/O request.
* 实现扇区的读写
*/
static void sbull_transfer(struct sbull_dev *dev, unsigned long sector,
unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector*KERNEL_SECTOR_SIZE;
unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
if ((offset + nbytes) > dev->size) {
printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if (write)
memcpy(dev->data + offset, buffer, nbytes);
else
memcpy(buffer, dev->data + offset, nbytes);
}
static void sbull_request(struct request_queue *q)
{
struct request *req;
struct sbull_dev *dev;
while ((req = blk_fetch_request(q)) != NULL) {
dev= req->rq_disk->private_data;
/*
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non- ererefs request\n");
__blk_end_request_all(req, -EIO);
printk("*** not blk_fs_request ***\n");
continue;
}
*/
while ( 1 )
{
sbull_transfer(dev, blk_rq_pos(req), blk_rq_cur_sectors(req),
req->buffer, rq_data_dir(req));
if ( ! __blk_end_request_cur(req, 0) )
{
break;
}
}
}
}
/*
* Transfer a single BIO.
*/
static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
{
int i;
struct bio_vec *bvec;
sector_t sector = bio->bi_sector;
/* Do each segment independently. */
bio_for_each_segment(bvec, bio, i) {
char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
sbull_transfer(dev, sector, bio_cur_bytes(bio)>>9 ,
buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_bytes(bio)>>9;
__bio_kunmap_atomic(bio, KM_USER0);
}
return 0; /* Always "succeed" */
}
/*
* Transfer a full request.
*/
static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
struct bio *bio;
int nsect = 0;
__rq_for_each_bio(bio, req) {
sbull_xfer_bio(dev, bio);
nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
}
return nsect;
}
/*
* Smarter request function that "handles clustering".*/
static void sbull_full_request(struct request_queue *q)
{
struct request *req;
int nsect;
struct sbull_dev *dev ;
int i = 0;
while ((req = blk_fetch_request(q)) != NULL) {
dev = req->rq_disk->private_data;
/*
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-fs request\n");
__blk_end_request_all(req, -EIO);
continue;
}
*/
nsect = sbull_xfer_request(dev, req);
__blk_end_request(req, 0, (nsect<<9));
printk ("i = %d\n", ++i);
}
}
//The direct make request version
static void sbull_make_request(struct request_queue *q, struct bio *bio)
{
struct sbull_dev *dev = q->queuedata;
int status;
status = sbull_xfer_bio(dev, bio);
bio_endio(bio, status);
return;
}
/*
* The device operations structure.
*/
static struct block_device_operations sbull_ops = {
.owner = THIS_MODULE,
};
/*
* Set up our internal device.
*/
static void setup_device(struct sbull_dev *dev, int which)
{
/*
* Get some memory.
*/
memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors * hardsect_size;
dev->data = vmalloc(dev->size);
if (dev->data == NULL) {
printk (KERN_NOTICE "vmalloc failure.\n");
return;
}
spin_lock_init(&dev->lock);
/*
* The I/O queue, depending on whether we are using our own
* make_request function or not.
*/
switch (request_mode) {
case RM_NOQUEUE:
dev->queue = blk_alloc_queue(GFP_KERNEL);
if (dev->queue == NULL)
goto out_vfree;
blk_queue_make_request(dev->queue, sbull_make_request);
break;
case RM_FULL:
dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
if (dev->queue == NULL)
goto out_vfree;
break;
case RM_SIMPLE:
dev->queue = blk_init_queue(sbull_request, &dev->lock);
if (dev->queue == NULL)
goto out_vfree;
break;
default:
printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode);
/* fall into.. */
}
dev->queue->queuedata = dev;
/*
* And the gendisk structure.
*/
dev->gd = alloc_disk(SBULL_MINORS);
if (! dev->gd) {
printk (KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + ‘a‘);
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);
return;
out_vfree:
if (dev->data)
vfree(dev->data);
}
static int __init sbull_init(void)
{
int i;
/*
* Get registered.
*/
sbull_major = register_blkdev(sbull_major, "sbull");
if (sbull_major <= 0) {
printk(KERN_WARNING "sbull: unable to get major number\n");
return -EBUSY;
}
/*
* Allocate the device array, and initialize each one.
*/
Devices = (struct sbull_dev *)kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL);
if (Devices == NULL)
goto out_unregister;
for (i = 0; i < ndevices; i++)
setup_device(Devices + i, i);
return 0;
out_unregister:
unregister_blkdev(sbull_major, "sbull");
return -ENOMEM;
}
static void sbull_exit(void)
{
int i;
for (i = 0; i < ndevices; i++) {
struct sbull_dev *dev = Devices + i;
if (dev->gd) {
del_gendisk(dev->gd);
}
if (dev->queue) {
if (request_mode != RM_NOQUEUE)
{
blk_cleanup_queue(dev->queue);
}
}
if (dev->data)
vfree(dev->data);
}
unregister_blkdev(sbull_major, "sbull");
kfree(Devices);
}
module_init(sbull_init);
module_exit(sbull_exit);
sbull驱动的使用
1.编译
make
2.加载驱动
sudo insmod sbull.ko
3.分区
sudo fdisk /dev/sbulla
出现磁盘分区界面,选择m出现帮助信息:
选择n添加新分区:
选择p,新建主分区
选择w,保存分区信息
5.格式化disk
sudo mkfs.ext2 /dev/sbulla1
6.挂载文件系统
sudo mount –t ext2 /dev/sbulla1 mnt
7.修改为其他模式重新测试
文章来源:华清远见嵌入式学院,原文地址:http://www.embedu.org/Column/Column863.htm
块设备