linux块设备
来自:
https://www.cnblogs.com/xuyh/p/5333086.html
http://www.cnblogs.com/xuyh/p/5333191.html
http://www.cnblogs.com/xuyh/p/5340026.html
一: 块设备概念:
一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。可以随机访问,块设备的访问位置必须能够在介质的不同区间前后移动。
二: 块设备相关属性:
扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte。(对设备而言)
块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)
段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。
三: 块设备被访问的分层实现:
1. 首先块设备驱动是以何种方式对块设备进行访问的。在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统(即:上图中的I/O调度层)来负责提交I/O请求,I/O调度程序将磁盘资源分配给系统中所有挂起的块I/O请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备,关于更多详细的I/O调度知识这里就不深加研究了。
2. 块设备驱动又是怎样维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系呢?这就是上图中通用块层(Generic Block Layer)要做的事情了。在通用块层中,通常用一个bio结构体来对应一个I/O请求,它代表了正在活动的以段(Segment)链表形式组织的块IO操作,对于它所需要的所有段又用bio_vec结构体表示。
3,块设备驱动又是怎样对底层物理磁盘进行反问的呢?上面讲的都是对上层的访问对上层的关系。Linux提供了一个gendisk数据结构体,用他来表示一个独立的磁盘设备或分区。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,他就是block_device_operations结构体,他的作用相信大家已经很清楚了。
块设备相关的数据结构以及接口:
块设备接口则相对复杂,读写API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。
一: block_device: block_device结构代表了内核中的一个块设备。它可以表示整个磁盘或一个特定的分区。当这个结构代表一个分区时,它的bd_contains成员指向包含这个分区的设备,bd_part成员指向设备的分区结构。当这个结构代表一个块设备时,bd_disk成员指向设备的gendisk结构。
struct block_device {
dev_t bd_dev;
struct inode * bd_inode; /*分区结点*/
int bd_openers;
struct semaphore bd_sem; /*打开/关闭锁*/
struct semaphore bd_mount_sem; /* 加载互斥锁*/
struct list_head bd_inodes;
void * bd_holder;
int bd_holders;
struct block_device * bd_contains;
unsigned bd_block_size;//分区块大小
struct hd_struct * bd_part;
unsigned bd_part_count;//打开次数
int bd_invalidated;
struct gendisk * bd_disk;
struct list_head bd_list;
struct backing_dev_info *bd_inode_backing_dev_info;
unsigned long bd_private;
};
二:gendisk是一个单独的磁盘驱动器的内核表示。内核还使用gendisk来表示分区。
struct gendisk {
int major; //主设备号
int first_minor;
int minors; //最大的次设备号数量,如果设备不能分区,该值为1
char disk_name[32]; //主设备名
struct hd_struct **part; //分区信息,有minors个
struct block_device_operations *fops;//设备操作
struct request_queue *queue; //设备管理I/O请求
void *private_data;
sector_t capacity;
int flags;
char devfs_name[64];
int number;
struct device *driverfs_dev;
struct kobject kobj;
struct timer_rand_state *random;
int policy;
atomic_t sync_io;
unsigned long stamp, stamp_idle;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats; #else
struct disk_stats dkstats; #endif
};
gendisk结构的操作函数包括以下几个:struct gendisk *alloc_disk(int minors); //分配磁盘 void add_disk(struct gendisk *disk); //增加磁盘信息 void unlink_gendisk(struct gendisk *disk) //删除磁盘信息 void delete_partition(struct gendisk *disk, int part); //删除分区 void add_partition(struct gendisk *disk, int part, sector_t start, sector_t len, int flags);//添加分区
三: 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);
long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
long (*compat_ioctl) (struct file *, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
block_device_operations并不能完全提供文件操作全部的API,实际上只提供了open、release等函数,其他的文件操作依赖于def_blk_fops:
const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write= generic_file_aio_write_nolock,
.mmap = generic_file_mmap,
.fsync = block_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl, #endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
四: 系统对块设备进行读写操作时,通过块设备通用的读写操作函数将一个请求保存在该设备的操作请求队列(request queue)中,然后调用这个块设备的底层处理函数,对请求队列中的操作请求进行逐一执行。request_queue结构描述了块设备的请求队列,该结构定义如下:
struct request_queue
{
struct list_head queue_head;
struct request *last_merge;
elevator_t elevator;
/*请求队列列表*/
struct request_list rq;
request_fn_proc *request_fn;
merge_request_fn *back_merge_fn;
merge_request_fn *front_merge_fn;
merge_requests_fn *merge_requests_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unplug_fn *unplug_fn;
merge_bvec_fn *merge_bvec_fn;
activity_fn *activity_fn;
/*自动卸载状态*/
struct timer_list unplug_timer;
int unplug_thresh;
unsigned long unplug_delay; /*自动卸载延时*/
struct work_struct unplug_work;
struct backing_dev_info backing_dev_info;
void *queuedata;
void *activity_data;
unsigned long bounce_pfn;
int bounce_gfp;
unsigned long queue_flags;//各种队列标志
/*保护队列结构,避免重入*/
spinlock_t *queue_lock;
/* 请求的核心结构*/
struct kobject kobj;
/*请求的配置*/
unsigned long nr_requests; /* 请求的最大数*/
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned short max_sectors;
unsigned short max_phys_segments;
unsigned short max_hw_segments;
unsigned short hardsect_size;
unsigned int max_segment_size;
unsigned long seg_boundary_mask;
unsigned int dma_alignment;
struct blk_queue_tag *queue_tags;
atomic_t refcnt;
unsigned int in_flight;
/*sg 参数配置*/
unsigned int sg_timeout;
unsigned int sg_reserved_size;
};
请求队列相关的处理函数包括://创建队列时提供了一个自旋锁。
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock); //获得队列中第一个未完成的请求。 struct request *elv_next_request(request_queue_t *q); void end_request(struct request *req, int uptodate);//请求完成 void blk_stop_queue(request_queue_t *queue); //停止请求 void blk_start_queue(request_queue_t *queue); //开始请求 void blk_cleanup_queue(request_queue_t *);//清除请求队列
五:向内核注册和注销一个块设备可使用如下函数:
int register_blkdev(unsigned int major, const char *name); int unregister_blkdev(unsigned int major, const char *name);
linux 块设备驱动 (三)块设备驱动开发
一: 块设备驱动注册与注销
块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:
int register_blkdev(unsigned int major, const char *name);
与register_blkdev()对应的注销函数是unregister_blkdev(),其原型为:
int unregister_blkdev(unsigned int major, const char *name);
这里,传递给register_blkdev()的参数必须与传递给register_blkdev()的参数匹配,否则这个函数返回-EINVAL。
二: 块设备的请求队列操作
标准的请求处理程序能排序请求,并合并相邻的请求,如果一个块设备希望使用标准的请求处理程序,那它必须调用函数blk_init_queue来初始化请求队列。当处理在队列上的请求时,必须持有队列自旋锁。初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
该函数的第1个参数是请求处理函数的指针,第2个参数是控制访问队列权限的自旋锁,这个函数会发生内存分配的行为,故它可能会失败,函数调用成
功时,它返回指向初始化请求队列的指针,否则,返回NULL。这个函数一般在块设备驱动的模块加载函数中调用。清除请求队列
void blk_cleanup_queue(request_queue_t * q);
这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。
提取请求
struct request *elv_next_request(request_queue_t *queue);
上述函数用于返回下一个要处理的请求(由 I/O 调度器决定),如果没有请求则返回NULL。
去除请求
void blkdev_dequeue_request(struct request *req);
上述函数从队列中去除1个请求。如果驱动中同时从同一个队列中操作了多个请求,它必须以这样的方式将它们从队列中去除。
分配“请求队列”
request_queue_t *blk_alloc_queue(int gfp_mask);
对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用上述函数分配1个“请求队列”,并使用如下函数来绑定“请求队列”和“制造请求”函数。
void blk_queue_make_request(request_queue_t * q,
make_request_fn * mfn);
void blk_queue_hardsect_size(request_queue_t *queue,
unsigned short max);
该函数用于告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对界。但是,内核块设备层和驱动之间的通信还是以512字节扇区为单位进行。
步骤:
在块设备驱动的模块加载函数中通常需要完成如下工作:
① 分配、初始化请求队列,绑定请求队列和请求函数。
② 分配、初始化gendisk,给gendisk的major、fops、queue等成
员赋值,最后添加gendisk。
③ 注册块设备驱动。
在块设备驱动的模块卸载函数中通常需要与模块加载函数相反的工作:
① 清除请求队列。
② 删除gendisk和对gendisk的引用。
③ 删除对块设备的引用,注销块设备驱动。
总结:
块设备的I/O操作方式与字符设备存在较大的不同,因而引入了
request_queue、request、bio等一系列数据结构。在整个块设备的I/O操作中,贯穿于始终的就是“请求”,字符设备的I/O操作则是直接进行不绕弯,
块设备的I/O操作会排队和整合。
驱动的任务是处理请求,对请求的排队和整合由I/O调度算法解决,因此,块设备驱动的核心就是请求处理函数或“制造请求”函数。
尽管在块设备驱动中仍然存在block_device_operations结构体及其成员函数,但其不再包含读写一类的成员函数,而只是包含打开、释放及I/O控制等
与具体读写无关的函数。块设备驱动的结构相当复杂的,但幸运的是,块设备不像字符设备那么包罗万象,它通常就是存储设备,而且驱动的主体已经
由Linux内核提供,针对一个特定的硬件系统,驱动工程师所涉及到的工作往往只是编写少量的与硬件直接交互的代码。