bio-linux分区,bio 与块设备驱动

中,该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块I/O操作。一个片段是一小 块连续的内存缓冲区。这样的好处就是不需要保证单个缓冲区一定要连续。所以通过片段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio结构体也 能对内核保证I/O操作的执行,这样的就叫做聚散I/O.

bio为通用层的主要数据结构,既描述了磁盘的位置,又描述了内存的位置,是上层内核vfs与下层驱动的连接纽带。

struct bio {

//该bio结构所要传输的第一个(512字节)扇区:磁盘的位置

sector_t bi_sector;

struct bio *bi_next; //请求链表

struct block_device *bi_bdev;//相关的块设备

unsigned long bi_flags//状态和命令标志

unsigned long bi_rw; //读写

unsigned short bi_vcnt;//bio_vesc偏移的个数

unsigned short bi_idx; //bi_io_vec的当前索引

unsigned short bi_phys_segments;//结合后的片段数目

unsigned short bi_hw_segments;//重映射后的片段数目

unsigned int bi_size; //I/O计数

unsigned int bi_hw_front_size;//第一个可合并的段大小;

unsigned int bi_hw_back_size;//最后一个可合并的段大小

unsigned int bi_max_vecs; //bio_vecs数目上限

struct bio_vec *bi_io_vec; //bio_vec链表:内存的位置

bio_end_io_t *bi_end_io;//I/O完成方法

atomic_t bi_cnt; //使用计数

void *bi_private; //拥有者的私有方法

bio_destructor_t *bi_destructor; //销毁方法

};

此结构体的目的主要就是代表正在现场执行的I/O操作,所以该结构体中的主要域都是用来相关的信息的,而其中bi_io_vec、bi_vcnt、bi_idx重要

这三者形成了这样一种关系:bio-->bi_io_vec,bi_idx(就如基地址加偏移量一般,可以轻易的找到具体的bio_vec)-->page(再通过vec找到page)

其 中bi_io_vec指向一个bio_vec结构体数组,该结构体链表包含了一个特定的I/O操作所需要使用到的所有片段。每个bio_vec都是的向量,描述的是一个特定的片段:片段所在的物理页,块在物理页中的偏移位置,从给定偏移量开始的块长度,整个bio_io_vec结构体数组表示了一个完整的缓冲区。

struct bio_vec {

struct page    *bv_page;指向整个缓冲区所驻留的物理页面

unsigned int    bv_len;这个缓冲区以字节为单位的大小

unsigned int    bv_offset;缓冲区所驻留的页中以字节为单位的偏移量。

};

bi_vcnt域用来描述bi_io_vec所指向的bio_vec数组中的向量数目。当I/O操作完成后,bi_idx指向数组的当前索引。一个块请求通过一个bio表示。每个请求包括多个或者一个块,而这些块有都存储在bio_vec结构体的数组中,这些结构描述了每个片段在物理页中的实际位置,并且如向量一样的组织在一起,I/O操作的第一个片段由b_io_vec结构体所指向,其他片段则在其后依次放置,共有bi_vcnt个片段,当I/O层开始执行请求,需要各个使用片段时,bi_idx会不断更新,从而总指向当前的片段。看,这就是在入门C语言中用到的最朴实的概念,数组寻址的概念相类似。

块设备将挂起的块请求保存在请求队列中,该队列由request_queue结构体表示,定义在文件中,包含一个双向请求队列以及相关控制信息。通过内核中像文件系统这样高层的代码将请求加入到队列中,请求队列只要不为空,队列对应的块设备驱动程序就会从队列头 获取请求,然后将其加入到对应的块设备中去,请求队列表中的每一项都是一个单独的请求,由request结构体表示。

而队列中的请求request,定义在中,一个请求可能要操作多个连续的磁盘块,所以每个请求可以由多个bio结构体组成。每个bio结构体都可以描述多个片段。下面就是request中比较常用的几个域。

struct request {

struct list_head queuelist;//连接这个请求到请求队列.

//追踪请求硬件完成的扇区的成员.第一个尚未被传送的扇区被存储到 hard_sector,已经传送的扇区总数在hard_nr_sectors,并且在当前bio中剩余的扇区数是hard_cur_sectors.这些成员打算只用在块子系统;驱动不应当使用它们.

struct request_queue *q;

sector_t hard_sector;

unsigned long hard_nr_sectors;

unsigned int hard_cur_sectors;

struct bio *bio;//bio 是给这个请求的 bio 结构的链表. 你不应当直接存取这个成员; 使用 rq_for_each_bio(后面描述) 代替.

unsigned short nr_phys_segments;//被这个请求在物理内存中占用的独特段的数目, 在邻近页已被合并后

char *buffer;//随着深入理解,可见到这个成员仅仅是在当前 bio 上调用 bio_data 的结果.

};

而几个关键结构之间的关系是如何的呢?request_queue中是请求队列,通过它找到request,将这些请求连成一体,然后在request中包含bio,然后通过bio结构体找到对应的page,然后通过page读取物理内存中的信息。大体就是这样一个关系。

块驱动程序步骤与实例:

对于大多数块驱动程序来说,首先都该是向内核注册自己!这个任务的函数是register_blkdev(在中定义):

int register_blkdev(unsigned int major, const char *name);

