簇:簇是指可分配的用来保存文件的最小磁盘空间,扇区是磁盘最小的物理存储单元,但由于操作系统无法对数目众多的扇区进行寻址,所以操作系统就将相邻的扇区组合在一起,形成一个簇,然后再对簇进行管理。每个簇可以包括2、4、8、16、32或64个扇区。显然,簇是操作系统所使用的逻辑概念,而非磁盘的物理特性。
操作系统规定一个簇中只能放置一个文件的内容,因此文件所占用的空间,只能是簇的整数倍;而如果文件实际大小小于一簇,它也要占一簇的空间。所以,一般情况下文件所占空间要略大于文件的实际大小。
看到这幅图片应该就豁然开朗了吧,张开嘴巴,"哦...."。
在FAT16文件系统中,每个分区最多有65525个簇,簇大小默认值为32KB;在FAT32文件系统中使用的簇比FAT16小,默认为4KB。
柱面:硬盘中,不同盘片相同半径的磁道所组成的圆柱称为柱面.柱面这里重点要落到这个“面”子。柱的寓意在于,我们记录数据的方式,其实就是在盘面上烧洞洞,你懂得。凹凸分别表示着二进制的0和1。
存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数
磁头数:即代表了硬盘实际的盘面数目,因为是两面的嘛。
磁道(柱面)数:即盘面上由中心向外延伸的圆的圈数,有多少个同心圆数目就是几。
扇区数:我们单独拿一个同心圆来看,圆的周长,被扇区的外弧面分成了很多的段,这些段的数目就是啦。
扇区字节数:这个就不多说了,因机器不同而不同,一般都是512 B(字节)大的有1K、4K的。
我提一个思考题哈:我们的硬盘一定是2的倍数么,如果不是,是在哪个地方出了问题导致不是2的倍数的呢?【提示:簇的含义中做了加法!哈哈】
Linux 块设备处理层次模型:
块设备中那些的重要的结构体:这些事转得别人,很总结得很好
原文:http://blog.chinaunix.net/space.php?uid=23399063&do=blog&id=70124
gendisk结构体
内核使用gendisk结构来表示一个独立的磁盘设备。内核还使用gendisk结构表示分区,在此结构中,很多成员必须由驱动程序来进行初始化。此结构定义在<linux/genhd.h>。
//gendisk structure
struct gendisk{
/*前三个元素共同表征了一个磁盘的主,次设备号,同一个磁盘的各个分区共享一个主设备号*/
int major;/*主设备号*/
int first_minor;/*第一个次设备号*/
int minors;/*最大的次设备数,如果不能分区,则为*/
char disk_name[32];
struct hd_struct** part;/*磁盘上的分区信息*/
struct block_device_operations* fops;/*块设备操作,block_device_operations*/
struct request_queue* queue;/*请求队列,用于管理该设备IO请求队列的指针*/
void* private_data;/*私有数据*/
sector_t capacity;/*扇区数,512字节为个扇区,描述设备容量*/
//......
};
关于gendisk的操作:
/*分配一个gendisk结构体,此结构体是由内核动态分配的*/
struct gendisk* alloc_disk(int minors);
/*增加gendisk,来注册该设备,此动作应该在设备驱动初始化完毕,并能响应磁盘请求之后*/
void add_disk(struct gendisk* gd);
/*释放一个不再需要的磁盘*/
/**
***gendisk引用计数器:gendisk包含一个kobject成员.通过get_disk()&put_disk()函数来操作引用
***计数,此操作不需要驱动亲自完成.通常调用del_gendisk()会去掉gendisk的最终引用计数,但不是必
***须的.因此在del_gendisk()后gendisk结构体可能继续存在.
**/
/*设置gendisk容量*/
void set_capacity(struct gendisk* disk, sector_t size);
块设备中,最小的可寻址单元就扇区,常见扇区大小是512字节.扇区的大小是设备的物理属性,是所有块设备的基本单元,块设备无法对比扇区小的单元进行寻址和操作.不过许多块设备能够一次传输多个扇区.不管物理设备的真实扇区是多少,内核与块设备交互的扇区均以512字节为单位.所以set_capcity()函数以512字节为单位.
request和bio结构体
1)请求request
request和request_queue结构体:Linux块设备驱动中,使用request结构体来表征等待进行的IO请求;并用request_queue来表征一个块IO请求队列.两个结构体的定义如下:
request结构体
struct request{
struct list_head queuelist;
unsigned long flags;
sector_t sector;/*要传输的下一个扇区*/
unsigned long nr_sectors;/*要传送的扇区数目*/
unsigned int current_nr_sector;/*当前要传送的扇区*/
sector_t hard_sector;/*要完成的下一个扇区*/
unsigned long hard_nr_sectors;/*要被完成的扇区数目*/
unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/
struct bio* bio;/*请求的bio结构体的链表*/
struct bio* biotail;/*请求的bio结构体的链表尾*/
/*请求在屋里内存中占据的不连续的段的数目*/
unsigned short nr_phys_segments;
unsigned short nr_hw_segments;
int tag;
char* buffer;/*传送的缓冲区,内核的虚拟地址*/
int ref_count;/*引用计数*/
...
};
说明:
request结构体的主要成员包括:
sector_t hard_sector;/*要完成的下一个扇区*/
unsigned long hard_nr_sectors;/*要被完成的扇区数目*/
unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/
/*
* 上述三个成员依次是第一个尚未传输的扇区,尚待完成的扇区数,当前IO操作中待完成的扇区数
* 但驱动中一般不会用到他们.而是下面的一组成员.
*/
sector_t sector;/*要传输的下一个扇区*/
unsigned long nr_sectors;/*要传送的扇区数目*/
unsigned int current_nr_sector;/*当前要传送的扇区*/
/*
* 这三个成员,以字节为单位.如果硬件的扇区大小不是512字节.如字节,则在开始对硬件进行操作之
* 前,应先用4来除起始扇区号.前三个成员,与后三个成员的关系可以理解为"副本".
*/
关于unsigned short nr_phys_segments:该成员表示相邻的页被合并后,这个请求在物理内存中的段的数目.如果该设备支持SG(分散/聚合,scatter/gather),可根据该字段申请sizeof(scatterlist*) nr_phys_segments的内存,并使用下面的函数进行DMA映射:
int blk_rq_map_sg(request_queue_t* q, struct request* rq, struct scatterlist *sg);
该函数与dma_map_sg()类似,返回scatterlist列表入口的数量.
关于struct list_head queuelist:该成员用于链接这个请求到请求队列的链表结构,函数blkdev_ dequeue_request()可用于从队列中移除请求.宏rq_data_dir(struct request* req)可获得数据传送方向.返回0表示从设备读取,否则表示写向设备.
request_queue请求队列
struct request_queue{
...
/*自旋锁,保护队列结构体*/
spinlock_t __queue_lock;
spinlock_t* queue_lock;
struct kobject kobj;/*队列kobject*/
/*队列设置*/
unsigned long nr_requests;/*最大的请求数量*/
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned int nr_batching;
unsigned short max_sectors;/*最大扇区数*/
unsigned short max_hw_sectors;
unsigned short max_phys_sectors;/*最大的段数*/
unsigned short max_hw_segments;
unsigned short hardsect_size;/*硬件扇区尺寸*/
unsigned int max_segment_size;/*最大的段尺寸*/
unsigned long seg_boundary_mask;/*段边界掩码*/
unsigned int dma_alignment;/*DMA传送内存对齐限制*/
struct blk_queue_tag* queue_tags;
atomic_t refcnt;/*引用计数*/
unsigned int in_flight;
unsigned int sg_timeout;
unsigned int sg_reserved_size;
int node;
struct list_head drain_list;
struct request* flush_rq;
unsigned char ordered;
};
说明:请求队列跟踪等候的块IO请求,它存储用于描述这个设备能够支持的请求的类型信息,他们的最大大小,多少不同的段可以进入一个请求,硬件扇区大小,对齐要求等参数.其结果是:如果请求队列被配置正确了,它不会交给该设备一个不能处理的请求.
请求队列还要实现一个插入接口,这个接口允许使用多个IO调度器,IO调度器以最优性能的方式向驱动提交IO请求.大部分IO调度器是积累批量的IO请求,并将其排列为递增/递减的块索引顺序后,提交给驱动.另外,IO调度器还负责合并邻近的请求,当一个新的IO请求被提交给调度器后,它会在队列里搜寻包含邻近的扇区的请求.如果找到一个,并且请求合理,调度器会将这两个请求合并.
Linux2.6的四个IO调度器,他们分别是No-op/Anticipatory/Deadline/CFQ IO scheduler.
关于request_queu结构体的操作:
//初始化请求队列
kernel elevator = deadline;/*给kernel添加启动参数*/
request_queue_t* blk_init_queue(request_fn_proc* rfn, spinlock_t* lock);
/*
* 两个参数分别是请求处理函数指针和控制队列访问权限的自旋锁.
* 此函数会发生内存分配的行为,需要检查其返回值.一般在加载函数中调用.
*/
//清除请求队列
void blk_cleanup_queue(request_queue_t* q);
/*
* 此函数完成将请求队列返回给系统的任务,一般在卸载函数中调用.
* 此函数即bld_put_queue()的宏定义#define blk_put_queue(q) blk_cleanup_queue((q))
*/
//分配"请求队列"
request_queue_t* blk_alloc_queue(int gfp_mask);
void blk_queue_make_request(request_queue_t* q, make_request_fn* mfn);
/*
* 前一个函数用于分配一个请求队列,后一个函数是将请求队列和"制造函数"进行绑定
* 但函数blk_alloc_queue实际上并不包含任何请求.
*/
//提取请求
struct request* elv_next_request(request_queue_t* q);
//去除请求
void blkdev_dequeue_request(struct request* req);
void elv_requeue_request(request_queue_t* queue, struct request* req);
//启停请求
void blk_stop_queue(request_queue_t* queue);
void blk_start_queue(request_queue_t* queue);
//参数设置
void blk_queue_max_sectors(request_queue_t* q, unsigned short max);
/*请求可包含的最大扇区数.默认255*/
void blk_queue_max_phys_segments(request_queue_t* q, unsigned short max);
void blk_queue_max_hw_segments(request_queue_t* q, unsigned short max);
/*这两个函数设置一个请求可包含的最大物理段数(系统内存中不相邻的区),缺省是128*/
void blk_queue_max_segment_size(request_queue_t* q, unsigned int max);
/*告知内核请求短的最大字节数,默认2^16 = 65536*/
//通告内核
void blk_queue_bounce_limit(request_queue_t* queue, u64 dma_addr);
/*
* 此函数告知内核设备执行DMA时,可使用的最高物理地址dma_addr,常用的宏如下:
* BLK_BOUNCE_HIGH:对高端内存页使用反弹缓冲(缺省)
* BLK_BOUNCE_ISA:驱动只可以在MB的ISA区执行DMA
* BLK_BOUNCE_ANY:驱动可在任何地方执行DMA
*/
blk_queue_segment_boundary(request_queue_t* queue, unsigned long mask);
/*这个函数在设备无法处理跨越一个特殊大小内存边界的请求时,告知内核这个边界.*/
void blk_queue_dma_alignment(request_queue_t* q, int mask);
/*告知内核设备加于DMA传送的内存对齐限制*/
viod blk_queue_hardsect_size(request_queue_t* q, unsigned short max);
/*此函数告知内核块设备硬件扇区大小*/
块I/O
通常一个bio对应一个IO请求.IO调度算法可将连续的bio合并成一个请求.所以一个请求包含多个bio.
struct bio{
sector_t bi_sector;/*要传送的第一个扇区*/
struct bio* bi_next;/*下一个bio*/
struct block_device* bi_bdev;
unsigned long bi_flags;
/*如果是一个写请求,最低有效位被置位,可使用bio_data_dir(bio)宏来获取读写方向*/
unsigned long bi_rw;/*地位表示R/W方向,高位表示优先级*/
unsigned short bi_vcnt;/*bio_vec数量*/
unsigned short bi_idx; /*当前bvl_vec索引*/
unsigned short bi_phys_segments;/*不相邻的物理段的数目*/
unsigned short bi_hw_segments;/*物理合并和DMA remap合并后不相邻的物理扇区*/
unsigned int bi_size;
/*被传送的数据大小(byte),用bio_sector(bio)获取扇区为单位的大小*/
/*为了明了最大的hw尺寸,考虑bio中第一个和最后一个虚拟的可合并的段的尺寸*/
unsigned int bi_hw_front_size;
unsigned int bi_hw_back_size;
unsigned int bi_max_vecs;/*能持有的最大bvl_vecs数*/
struct bio_vec* bio_io_vec;/*实际的vec列表*/
bio_end_io_t* bio_end_io;
atomic_t bi_cnt;
void* bi_private;
bio_destructor_t* bi_destructor;
};
//结构体包含三个成员
struct bio_vec{
struct page* bv_page;//页指针
unsigned int bv_len;//传送的字节数
unsigned int bv_offset;//偏移位置
};
/*一般不直接访问bio的bio_vec成员,而使用bio_for_each_segment()宏进行操作.
*该宏循环遍历整个bio中的每个段.
*/
#define __bio_for_each_segment(bvl, bio, i, start_idx)\
for(
bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\
i <(bio)->bi_vcnt;\
bvl++, i++\
)
#define bio_for_each_segment(bvl, bio, i)\
__bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)
在内核中,提供了一组函数(宏)用于操作bio:
int bio_data_dir(struct bio* bio);
该函数用于获得数据传送方向.
struct page* bio_page(struct bio* bio);
该函数用于获得目前的页指针.
int bio_offset(struct bio* bio);
该函数返回操作对应的当前页的页内偏移,通常块IO操作本身就是页对齐的.
int bio_cur_sectors(struct bio* bio);
该函数返回当前bio_vec要传输的扇区数.
char* bio_data(struct bio* bio);
该函数返回数据缓冲区的内核虚拟地址.
char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset);
该函数也返回一个内核虚拟地址此地址可用于存取被给定的bio_vec入口指向的数据缓冲区.同时会屏蔽中断并返回一个原子kmap,因此,在此函数调用之前,驱动不应该是睡眠状态.
void bvec_kunmap_irq(char* buffer, unsigned long flags);
该函数撤销函数bvec_kmap_irq()创建的内存映射.
char* bio_kmap_irq(struct bio* bio, unsigned long* flags);
该函数是对bvec_kmap_irq函数的封装,它返回给定的比偶的当前bio_vec入口的映射.
char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type);
该函数是通过kmap_atomic()获得返回给定bio的第i个缓冲区的虚拟地址.
void __bio_kunmap_atomic(char* addr, enum km_type type);
该函数返还由函数__bio_kmap_atomic()获得的内核虚拟地址给系统.
void bio_get(struct bio* bio);
void bio_put(struct bio* bio);
上面两个函数分别完成对bio的引用和引用释放.
下图可以体现出bio/request/request_queue/bio_vec四个结构体之间的关系.
这个图的意思是:在我们的request里面,包含着在device_operation中没有定义到的读写操作,并且,这些请求是一队列的方式被处理的。那么请求队列中就是一个个请求;我们拿出一个单个请求来看,它又包含着一个bio结构体,结构体里面的bi_bdev和bi_io_vec,他们分别对应着bio_vec这个结构体里面的的bv_page和bv_offset。而这个bv_page成员又同时对应着多个page页面,因为我们这里讲的这个块设备使我们那内存中的一片地址空间,虚拟出来的一个虚拟块设备,在内存中,是以页(page)来管理的;即抽象成我们的这个虚拟设备的话,就对应着最小的单元“扇区”。这里想说的是一个bv_page可以对应着多个page。在我们这个虚拟设备中,对应着有多种处理请求的方式,可以一次处理一个,也可以一次处理多个(即对一个请求队列完成处理)看完下面这个图也许会更明白点: