1、基础概念
块设备是一种能随机访问的存储介质,与字符设备不同,它能保存文件系统数据。
IDE(Integrated Driver Electrics,集成驱动电子设备)是PC中常见的存储接口技术
ATA(Advance Technology Attachment ,高级技术配件),很老的了。
CD-ROM 和磁带等存储设备称为 ATAPI(ATA Packet Interface ,ATA包接口)
IDE/ATA 是一种并行总线技术(并行ATA或PATA),不能扩展至高速传输。
SATA(Serial ATA,串行ATA): 由PATA发展而来的现代串行总线技术,支持300MB/s 甚至更高的传输速度,支持热交换。
SCSI(Small Computer Interface,小型计算机系统接口):是服务器和高端工作站选用的一种存储技术,速度比SATA快一点。逐渐发展成SAS(Serial Attached SCSI,串行附加SCSI)总线技术(串行操作)
...
Linux 系统中的块设备一般以512字节单位,这个只的是在对硬件上的操作,而用户可以随意获取其中的每个字节
块设备的操作:
1、块设备格式化:mkfs.ext3 /dev/块名
2、挂载:mount
3、经过1、2之后才能访问
2、块设备驱动系统架构
过程解析:
1、用户应用程序唤醒I/O系统调用来访问文件,相关文件系统操作在到达各自的文件系统 驱动前,先经过VFS
2、如果能在高速缓存中取到相关的文件,则返回;
3、否则直接进入下一层,之后通过I/O调度到达块设备
4、每个块设备指定的数据在请求队列中排队。文件系统驱动程序将请求加入指定块设备的请求队列,同时块驱动程序从相应的队列中取出数据,在这期间,I/O调度器操控请求队列,使磁盘访问延时最小,同时吞吐量最大。
层次作用解析:
VFS:是对各种具体文件系统的一种封装 ,为用户程序访问文件提供统一的接口。
Cache:当用户发起文件访问请求的时候,首先会到Disk Cache中寻找文件是否被缓存了,如果在cache中,则直接从cache中读取。如果数据不在缓存中,就必须要到具体的文件系统中读取数据。
Mapping Layer:
1. 首先确定文件系统的block size,然后计算所请求的数据包含多少个block。
2. 调用具体文件系统的函数来访问文件的inode结构,确定所请求的数据在磁盘上的地址。
Generic Block Layer:
Linux内核把把块设备看作是由若干个扇区组成的数据空间。上层的读写请求在通用块层被构造成一个或多个bio结构。
2.5 I/O Scheduler LayerI/O调度层负责采用某种算法(如:电梯调度算法、限时式等)将I/O操作进行排序。
重要数据结构:
通用磁盘(generic disk)每个磁盘对应一个 gendisk结构
struct gendisk {
int major; /* major number of driver */
int first_minor;
int minors; /* maximum number of minors, =1 for* disks that can't be partitioned. */
char disk_name[DISK_NAME_LEN]; /* name of major driver */
...
const struct block_device_operations *fops;
struct request_queue *queue;/*等待队列*/
void *private_data; /*私有数据*/
....
};
struct request
{
...
struct request_queue *q; /*等待队列*/
...
sector_t __sector; /* sector cursor */
}
struct bio:该结构体是块I/O操作在页级粒度的底层描述。块数据通过一个bio_vec结构体数组在内部被表示成I/O向量。每个bio_vec数组元素由三元组(页,页偏移,长度)组成,表示该I/O块的一个段。使用I/O请求也页向量的优点:可实现高效的分散、聚集操作
初始化:
1、int register_blkdev(unsigned int major, const char *name) /*分配一个未使用的主设备 号,并为设备在/proc/devices中增加一个入口*/
2、struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
/*初始化请求队列,返回请求队列指针*/
3、void blk_queue_logical_block_size(struct request_queue *q, unsigned short size)
/*为等待队列设置扇区尺寸*/
硬件执行磁盘事务是以扇区为单位的,而软件子系统一般是以块为单位处理数据的。扇区大小通常是512B,块大小是4K。在初始化时需要将存储硬件所能支持的扇区大小和驱动程序在一次请求中能接受的最大扇区数通知块层,使用以上函数即可实现
4、struct gendisk *alloc_disk(int minors) /*分配块设备*/
5、对块设备成员进行初始化
i. 主设备号
ii. 次设备号起始,一般为0
iii. 操作函数集:
iv. 等待队列
v. 磁盘名
vi. 使用set_capacity()填写扇区大小
6、调用add_disk()将磁盘添加到块I/O层
块设备操作
1、块驱动程序的open方法时在诸如挂载介质的文件系统或执行文件系统检查(fsck命令)等操作期间被调用。open()中完成的任务一般依赖于硬件
2、如果驱动需要支持命令,则使用ioctl()方法实现,如软盘驱动支持弹出介质的命令
磁盘(块设备)访问
1、 块驱动程序不像字符设备驱动程序,它不支持read()/write()的数据传输方式。而是用称 为“请求方法”的特殊方式实现磁盘访问
2、 有一点需要注意的是:内核在调用request()前先占用一个请求锁,这是保护相关队列不 被并发访问。因此,如果request中调用了可能引起睡眠的函数,就必须在调用前先释 放,并在返回前从新获得
3、 实现读写请求处理函数:
struct request *blk_fetch_request(struct request_queue *q) /*取出队列*/
之后对取出的队列数据进行操作
/************************************
作者:hntea
时间:2016/3/25
函数功能:简单块设备驱动设计
操作:
加载驱动之后,
需要格式化 /dev/myblk;
之后才能通过mount 命令挂载
************************************/
#include<linux/module.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/errno.h>
#include<linux/genhd.h>
#include <linux/spinlock.h>
#include<linux/blkdev.h>
#include <linux/vmalloc.h>
#define SECT_SIZE 512 /*扇区大小*/
#define DISK_SIZE 512*1024 /*块大小*/
static DEFINE_SPINLOCK(myblk_lock); /*初始化自旋锁*/
struct request_queue *myblk_que ; /*定义请求队列*/
struct gendisk *myblk; /*定义磁盘结构*/
unsigned char* buf ; /*虚拟磁盘*/
static int major = 0;
/*块设备操作*/
static struct block_device_operations myblk_fops=
{
.owner = THIS_MODULE,
};
/**********************************
函数功能:数据处理
参数说明:
dest :数据传输目的地址
src :数据传输源地址
sector :请求访问的起始扇区
__data_len :总共需要操作多少字节数
data_dir :数据传输方向
***********************************/
static void blk_transfer(unsigned char* dest, unsigned char *src,unsigned long sector,unsigned long __data_len, int data_dir)
{
unsigned long offset = sector * SECT_SIZE ; /*扇区偏移*/
unsigned long nbytes = __data_len; /*写入大小*/
unsigned char* d_buf = dest ;
if(data_dir)
{
/*向设备写入数据*/
memcpy(d_buf+offset,src,nbytes);
}
else{
/*向用户输出数据*/
memcpy(src,d_buf+offset,nbytes);
}
}
/*队列处理*/
static void do_myblk_request(struct request_queue *q)
{
struct request *rq;
/*取出队列*/
rq = blk_fetch_request(q);
/*循环获取直到队列为空*/
while (rq != NULL)
{
/*处理请求*/
blk_transfer(buf,rq->buffer,rq->__sector,rq->__data_len,rq_data_dir(rq));
/*再次取出当前队列*/
if( ! __blk_end_request_cur(rq, 0) )
rq = blk_fetch_request(q);
}
}
static int blk_init(void)
{
int ret = 0;
/*0、分配一个虚拟磁盘,既在内存中画出512K 用来虚拟磁盘*/
if(!(buf = vmalloc(DISK_SIZE)))
printk("vmalloc err!\n");
/*1.注册并分配主设备号*/
if ((major = register_blkdev(0, "myblk")) <=0 ) /*当major=0时自动分配一个未使用的主设备*/
{
printk("alloc major num err!\n");
return -EIO;
}
/*2、初始化请求队列*/
myblk_que = blk_init_queue(do_myblk_request, &myblk_lock);
if(!myblk_que)
return -EIO;
/*3为等待队列设置扇区尺寸*/
blk_queue_logical_block_size(myblk_que, SECT_SIZE);
/*4、分配块设备,这个类似于A盘,B盘*/
myblk = alloc_disk(1);
/*5、初始化块设备*/
myblk->major = major;
myblk->first_minor = 0;
myblk->fops = &myblk_fops;
myblk->queue = myblk_que; /*绑定队列*/
sprintf(myblk->disk_name,"myblk");
set_capacity(myblk,1024);
/*6、添加磁盘*/
add_disk(myblk);
return ret;
}
static void blk_exit(void)
{
del_gendisk(myblk);
put_disk(myblk);
blk_cleanup_queue(myblk_que);
unregister_blkdev(major, "myblk");
vfree(buf);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hntea");
module_init(blk_init);
module_exit(blk_exit);