参数是设备要使用的主编号和关联的名子(内核将显示它在/proc/devices). 如果major传递为0,内核分配一个新的主编号并且返回它给调用者.

取消注册的对应函数是:int unregister_blkdev(unsigned int major, const char *name);参数必须匹配传递给 register_blkdev 的那些。

在2.6内核,register_blkdev所进行的功能已随时间正在减少;这个调用唯一的任务是如果需要,分配一个动态主编号,并且在/proc/devices创建一个入口.

描述虚拟设备的结构体,里面的结构体除去timer_list都在前面介绍:

struct sbull_dev

{

int size; //以扇区为单位,设备的大小

u8 *data; //数据数组

short users;//用户数目

short media_change;//介质改变标志

spinlock_t lock;//用户互斥

struct request_queue *queue;//设备请求队列

struct gendisk *gd;//gendisk结构

struct timer_list timer;//模拟介质改变

};

static struct sbull_dev *Devices = NULL;//申请一个设备

memset (dev, 0, sizeof (struct sbull_dev));//申请内存空间

dev->size = nsectors*hardsect_size;//设备大小:1024*512

dev->data = vmalloc(dev->size);

switch (request_mode) {

case RM_NOQUEUE:

dev->queue = blk_alloc_queue(GFP_KERNEL);

blk_queue_make_request(dev->queue, sbull_make_request);

break;

case RM_FULL:

dev->queue = blk_init_queue(sbull_full_request, &dev->lock);

break;

default:

printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode);

case RM_SIMPLE:

dev->queue = blk_init_queue(sbull_request, &dev->lock);

if (dev->queue == NULL)

goto out_vfree;

break;

}

使用bio结构编写的块设备驱动程序。

static void sbull_full_request(request_queue_t *q)

{

struct request *req;

int sectors_xferred;

struct sbull_dev *dev = q->queuedata;

while ((req = elv_next_request(q)) != NULL) {//获得队列中的下一个request

if (! blk_fs_request(req)) {

printk (KERN_NOTICE "Skip non-fs request\n");

end_request(req, 0);//配合elv_next_request使用,完成一个请求

continue;

}

sectors_xferred = sbull_xfer_request(dev, req);//返回数量

if (! end_that_request_first(req, 1, sectors_xferred)) {//驱动程序从前一次结束的地方开始,完成了规定数目的扇区的传输

blkdev_dequeue_request(req);//从队列中删除一个请求函数,当end_that_request_first都被传输后,则必须调用此函数

end_that_request_last(req);//通知任何等待已经完成请求的对象,并重复利用该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) {//以宏的形式实现的控制结构,遍历请求中的每个bio

sbull_xfer_bio(dev, bio);

nsect += bio->bi_size/KERNEL_SECTOR_SIZE;//#define KERNEL_SECTOR_SIZE    512

}

return nsect;

}

static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)

{

int i;

struct bio_vec *bvec;

sector_t sector = bio->bi_sector;

bio_for_each_segment(bvec, bio, i) //用来遍历组成bio结构的段的伪控制结构

{

char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);//底层函数直接映射了指定索引号为i的bio_vec中的缓冲区。

sbull_transfer(dev, sector, bio_cur_sectors(bio),buffer, bio_data_dir(bio) == WRITE);//完全简单的基于ram设备。完成实际传输。

//bio_cur_sectors用来访问bio结构中的当前段,bio_data_dir用来获得bio结构描述的大小和传输方向

sector += bio_cur_sectors(bio);

__bio_kunmap_atomic(bio, KM_USER0);

}

return 0;

}

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 (write)

memcpy(dev->data + offset, buffer, nbytes);

else

memcpy(buffer, dev->data + offset, nbytes);

}

register_blkdev可用来获得一个主编号,但不使任何磁盘驱动器对系统可用.有一个分开的注册接口你必须使用来管理单独的驱动器.它是 struct block_device_operations, 定义在 .

struct block_device_operations {

int (*open) (struct inode *, struct file *);//设备打开函数

int (*release) (struct inode *, struct file *);//设备关闭函数

int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);//实现ioctl系统调用的方法.大部分的块驱动 ioctl 方法相当短.

long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);//

long (*compat_ioctl) (struct file *, unsigned, unsigned long);

int (*direct_access) (struct block_device *, sector_t,void **, unsigned long *);

int (*media_changed) (struct gendisk *);

//被内核调用来检查是否用户已经改变了驱动器中的介质的方法,如果是这样返回一个非零值.显然,这个方法仅适用于支持可移出的介质的驱动器(并且最好给驱动一个"介质被改变"标志); 在其他情况下可被忽略.

int (*revalidate_disk) (struct gendisk *);

//revalidate_disk方法被调用来响应一个介质改变;它给驱动一个机会来进行需要的任何工作使新介质准备好使用.这个函数返回一个int值,但是值被内核忽略.

int (*getgeo)(struct block_device *, struct hd_geometry *);

struct module *owner;//一个指向拥有这个结构的模块的指针; 它应当常常被初始化为 THIS_MODULE.

};

继续初始化:

dev->gd = alloc_disk(SBULL_MINORS);//动态分配gendisk结构(表是一个独立的磁盘设备)

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));

//使用KERNEL_来进行内核512字节扇区到实际使用扇区大小的转换。

add_disk(dev->gd);//结束设置过程。

其余部分参见ldd3的sbull

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值