《Linux设备驱动程序》第十六章 块设备驱动程序读书笔记
简介
一个块设备驱动程序主要通过传输固定大小的随机数据来访问设备
Linux内核视块设备为与字符设备相异的基本设备类型
Linux块设备驱动程序接口使得块设备可以发挥其最大的功效,但是其复杂程序又是编程者必须面对的一
个问题
一个数据块指的是固定大小的数据,而大小的值由内核确定
数据块的大小通常是4096个字节,但是可以根据体系结构和所使用的文件系统进行改变
与数据块对应的是扇区,它是由底层硬件决定大小的一个块,内核所处理的设备扇区大小是512字节
如果要使用不同的硬件扇区大小,用户必须对内核的扇区数做相应的修改
注册
注册块设备驱动程序
<linux/fs.h>
int register_blkdev(unsigned int major, const char *name);
如果需要的话分配一个动态的主设备号
在/proc/devices中创建一个入口项
int unregister_blkdev(unsigned int major, const char *name);
注册磁盘
struct block_device_operations
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);
int (*media_changed) (struct gendisk *gd);
int (*revalidate_disk) (struct gendisk *gd);
struct module *owner;
gendisk结构
<linux/genhd.h>
struct gendisk
int major;
int first_minor;
int minors;
常取16
char disk_name[32]
显示在/proc/partitions和sysfs中
struct block_device_operations *fops;
struct request_queue *queue;
int flags;
sector_t capacity;
void *private_data;
struct gendisk *alloc_disk(int minors);
void del_gendisk(struct gendisk *gd);
void add_disk(struct gendisk *gd);
块设备操作
open和release函数
对于那些操作实际硬件设备的驱动程序,open和release函数可以设置驱动程序和硬件的状态。这些操作
包括使磁盘开始或者停止旋转,锁住可移动介质的仓门以及分配DMA缓存等
有一些操作能够让块设备在用户空间内被直接打开,这些操作包括给磁盘分区,或者在分区上创建文件
系统,或者运行文件系统检查程序
对可移动介质的支持
调用media_changed函数以检查介质是否被改变
在介质改变后将调用revalideate函数
ioctl函数
高层的块设备子系统在驱动程序获得ioctl命令前,已经截取了大量的命令
实际上在一个现代驱动程序中,许多ioctl命令根本就不用实现
请求处理
每个块设备驱动程序的核心是它的请求函数
request函数介绍
void request(request_queue_t *queue);
当内核需要驱动程序处理读取、写入以及其他对设备的操作时,就会调用该函数
每个设备都有一个请求队列
dev->queue = blk_init_queue(test_request, &dev->lock);
对request函数的调用是与用户空间进程中的动作完全异步的
一个简单的request函数
struct request * elv_next_request(request_queue_t queue);
void end_request(struct request *req, int succeeded);
struct request
sector_t secotr;
unsigned long nr_sectors;
char *buffer
rq_data_dir(struct request *req);
请求队列
一个块设备请求队列可以这样描述:包含块设备I/O请求的序列
请求队列跟踪未完成的块设备的I/O请求
请求队列还实现了插件接口
I/O调度器还负责合并邻近的请求
请求队列拥有request_queue或request_queue_t结构类型
<linux/blkdev.h>
队列的创建与删除
request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
void blk_cleanup_queue(request_queue_t *queue);
队列函数
struct request *elv_next_request(request_queue_t *queue);
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_bounce_limit(request_queue_t *queue, u64 dma_addr);
void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);
void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_segment_size(request_queue_t *queue, unsigned short max);
void blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
void blk_queue_dma_alignment(request_queue_t *queue, int mask);
void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
请求过程剖析
从本质上讲,一个request结构是作为一个bio结构的链表实现的
bio结构
bio结构包含了驱动程序执行请求的全部信息,而不必与初始化这个请求的用户空间的进程相关联
<linux/bio.h>
struct bio
sector_t bi_sector;
unsigned int bi_size;
以字节为单位所需要传输的数据大小
unsigned long bi_flags;
unsigned short bio_phys_segments;
unsigned short bio_hw_segments;
struct bio_vec *bi_io_vec
struct bio_vec
struct page *vb_page;
unsigned int bv_len;
unsigned int bv_offset;
example
int segno;
struct bio_vec *bvec;
bio_for_each_segment(bvec, bio, segno)
{
/* 使用该段进行一定的操作 */
}
char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);
void __bio_kunmap_atomic(char *buffer, enum km_type type):
struct page *bio_page(struct bio *bio);
int bio_offset(struct bio *bio);
int bio_cur_sectors(struct bio *bio);
char *bio_data(struct bio *bio);
char *bio_kmap_irq(struct bio *bio, unsigned long *flags);
void bio_kunmap_irq(char *buffer, unsigned long *flags);
request结构成员
struct request
sector_t hard_sector;
unsigned long hard_nr_sectors;
unsigned int hard_cur_sectors;
struct bio *bio;
char *buffer;
unsigned short nr_phys_segments;
struct list_head queuelist;
屏障请求
在驱动程序接收到请求前,块设备层重新组合了请求以提高I/O性能
出于同样的目的,驱动程序也可以重新组合请求
但在无限制重新组合请求时面临了一个问题:一些应用程序的某些操作,要在另外一些操作开始前完成
2.6版本的块设备层使用屏障(barrier)请求来解决这个问题
如果一个请求被设置了REQ_HARDBARRER标志,那么在其他后续请求被初始化前,它必须被写入驱动器
void blk_queue_ordered(request_queue_t *queue, int flag);
int blk_barrier_rq(sruct request *req);
如果返回一个非零值,该请求是一个屏障请求
不可重试请求
int blk_noretry_request(struct request *req);
请求完成函数
int end_that_request_first(struct request *req, int success, int count);
void end_that_request_last(struct request *req);
example
void end_request(struct request *req, int uptodate)
{
if (!end_that_request(req, uptodate, req->hard_cur_sectors)
{
add_disk_randomness(req->rq_disk);
blkdev_dequeue_request(req);
end_that_request_last(req);
}
}
使用bio
example
struct request *req
struct bio *bio;
rq_for_each_bio(bio, req)
{
/* 使用该bio结构进行一定的操作 */
}
块设备请求和DMA
int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);
clear_bit(QUEUE_FLAG_CLEAR, &queue->queue_flags);
不使用请求队列
typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
void bio_endio(struct bio *bio, unsigned int bytes, int error);
request_queue_t *blk_alloc_queue(int flags);
并未真正地建立一个保存请求的队列
void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);
drivers/block/ll_rw_block.c
其他一些细节
命令预处理
typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req);
该函数要能返回下面的值之一
BLKPREP_OK
BLKPREP_KILL
BLKPREP_DEFER
void blk_queue_prep_rq(request_queue_t *queue, prep_rq_fn *func);
标记命令队列
同时拥有多个活动请求的硬件通常支持某种形式的标记命令队列(Tagged Command Queueing, TCQ)
TCQ只是为每个请求添加一个整数(标记)的技术,这样当驱动器完成它们中的一个请求后,它就可以告
诉驱动程序完成的是哪个
int blk_queue_int_tags(request_queue_t *queue, int depth, struct blk_queue_tag *tags);
int blk_queue_resize_tags(request_queue_t *queue, int new_depth);
int blk_queue_start_tag(request_queue_t *queue, struct request *req);
void blk_queue_end_tag(request_queue_t *queue, struct request *req);
struct request *blk_queue_find_tag(request_queue_t *queue, int tag);
void blk_queue_invalidate_tags(request_queue_t *queue);
========
Linux设备驱动--块设备(一)之概念和框架
基本概念
块设备(blockdevice)
--- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时
的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。
字符设备(Character device)
---是一个顺序的数据流设备,对这种设备的读写是按字符进行的,而且这些字符是连续地形成一个数据
流。他不具备缓冲区,所以对这种设备的读写是实时的。
扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte。(对设备而
言)
块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组
成。(对Linux操作系统而言)
段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。
页、段、块、扇区之间的关系图如下:
块设备驱动整体框架
块设备的应用在Linux中是一个完整的子系统。
在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结
构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求
添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内
核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责
提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备
的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。
在通用块层中,通常用一个bio结构体来对应一个I/O请求。
Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行
访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是
block_device_operations结构体。
当多个请求提交给块设备时,执行效率依赖于请求的顺序。如果所有的请求是同一个方向(如:写数据
),执行效率是最大的。内核在调用块设备驱动程序例程处理请求之前,先收集I/O请求并将请求排序,
然后,将连续扇区操作的多个请求进行合并以提高执行效率(内核算法会自己做,不用你管),对I/O请
求排序的算法称为电梯算法(elevator algorithm)。电梯算法在I/O调度层完成。内核提供了不同类型
的电梯算法,电梯算法有
1 noop(实现简单的FIFO,基本的直接合并与排序),
2 anticipatory(延迟I/O请求,进行临界区的优化排序),
3 Deadline(针对anticipatory缺点进行改善,降低延迟时间),
4 Cfq(均匀分配I/O带宽,公平机制)
PS:其实IO调度层(包括请求合并排序算法)是不需要用户管的,内核已经做好
相关数据结构
block_device: 描述一个分区或整个磁盘对内核的一个块设备实例
gendisk: 描述一个通用硬盘(generic hard disk)对象。
hd_struct: 描述分区应有的分区信息
bio: 描述块数据传送时怎样完成填充或读取块给driver
request: 描述向内核请求一个列表准备做队列处理。
request_queue: 描述内核申请request资源建立请求链表并填写BIO形成队列。
========
Linux设备驱动--块设备(二)之相关结构体
上回最后面介绍了相关数据结构,下面再详细介绍
块设备对象结构 block_device
内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区。如果该结构代表一个分
区,则其成员bd_part指向设备的分区结构。如果该结构代表设备,则其成员bd_disk指向设备的通用硬
盘结构gendisk
当用户打开块设备文件时,内核创建结构block_device实例,设备驱动程序还将创建结构gendisk实例,
分配请求队列并注册结构block_device实例。
块设备对象结构block_device列出如下(在include/linux/fs.h中)
[cpp] view plain copy print?
struct block_device {
dev_t bd_dev; /* not a kdev_t - it's a search key */
struct inode * bd_inode; /* 分区节点 */
struct super_block * bd_super;
int bd_openers;
struct mutex bd_mutex;/* open/close mutex 打开与关闭的互斥量*/
struct semaphore bd_mount_sem; /*挂载操作信号量*/
struct list_head bd_inodes;
void * bd_holder;
int bd_holders;
#ifdef CONFIG_SYSFS
struct list_head bd_holder_list;
#endif
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;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
};
通用硬盘结构 gendisk
结构体gendisk代表了一个通用硬盘(generic hard disk)对象,它存储了一个硬盘的信息,包括请求
队列、分区链表和块设备操作函数集等。块设备驱动程序分配结构gendisk实例,装载分区表,分配请求
队列并填充结构的其他域。
支持分区的块驱动程序必须包含 <linux/genhd.h> 头文件,并声明一个结构gendisk,内核还维护该结
构实例的一个全局链表gendisk_head,通过函数add_gendisk、del_gendisk和get_gendisk维护该链表。
结构gendisk列出如下(在include/linux/genhd.h中):
[cpp] view plain copy print?
struct gendisk {
int major; /* 驱动程序的主设备号 */
int first_minor; /*第一个次设备号*/
int minors; /*次设备号的最大数量,没有分区的设备,此值为1 */
char disk_name[32]; /* 主设备号驱动程序的名字*/
struct hd_struct **part; /* 分区列表,由次设备号排序 */
struct block_device_operations *fops; /*块设备操作函数集*/
struct request_queue *queue; /*请求队列*/
struct blk_scsi_cmd_filter cmd_filter;
void *private_data; /*私有数据*/
sector_t capacity; /* 函数set_capacity设置的容量,以扇区为单位*/
int flags; /*设置驱动器状态的标志,如:可移动介质为
GENHD_FL_REMOVABLE*/
struct device dev; /*从设备驱动模型基类结构device继承*/
struct kobject *holder_dir;
struct kobject *slave_dir;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
unsigned long stamp;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
/*硬盘统计信息,如:读或写的扇区数、融合的扇区数、在请求队列的时间等*/
struct disk_stats dkstats;
#endif
struct work_struct async_notify;
#ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity *integrity; /*用于数据完整性扩展*/
#endif
};
Linux内核提供了一组函数来操作gendisk,主要包括:
分配gendisk
struct gendisk *alloc_disk(int minors);
minors 参数是这个磁盘使用的次设备号的数量,一般也就是磁盘分区的数量,此后minors不能被修改。
增加gendisk
gendisk结构体被分配之后,系统还不能使用这个磁盘,需要调用如下函数来注册这个磁盘设备:
void add_disk(struct gendisk *gd);
特别要注意的是对add_disk()的调用必须发生在驱动程序的初始化工作完成并能响应磁盘的请求之后。
释放gendisk
当不再需要一个磁盘时,应当使用如下函数释放gendisk:
void del_gendisk(struct gendisk *gd);
设置gendisk容量
void set_capacity(struct gendisk *disk, sector_t size);
块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小
是设备的物理属性,扇区是所有块设备的基本单元,块设备 无法对比它还小的单元进行寻址和操作,不
过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇
区也很常见, 比如,很多CD-ROM盘的扇区都是2K大小。不管物理设备的真实扇区大小是多少,内核与块
设备驱动交互的扇区都以512字节为单位。因此,set_capacity()函数也以512字节为单位。
分区结构hd_struct代表了一个分区对象,它存储了一个硬盘的一个分区的信息,驱动程序初始化时,从
硬盘的分区表中提取分区信息,存放在分区结构实例中。
块设备操作函数集结构 block_device_operations
字符设备通过 file_operations 操作结构使它们的操作对系统可用. 一个类似的结构用在块设备上是
struct block_device_operations,
定义在 <linux/fs.h>.
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
就像它们的字符驱动对等体一样工作的函数; 无论何时设备被打开和关闭都调用它们. 一个字符驱动可
能通过启动设备或者锁住门(为可移出的介质)来响应一个 open 调用. 如果你将介质锁入设备, 你当然
应当在 release 方法中解锁.
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);
实现 ioctl 系统调用的方法. 但是, 块层首先解释大量的标准请求; 因此大部分的块驱动 ioctl 方法
相当短.
PS:在block_device_operations中没有实际读或写数据的函数. 在块 I/O 子系统, 这些操作由请求函
数处理
请求结构request
结构request代表了挂起的I/O请求,每个请求用一个结构request实例描述,存放在请求队列链表中,由
电梯算法进行排序,每个请求包含1个或多个结构bio实例
struct request {
//用于挂在请求队列链表的节点,使用函数blkdev_dequeue_request访问它,而不能直接访
问
struct list_head queuelist;
struct list_head donelist; /*用于挂在已完成请求链表的节点*/
struct request_queue *q; /*指向请求队列*/
unsigned int cmd_flags; /*命令标识*/
enum rq_cmd_type_bits cmd_type; /*命令类型*/
/*各种各样的扇区计数*/
/*为提交i/o维护bio横断面的状态信息,hard_*成员是块层内部使用的,驱动程序不应该改变
它们*/
sector_t sector; /*将提交的下一个扇区*/
sector_t hard_sector; /* 将完成的下一个扇区*/
unsigned long nr_sectors; /* 整个请求还需要传送的扇区数*/
unsigned long hard_nr_sectors; /* 将完成的扇区数*/
/*在当前bio中还需要传送的扇区数 */
unsigned int current_nr_sectors;
/*在当前段中将完成的扇区数*/
unsigned int hard_cur_sectors;
struct bio *bio; /*请求中第一个未完成操作的bio*、
struct bio *biotail; /*请求链表中末尾的bio*、
struct hlist_node hash; /*融合 hash */
/* rb_node仅用在I/O调度器中,当请求被移到分发队列中时,
请求将被删除。因此,让completion_data与rb_node分享空间*/
union {
struct rb_node rb_node; /* 排序/查找*/
void *completion_data;
};
request结构体的主要成员包括:
sector_t hard_sector;
unsigned long hard_nr_sectors;
unsigned int hard_cur_sectors;
上述3个成员标识还未完成的扇区,hard_sector是第1个尚未传输的扇区,hard_nr_sectors是尚待完成
的扇区数,hard_cur_sectors是并且当前I/O操作中待完成的扇区数。这些成员只用于内核块设备层,驱
动不应当使用它们。
sector_t sector;
unsigned long nr_sectors;
unsigned int current_nr_sectors;
驱动中会经常与这3个成员打交道,这3个成员在内核和驱动交互中发挥着重大作用。它们以512字节大小
为1个扇区,如果硬件的扇区大小不是512字节,则需要进行相应的调整。例如,如果硬件的扇区大小是
2048字节,则在进行硬件操作之前,需要用4来除起始扇区号。
hard_sector、hard_nr_sectors、hard_cur_sectors与sector、nr_sectors、current_nr_sectors之间
可认为是“副本”关系。
struct bio *bio;
bio是这个请求中包含的bio结构体的链表,驱动中不宜直接存取这个成员,而应该使用后文将介绍的
rq_for_each_bio()。
请求队列结构request_queue
每个块设备都有一个请求队列,每个请求队列单独执行I/O调度,请求队列是由请求结构实例链接成的双
向链表,链表以及整个队列的信息用结构request_queue描述,称为请求队列对象结构或请求队列结构。
它存放了关于挂起请求的信息以及管理请求队列(如:电梯算法)所需要的信息。结构成员request_fn
是来自设备驱动程序的请求处理函数。
请求队列结构request_queue列出如下(在/include/linux/blk_dev.h中)
太长了,此处略,其实也看不懂,- -#
Bio结构
通常1个bio对应1个I/O请求,IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个
bio。
内核中块I/O操作的基本容器由bio结构体表示,定义 在<linux/bio.h>中,该结构体代表了正在现场的
(活动的)以片段(segment)链表形式组织的块I/O操作。一个片段是一小 块连续的内存缓冲区。这样
的好处就是不需要保证单个缓冲区一定要连续。所以通过片段来描述缓冲区,即使一个缓冲区分散在内
存的多个位置上,bio结构体也 能对内核保证I/O操作的执行,这样的就叫做聚散I/O.
bio为通用层的主要数据结构,既描述了磁盘的位置,又描述了内存的位置,是上层内核vfs与下层驱动
的连接纽带
struct bio {
sector_t bi_sector;//该bio结构所要传输的第一个(512字节)扇区:磁盘的位置
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; //销毁方法
};
内存数据段结构bio_vec
结构bio_vec代表了内存中的一个数据段,数据段用页、偏移和长度描
述。I/O需要执行的内存位置用段表示,结构bio指向了一个段的数组。
结构bio_vec列出如下(在include/linux/bio.h中):
struct bio_vec {
struct page *bv_page; /*数据段所在的页*/
unsigned short bv_len; /*数据段的长度*/
unsigned short bv_offset; /*数据段页内偏移*/
};
块设备各个结构体间关系
========
Linux设备驱动--块设备(三)之程序设计
块设备驱动注册与注销
块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其
原型为:
int register_blkdev(unsigned int major, const char *name);
major 参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被显示。 如果major为
0,内核会自动分配一个新的主设备号register_blkdev()函数的返回值就是这个主设备号。如果返回1个
负值,表明发生了一个错误。
与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内核提供,针对一个特定的硬件系统,驱动工程师所涉及到的工作往往只是编写少量的与硬件直
接交互的代码。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#define MAXBUF 1024
#define BLK_MAJOR 253
char blk_dev_name[]="blk_dev";
static char flash[1024*16];
int major;
spinlock_t lock;
struct gendisk *gd;
/*块设备数据传输*/
static void blk_transfer(unsigned long sector, unsigned long nsect, char *buffer, int
write)
{
int read = !write;
if(read)
{
memcpy(buffer, flash+sector*512, nsect*512);
}
else
{
memcpy(flash+sector*512, buffer, nsect*512);
}
}
/*块设备请求处理函数*/
static void blk_request_func(struct request_queue *q)
{
struct request *req;
while((req = elv_next_request(q)) != NULL)
{
if(!blk_fs_request(req))
{
end_request(req, 0);
continue;
}
blk_transfer(req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req));
/*rq_data_dir从request获得数据传送的方向*/
/*req->current_nr_sectors 在当前段中将完成的扇区数*/
/*req->sector 将提交的下一个扇区*/
end_request(req, 1);
}
}
/*strcut block_device_operations*/
static int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long
arg)
{
return -ENOTTY;
}
static int blk_open (struct block_device *dev , fmode_t no)
{
printk("blk mount succeed\n");
return 0;
}
static int blk_release(struct gendisk *gd , fmode_t no)
{
printk("blk umount succeed\n");
return 0;
}
struct block_device_operations blk_ops=
{
.owner = THIS_MODULE,
.open = blk_open,
.release = blk_release,
.ioctl = blk_ioctl,
};
//-----------------------------------------------
static int __init block_module_init(void)
{
if(!register_blkdev(BLK_MAJOR, blk_dev_name)) //注册一个块设备
{
major = BLK_MAJOR;
printk("regiser blk dev succeed\n");
}
else
{
return -EBUSY;
}
gd = alloc_disk(1); //分配一个gendisk,分区是一个
spin_lock_init(&lock); //初始化一个自旋锁
gd->major = major;
gd->first_minor = 0; //第一个次设备号
gd->fops = &blk_ops; //关联操作函数
gd->queue = blk_init_queue(blk_request_func, &lock); //初始化请求队列并关联到gendisk
snprintf(gd->disk_name, 32, "blk%c", 'a');
blk_queue_hardsect_size(gd->queue, 512); //设置扇区大小512字节
set_capacity(gd, 32); //设置块设备大小 512*32=16K
add_disk(gd);
printk("gendisk init success!\n");
return 0;
}
static void __exit block_module_exit(void)
{
blk_cleanup_queue(gd->queue);
del_gendisk(gd);
unregister_blkdev(BLK_MAJOR, blk_dev_name);
printk("block module exit succeed!\n");
}
module_init(block_module_init);
module_exit(block_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gec");
========
linux之块设备驱动
Linux操作系统有两类主要的设备文件:
1.字符设备:以字节为单位进行顺序I/O操作的设备,无需缓冲区且被直接读写。
2.块设备:只能以块单位接收输入返回,对于I/O请求有对应的缓冲区,可以随机访问,块设备的访问位
置必须能够在介质的不同区间前后移动。在块设备中,最小的可寻址单元是扇区,扇区的大小一般是2的
整数倍,常见的大小为512个字节。
上图是一个块设备操作的分层实现图
1.当一个进程被Read时,内核会通过VFS层去读取要读的文件块有没有被cache了,这个cache由一个
buffer_head结构读取。如果要读取的文件块还没有被cache,则就要从文件系统中去读取,通过一个
address_space结构来引用,如果调用文件系统读取函数去读取一个扇区的数据。当它从磁盘读出数据时
,将数据页连入到cache中,当下一次在读取时,就不需要从磁盘去读取了。Read完后将请求初始化成一
个bio结构,并提交给通用块层。
2.它通过submit_bio()去完成,通用层在调用相应的设备IO调度器,这个调度器的调度算法,将这个bio
合并到已经存在的request中,或者创建一个新的request,并将创建的插入到请求队列中,最后就剩下
块设备驱动层来完成后面的所有工作。
内核中块得I/O操作的是由bio结构表示的
点击(此处)折叠或打开
struct bio {
sector_t bi_sector; /*该BIO结构所要传输的第一个(512字节)扇区*/
struct bio *bi_next; /*请求链表*/
struct block_device *bi_bdev;/*相关的块设备*/
unsigned long bi_flags; /*状态和命令的标志*/
unsigned long bi_rw; /*读写*/
unsigned short bi_vcnt; /* bio_vec的偏移个数 */
unsigned short bi_idx; /* bvl_vec */
unsigned short bi_phys_segments;
/* Number of segments after physical and DMA remapping
* hardware coalescing is performed.
*/
unsigned short bi_hw_segments;
unsigned int bi_size; /* residual I/O count */
/*
* To keep track of the max hw size, we account for the
* sizes of the first and last virtually mergeable segments
* in this bio
*/
unsigned int bi_hw_front_size;
unsigned int bi_hw_back_size;
unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
struct bio_vec *bi_io_vec; /* the actual vec list */
bio_end_io_t *bi_end_io;
atomic_t bi_cnt; /* pin count */
void *bi_private;
bio_destructor_t *bi_destructor; /* destructor */
};
此结构体的目的主要是正在执行的I/O操作,其中的bi_io_vecs、bi_vcnt、bi_idx三者都可以相互找到
。bio_vec描述一个特定的片段,片段所在的物理页,块在物理页中的偏移页,整个bio_io_vec结构表示
一个完整的缓冲区。
点击(此处)折叠或打开
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
当一个块被调用内存时,要储存在一个缓冲区,每个缓冲区与一个块对应,所以每一个缓冲区独有一个
对应的描述符,该描述符用buffer_head结构表示
点击(此处)折叠或打开
struct buffer_head {
unsigned long b_state; /* buffer state bitmap (see above) */
struct buffer_head *b_this_page;/* circular list of page's buffers */
struct page *b_page; /* the page this bh is mapped to */
sector_t b_blocknr; /* start block number */
size_t b_size; /* size of mapping */
char *b_data; /* pointer to data within the page */
struct block_device *b_bdev;
bh_end_io_t *b_end_io; /* I/O completion */
void *b_private; /* reserved for b_end_io */
struct list_head b_assoc_buffers; /* associated with another mapping */
struct address_space *b_assoc_map; /* mapping this buffer is
associated with */
atomic_t b_count; /* users using this buffer_head */
};
下面来看看块设备的核心ll_rw_block函数
点击(此处)折叠或打开
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
{
int i;
for (i = 0; i < nr; i ) {
struct buffer_head *bh = bhs[i];
if (!trylock_buffer(bh))
continue;
if (rw == WRITE) {
if (test_clear_buffer_dirty(bh)) {
bh->b_end_io = end_buffer_write_sync;
get_bh(bh);
submit_bh(WRITE, bh);
continue;
}
} else {
if (!buffer_uptodate(bh)) {
bh->b_end_io = end_buffer_read_sync;
get_bh(bh);
submit_bh(rw, bh);
continue;
}
}
unlock_buffer(bh);
}
}
请求块设备驱动将多个物理块读出或者写到块设备,块设备的读写都是在块缓冲区中进行。
点击(此处)折叠或打开
int submit_bh(int rw, struct buffer_head * bh)
{
struct bio *bio;
int ret = 0;
BUG_ON(!buffer_locked(bh));
BUG_ON(!buffer_mapped(bh));
BUG_ON(!bh->b_end_io);
BUG_ON(buffer_delay(bh));
BUG_ON(buffer_unwritten(bh));
/*
* Only clear out a write error when rewriting
*/
if (test_set_buffer_req(bh) && (rw & WRITE))
clear_buffer_write_io_error(bh);
/*
* from here on down, it's all bio -- do the initial mapping,
* submit_bio -> generic_make_request may further map this bio around
*/
bio = bio_alloc(GFP_NOIO, 1);
bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
bio->bi_bdev = bh->b_bdev;
bio->bi_io_vec[0].bv_page = bh->b_page;
bio->bi_io_vec[0].bv_len = bh->b_size;
bio->bi_io_vec[0].bv_offset = bh_offset(bh);
bio->bi_vcnt = 1;
bio->bi_idx = 0;
bio->bi_size = bh->b_size;
bio->bi_end_io = end_bio_bh_io_sync;
bio->bi_private = bh;
bio_get(bio);
submit_bio(rw, bio);
if (bio_flagged(bio, BIO_EOPNOTSUPP))
ret = -EOPNOTSUPP;
bio_put(bio);
return ret;
这个函数主要是调用submit_bio,最终调用generic_make_request去完成将bio传递给驱动去处理。
点击(此处)折叠或打开
void generic_make_request(struct bio *bio)
{
struct bio_list bio_list_on_stack;
if (!generic_make_request_checks(bio))
return;
if (current->bio_list) {
bio_list_add(current->bio_list, bio);
return;
}
BUG_ON(bio->bi_next);
bio_list_init(&bio_list_on_stack);
current->bio_list = &bio_list_on_stack;
do {
struct request_queue *q = bdev_get_queue(bio->bi_bdev);
q->make_request_fn(q, bio);
bio = bio_list_pop(current->bio_list);
} while (bio);
current->bio_list = NULL; /* deactivate */
}
这个函数主要是取出块设备相应的队列中的每个设备,在调用块设备驱动的make_request,如果没有指
定make_request就调用内核默认的__make_request,这个函数主要作用就是调用I/O调度算法将bio合并
,或插入到队列中合适的位置中去。
整个流程为:
那么request_fn指向那个函数呢?在内核中搜搜request_fn,发现
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
return blk_init_queue_node(rfn, lock, -1);
}
EXPORT_SYMBOL(blk_init_queue);
request_queue_t *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
request_queue_t *q = blk_alloc_queue_node(GFP_KERNEL, node_id);
if (!q)
return NULL;
q->node = node_id;
if (blk_init_free_list(q)) {
kmem_cache_free(requestq_cachep, q);
return NULL;
}
if (!lock) {
spin_lock_init(&q->__queue_lock);
lock = &q->__queue_lock;
}
q->request_fn = rfn;
q->prep_rq_fn = NULL;
q->unplug_fn = generic_unplug_device;
q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);
q->queue_lock = lock;
blk_queue_segment_boundary(q, 0xffffffff);
blk_queue_make_request(q, __make_request);
blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
q->sg_reserved_size = INT_MAX;
/*
* all done
*/
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
blk_put_queue(q);
return NULL;
}
原来request_queue的make_request_fn指向__make_request()函数,这个函数复杂I/O调度,并对bio做
些合并等。下面来看看__make_request()做了些什么?
由上面可以分析得出,其中有一个很重要的结构体
struct request {
struct list_head queuelist;//连接这个请求到请求队列.
//追踪请求硬件完成的扇区的成员. 第一个尚未被传送的扇区被存储到 hard_sector, 已经传送的扇区
总数在 ha//rd_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只是一个请求队列,通过可以找到requeue,然后通过bio结构体对应的page读取物理内存
中的信息。
下面看看内核使用的块设备的例子
static int jsfd_init(void)
{
static DEFINE_SPINLOCK(lock);
struct jsflash *jsf;
struct jsfd_part *jdp;
int err;
int i;
if (jsf0.base == 0)
return -ENXIO;
err = -ENOMEM;
//1. 分配gendisk: alloc_disk
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = alloc_disk(1);
if (!disk)
goto out;
jsfd_disk[i] = disk;
}
//2. 设置,分配/设置队列: request_queue_t // 它提供读写能力
if (register_blkdev(JSFD_MAJOR, "jsfd")) {
err = -EIO;
goto out;
}
jsf_queue = blk_init_queue(jsfd_do_request, &lock);
if (!jsf_queue) {
err = -ENOMEM;
unregister_blkdev(JSFD_MAJOR, "jsfd");
goto out;
}
//3. 设置gendisk其他信息 // 它提供属性: 比如容量
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = jsfd_disk[i];
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
jdp = &jsf->dv[i&JSF_PART_MASK];
disk->major = JSFD_MAJOR;
disk->first_minor = i;
sprintf(disk->disk_name, "jsfd%d", i);
disk->fops = &jsfd_fops;
set_capacity(disk, jdp->dsize >> 9);
disk->private_data = jdp;
disk->queue = jsf_queue;
//4 注册: add_disk
add_disk(disk);
set_disk_ro(disk, 1);
}
return 0;
out:
while (i--)
put_disk(jsfd_disk[i]);
return err;
}
========
Linux 块设备驱动 实例
任务:用一片虚拟地址连续的内存空间模拟一个块设备,并为其写一个驱动
/*
* Sample disk driver, from the beginning.
*/
#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 = 25600; /* 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_SIMPLE;
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
/*
* After this much idle time, the driver will simulate a media change.
*/
#define INVALIDATE_DELAY 60*HZ
/*
* The internal representation of our device.
*/
struct sbull_dev {
int size; /* Device size in sectors */
// data 是本程序模拟的块设备,是一片连续的虚拟空间
// 在初始化函数里分配的虚拟地址连续的内存空间
u8 *data; /* The data array */
short users; /* How many users */
short media_change; /* Flag a media change? */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
struct timer_list timer; /* For simulated media changes */
};
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;
}
// 实际的读写操作
// 由于本程序是用一片连续的内存空间模拟块设备
// 所以这里对硬件(内存空间)的读写操作,就是复制内存
// 在具体点,就是下面的memcpy
// 具体的项目,需修改为具体的接口函数
if (write)
// 写
memcpy(dev->data + offset, buffer, nbytes);
else
// 读
memcpy(buffer, dev->data + offset, nbytes);
}
/*The simple form of the request function.*/
static void sbull_request(struct request_queue *q)
{
struct request *req;
// 服务完队列上的所有请求
while ((req = elv_next_request(q)) != NULL) { // elv_next_request :从队列上去一个下来
struct sbull_dev *dev = req->rq_disk->private_data;
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-fs request\n");
end_request(req, 0);
continue;
}
sbull_transfer(dev, req->sector, req->current_nr_sectors,
req->buffer, rq_data_dir(req));
end_request(req, 1);
}
}
/*
* 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_sectors(bio),
buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_sectors(bio);
__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;
// steps through each bio that makes up a request.
// 遍历
__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 sectors_xferred;
struct sbull_dev *dev = q->queuedata;
printk("<0>""in %s\n",__FUNCTION__);
while ((req = elv_next_request(q)) != NULL) {
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-fs request\n");
end_request(req, 0);
continue;
}
sectors_xferred = sbull_xfer_request(dev, req);
__blk_end_request(req,0,sectors_xferred<<9);//add by lht for 2.6.27
}
}
//The direct make request version
static int 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, bio->bi_size, status);
bio_endio(bio, status);
return 0;
}
/*
* Open and close.
*/
static int sbull_open(struct inode *inode, struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
//printk("<0>" "fdfjdlksjfdlkj\n");
del_timer_sync(&dev->timer);
filp->private_data = dev;
spin_lock(&dev->lock);
if (! dev->users)
check_disk_change(inode->i_bdev);
dev->users++;
spin_unlock(&dev->lock);
return 0;
}
static int sbull_release(struct inode *inode, struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
spin_lock(&dev->lock);
dev->users--;
if (!dev->users) {
dev->timer.expires = jiffies + INVALIDATE_DELAY;
add_timer(&dev->timer);
}
spin_unlock(&dev->lock);
return 0;
}
/*
* Look for a (simulated) media change.
*/
int sbull_media_changed(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
return dev->media_change;
}
/*
* Revalidate. WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking
* with open. That needs to be reevaluated.
*/
int sbull_revalidate(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
if (dev->media_change) {
dev->media_change = 0;
memset (dev->data, 0, dev->size);
}
return 0;
}
/*
* The "invalidate" function runs out of the device timer; it sets
* a flag to simulate the removal of the media.
*/
void sbull_invalidate(unsigned long ldev)
{
struct sbull_dev *dev = (struct sbull_dev *) ldev;
spin_lock(&dev->lock);
if (dev->users || !dev->data)
printk (KERN_WARNING "sbull: timer sanity check failed\n");
else
dev->media_change = 1;
spin_unlock(&dev->lock);
}
/*
* The ioctl() implementation
*/
int sbull_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
long size;
struct hd_geometry geo;
struct sbull_dev *dev = filp->private_data;
switch(cmd) {
case HDIO_GETGEO:
/*
* Get geometry: since we are a virtual device, we have to make
* up something plausible. So we claim 16 sectors, four heads,
* and calculate the corresponding number of cylinders. We set the
* start of data at sector four.
*/
//printk("<0>""-------------size=%d\n",size);
/****************for early version************/
//size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
//printk("<0>""-------------size=%d\n",size);
//geo.cylinders = (size & ~0x3f) >> 6;
//geo.cylinders=2000;
//geo.heads = 4;
//geo.sectors = 16;
//geo.sectors=2560;
//geo.start = 0;
//if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
// return -EFAULT;
return 0;
}
return -ENOTTY; /* unknown command */
}
static int sbull_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
unsigned long size;
struct sbull_dev *pdev = bdev->bd_disk->private_data;
size = pdev->size;
geo->cylinders = (size & ~0x3f) >> 6;
geo->heads = 4;
geo->sectors = 16;
geo->start = 0;
return 0;
}
/*
* The device operations structure.
*/
static struct block_device_operations sbull_ops = {
.owner = THIS_MODULE,
.open = sbull_open,
.release = sbull_release,
.media_changed = sbull_media_changed,
.revalidate_disk = sbull_revalidate,
.ioctl = sbull_ioctl,
.getgeo = sbull_getgeo,
};
/*
* Set up our internal device.
*/
// 初始化设备结构体 static struct sbull_dev *Devices中的成员
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 timer which "invalidates" the device.
*/
init_timer(&dev->timer);
dev->timer.data = (unsigned long) dev;
dev->timer.function = sbull_invalidate;
/*
* 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;
default:
printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode);
/* fall into.. */
case RM_SIMPLE:
dev->queue = blk_init_queue(sbull_request, &dev->lock);
if (dev->queue == NULL)
goto out_vfree;
break;
}
blk_queue_hardsect_size(dev->queue, hardsect_size);
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.
*/
// printk("<0>" "add by lht\n");
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 = 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, "sbd");
return -ENOMEM;
}
static void sbull_exit(void)
{
int i;
for (i = 0; i < ndevices; i++) {
struct sbull_dev *dev = Devices + i;
del_timer_sync(&dev->timer);
if (dev->gd) {
del_gendisk(dev->gd);
put_disk(dev->gd);
}
if (dev->queue) {
if (request_mode == RM_NOQUEUE)
// blk_put_queue(dev->queue);
kobject_put(&(dev->queue)->kobj);
else
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);
测试方法:
# Makefile
ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /home/lht/kernel2.6/linux-2.6.14
KERNELDIR ?= /lib/modules/$(shell uname -r)/build M=$(PWD) modules
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := sbull.o
endif
将模块插入内核(2.6.27)
root@linuxidc:/source/workplace/test/sbull_linuxidc# insmod sbull.ko
用lsmod查看模块是否成功插入内核
root@linuxidc:/source/workplace/test/sbull_linuxidc# lsmod | grep sbu
sbull 13452 0
出现上面结果,说明成功了
用ls查看/dev下是否有sbull设备
root@linuxidc:/source/workplace/test/sbull_linuxidc# ls /dev | grep sbu
sbulla
出现上面结果,说明有了,如果没有,用命令
mknod /dev/sbulla b 254 0
手动创建
至此,已经有一个块设备了
下面用fdisk对虚拟块设备分区
root@linuxidc:/source/workplace/test/sbull_linuxidc# fdisk /dev/sbulla
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x14d0973f.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
Command (m for help): n 这里选择n,新建
Command action
e extended
p primary partition (1-4) 这里选p,主分区
p
Partition number (1-4): 1 这里选1,第一个分区
First cylinder (1-400, default 1): 1
Last cylinder, +cylinders or +size{K,M,G} (1-400, default 400):
Using default value 400
Command (m for help): w 这里选w,保存并推出
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
接着将其格式化为ext2
root@linuxidc:/source/workplace/test/sbull_linuxidc# mkfs.ext2 /dev/sbulla1
mke2fs 1.41.3 (12-Oct-2008)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
3200 inodes, 12792 blocks
639 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=13107200
2 block groups
8192 blocks per group, 8192 fragments per group
1600 inodes per group
Superblock backups stored on blocks:
8193
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 29 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
新建一个文件夹,作为此处模拟的块设备的挂载点
root@linuxidc:/source/workplace/test/sbull_linuxidc# ls /mnt/
hgfs initrd
root@linuxidc:/source/workplace/test/sbull_linuxidc# mkdir /mnt/sbulla1
挂载
root@linuxidc:/source/workplace/test/sbull_linuxidc# mount /dev/sbulla1 /mnt/sbulla1
进入目录,新建一个文件,测试一下
root@linuxidc:/source/workplace/test/sbull_linuxidc# cd /mnt/sbulla1/
root@linuxidc:/mnt/sbulla1# ls
lost+found
root@linuxidc:/mnt/sbulla1# echo hi > hello.c
root@linuxidc:/mnt/sbulla1# ls
hello.c lost+found
root@linuxidc:/mnt/sbulla1# cat hello.c
hi
root@linuxidc:/mnt/sbulla1#
========
linux下的块设备驱动(一)
块设备的驱动比字符设备的难,这是因为块设备的驱动和内核的联系进一步增大,但是同时块设备的访
问的几个基本结构和字符还是有相似之处的。
有一句话必须记住:对于存储设备(硬盘~~带有机械的操作)而言,调整读写的顺序作用巨大,因为读
写连续的扇区比分离的扇区快。
但是同时:SD卡和U盘这类设备没有机械上的限制,所以像上面说的进行连续扇区的调整显得就没有必要
了。
先说一下对于硬盘这类设备的简单的驱动。
在linux的内核中,使用gendisk结构来表示一个独立的磁盘设备或者分区。这个结构中包含了磁盘的主
设备号,次设备号以及设备名称。
在国嵌给的历程中,对gendisk这个结构体的填充是在simp_blkdev_init函数中完成的。在对gendisk这
个结构填充之前要对其进行分配空间。具体代码如下:
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
这里的alloc_disk函数是在内核中实现的,它后面的参数1代表的是使用次设备号的数量,这个数量是不
能被修改的。
在分配好了关于gendisk的空间以后就开始对gendisk里面的成员进行填充。具体代码如下:
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME); //宏定义simp_blkdev
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR; //主设备号
simp_blkdev_disk->first_minor = 0; //次设备号
simp_blkdev_disk->fops = &simp_blkdev_fops; //主要结构
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9); //宏定义(16*1024*1024),实际
上就是这个结构体。
在填充好gendisk这个结构以后向内核中 注册这个磁盘设备。具体代码如下:
add_disk(simp_blkdev_disk);
在LDD中说,想内核中注册设备的必须在gendisk这个结构体已经填充好了以后,我们以前的字符设备的
时候也是这么做的,不知道为什么LDD在这里强调了这个。
当不需要一个磁盘的时候要释放gendisk,释放部分的代码在函数simp_blkdev_exit中实现的。具体的释
放代码如下:
del_gendisk(simp_blkdev_disk);
在simp_blkdev_exit中同时还有put_disk(simp_blkdev_disk),这个是用来进行操作gendisk的引用计数
。simp_blkdev_exit还实现了blk_cleanup_queue清除请求队列的这个函数。终于说到请求队列了。
在说等待队列之前先要明确几个概念:
①用户希望对硬盘数据做的事情叫做请求,这个请求和IO请求是一样的,所以IO请求来自于上层。
②每一个IO请求对应内核中的一个bio结构。
③IO调度算法可以将连续的bio(也就是用户的对硬盘数据的相邻簇的请求)合并成一个request。
④多个request就是一个请求队列,这个请求队列的作用就是驱动程序响应用户的需求的队列。
请求队列在国嵌的程序中的simp_blkdev_queue
下面先说一下硬盘这类带有机械的存储设备的驱动。
这类驱动中用户的IO请求对应于硬盘上的簇可能是连续的,可能是不连续的,连续的当然好,如果要是
不连续的,那么IO调度器就会对这些BIO进行排序(例如老谢说的电梯调度算法),合并成一个request
,然后再接收请求,再合并成一个request,多个request之后那么我们的请求队列就形成了,然后就可
以向驱动程序提交了。
在硬盘这类的存储设备中,请求队列的初始化代码如下:
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
老谢说,在这种情况下首先调用的是内核中的make_requst函数,然后再调用自己定义的
simp_blkdev_do_request。追了一下内核代码,会发现make_requst内核代码如下所示:
static int make_request(struct request_queue *q, struct bio * bio)
具体的就不贴了,不过可以知道这个make_request的作用就是使用IO调度器对多个bio的访问顺序进行了
优化调整合并为一个request。也就是在执行完成了这个函数之后才去正式的执行内核的请求队列。
合并后的request其实还是一个结构,这个结构用来表征IO的请求,这个结构在内核中有具体的定义。
请求是一个结构,同时请求队列也是一个结构,这个请求队列在内核中的结构定义如下:
struct request_queue
{
/*
* Together with queue_head for cacheline sharing
*/
struct list_head
queue_head;
struct request
*last_merge;
struct elevator_queue
*elevator;
/*
* the queue request freelist, one for reads and one for writes
*/
struct request_list
rq;
request_fn_proc
*request_fn;
make_request_fn
*make_request_fn;
prep_rq_fn
*prep_rq_fn;
unplug_fn
*unplug_fn;
merge_bvec_fn
*merge_bvec_fn;
prepare_flush_fn
*prepare_flush_fn;
softirq_done_fn
*softirq_done_fn;
rq_timed_out_fn
*rq_timed_out_fn;
dma_drain_needed_fn
*dma_drain_needed;
lld_busy_fn
*lld_busy_fn;
/*
* Dispatch queue sorting
*/
sector_t
end_sector;
struct request
*boundary_rq;
/*
* Auto-unplugging state
*/
struct timer_list
unplug_timer;
int
unplug_thresh;
/* After this many requests */
unsigned long
unplug_delay;
/* After this many jiffies */
struct work_struct
unplug_work;
struct backing_dev_info
backing_dev_info;
/*
* The queue owner gets to use this for whatever they like.
* ll_rw_blk doesn't touch it.
*/
void
*queuedata;
/*
* queue needs bounce pages for pages above this limit
*/
gfp_t
bounce_gfp;
/*
* various queue flags, see QUEUE_* below
*/
unsigned long
queue_flags;
/*
* protects queue structures from reentrancy. ->__queue_lock should
* _never_ be used directly, it is queue private. always use
* ->queue_lock.
*/
spinlock_t
__queue_lock;
spinlock_t
*queue_lock;
/*
* queue kobject
*/
struct kobject kobj;
/*
* queue settings
*/
unsigned long
nr_requests;
/* Max # of requests */
unsigned int
nr_congestion_on;
unsigned int
nr_congestion_off;
unsigned int
nr_batching;
void
*dma_drain_buffer;
unsigned int
dma_drain_size;
unsigned int
dma_pad_mask;
unsigned int
dma_alignment;
struct blk_queue_tag
*queue_tags;
struct list_head
tag_busy_list;
unsigned int
nr_sorted;
unsigned int
in_flight[2];
unsigned int
rq_timeout;
struct timer_list
timeout;
struct list_head
timeout_list;
struct queue_limits
limits;
/*
* sg stuff
*/
unsigned int
sg_timeout;
unsigned int
sg_reserved_size;
int
node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
struct blk_trace
*blk_trace;
#endif
/*
* reserved for flush operations
*/
unsigned int
ordered, next_ordered, ordseq;
int
orderr, ordcolor;
struct request
pre_flush_rq, bar_rq, post_flush_rq;
struct request
*orig_bar_rq;
struct mutex
sysfs_lock;
#if defined(CONFIG_BLK_DEV_BSG)
struct bsg_class_device bsg_dev;
#endif
};
LDD说,请求队列实现了一个插入接口,这个接口允许使用多个IO调度器,大部分IO调度器批量累计IO请
求,并将它们排列为递增或者递减的顺序提交给驱动。
多个连续的bio会合并成为一个request,多个request就成为了一个请求队列,这样bio的是直接的也是
最基本的请求,bio这个结构的定义如下:
struct bio { sector_t bi_sector;
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev;
/* target device */
unsigned long bi_flags; /* status, command, etc */ unsigned long
bi_rw; /* low bits: r/w, high: priority */
unsigned int
bi_vcnt; /* how may bio_vec's */
unsigned int
bi_idx;
/* current index into bio_vec array */
unsigned int
bi_size; /* total size in bytes */
unsigned short
bi_phys_segments; /* segments after physaddr coalesce*/ unsigned
short
bi_hw_segments; /* segments after DMA remapping */ unsigned int
bi_max;
/* max
bio_vecs we can hold used as index into pool */ struct bio_vec *bi_io_vec; /* the actual
vec list */
bio_end_io_t
*bi_end_io; /* bi_end_io (bio) */
atomic_t
bi_cnt;
/* pin count: free when it hits zero */ void
*bi_private;
bio_destructor_t *bi_destructor; /* bi_destructor (bio) */ };
需要注意的是,在bio这个结构中最重要的是bio.vec这个结构。同时还有许多操作bio的宏,这些都是内
核给实现好了的。
请求队列的实现:
首先使用 while ((req = elv_next_request(q)) != NULL)进行循环检测,看看到底传来的IO请求是个
什么。
然后进行读写区域的判定:
if ((req->sector + req->current_nr_sectors) << 9
> SIMP_BLKDEV_BYTES) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": bad request: block=%llu, count=%u\n",
(unsigned long long)req->sector,
req->current_nr_sectors);
//结束本次请求。
end_request(req, 0);
continue;
}
在进行读写区域的判定的时候涉及到了很多linux的编程习惯。
sector表示要访问的第一个扇区。
current_nr_sectors表示预计访问扇区的数目。
这里的左移九位其实就是乘以512。
这样((req->sector + req->current_nr_sectors) << 9就计算出可以预计要访问的扇区的大小。进行了
一次判断。
如果上面的判断没有超出范围,那么就可以对请求的这一部分块设备进行操作了。
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = &simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk);
return 0;
关于gendisk结构的内存分配和成员的填充和硬盘类块设备是一样的。
由于SD卡和U盘属于一类非机械类的设备,所以我们不需要那么复杂的调度算法,也就是不需要把io请求
进行排序,所以我们需要自己为自己分配一个请求队列。具体代码如下:
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
需要注意一下的是在硬盘类块设备的驱动中这个函数的原型是blk_init_queue
(simp_blkdev_do_request, NULL);
在这种情况下其实并没有调用内核的make_request(这个函数的功能上文说过),也就是说我们接下来
要绑定的simp_blkdev_make_request这个函数和make_request是同一级别的函数。这里就没有涉及到算
法调度的问题了。
然后需要进行的工作是:绑定制造请求函数和请求队列。具体代码如下:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
把gendisk的结构成员都添加好了以后就可以执行add_disk(simp_blkdev_disk);这个函数把这块分区添
加进内核了。
整体列出制造请求部分的函数。
//这个条件是在判断当前正在运行的内核版本。
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
//遍历
bio_for_each_segment(bvec, bio, i) {
void *iovec_mem;
switch (bio_rw(bio)) {
case READ:
case READA:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(iovec_mem, dsk_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
case WRITE:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(dsk_mem, iovec_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lu\n",
bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem += bvec->bv_len;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, bio->bi_size, 0);
#else
bio_endio(bio, 0);
#endif
return 0;
}
========
Linux块设备驱动程序原理
1.4 块设备驱动程序
1.4.1 Linux块设备驱动程序原理(1)
顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备。块设备和字符设备最大的区别在于读
写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的
基本单元为字节。从实现角度来看,字符设备的实现比较简单,内核例程和用户态API一一对应,这种映
射关系由字符设备的file_operations维护。块设备接口则相对复杂,读写API没有直接到块设备层,而
是直接到文件系统层,然后再由文件系统层发起读写请求。
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)
;//添加分区
1.4.1 Linux块设备驱动程序原理(2)
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 *);//清除请求队列
1.4.2 简单的块设备驱动程序实例
向内核注册和注销一个块设备可使用如下函数:
int register_blkdev(unsigned int major, const char *name);
int unregister_blkdev(unsigned int major, const char *name);
例1.10 简单的块设备驱动程序实例
代码见光盘\src\1drivermodel\1-10block。核心代码如下所示:
static struct request_queue *Queue;
//自定义块设备结构
static struct simpleblockdevice
{
unsigned long size;
spinlock_t lock;
u8 *data;
struct gendisk *gd;
} Device;
//处理I/O请求
static void simpleblocktransfer(struct simpleblockdevice *dev, unsigned long sector,
unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector*hardsect_size;
unsigned long nbytes = nsect*hardsect_size;
//判断I/O请求是否超出范围
if ((offset + nbytes) > dev->size)
{
printk (KERN_NOTICE "sbd: 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 simpleblockrequest(struct request_queue *q)
{
struct request *req;
//获取下一个请求
while ((req = elv_next_request(q)) != NULL)
{
if (! blk_fs_request(req))
{
printk (KERN_NOTICE "Skip non-CMD request\n");
end_request(req, 0);
continue;
}
simpleblocktransfer(&Device, req->sector, req->current_nr_sectors,
req->buffer, rq_data_dir(req));
end_request(req, 1);
}
}
//简单的块设备ioctl函数
int simpleblockioctl (struct inode *inode, struct file *filp,unsigned int cmd, unsigned
long arg)
{
long size;
struct hd_geometry geo;
switch(cmd)
{
//获取磁盘信息
case HDIO_GETGEO:
size = Device.size*(hardsect_size/KERNEL_SECTOR_SIZE);
geo.cylinders = (size & ~0x3f) >> 6;
geo.heads = 4;
geo.sectors = 16;
geo.start = 4;
if (copy_to_user((void *) arg, &geo, sizeof(geo)))
return -EFAULT;
return 0;
}
return -ENOTTY; /* 未知命令 */
}
//设备操作结构
static struct block_device_operations simpleblockops = {
.owner = THIS_MODULE,
.ioctl = simpleblockioctl
};
static int __init simpleblockinit(void)
{
Device.size = nsectors*hardsect_size;
spin_lock_init(&Device.lock);
Device.data = vmalloc(Device.size);
if (Device.data == NULL)
return -ENOMEM;
//初始化请求队列,配置处理函数为sbd_request
Queue = blk_init_queue(simpleblockrequest, &Device.lock);
if (Queue == NULL)
goto out;
blk_queue_hardsect_size(Queue, hardsect_size);
//注册块设备
major_num = register_blkdev(major_num, "sbd");
if (major_num <= 0) {
printk(KERN_WARNING "sbd: unable to get major number\n");
goto out;
}
Device.gd = alloc_disk(16);
if (! Device.gd)
goto out_unregister;
Device.gd->major = major_num;
Device.gd->first_minor = 0;
Device.gd->fops = &simpleblockops;
Device.gd->private_data = &Device;
strcpy (Device.gd->disk_name, "sbd0");
//配置容量
set_capacity(Device.gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
Device.gd->queue = Queue;
add_disk(Device.gd);
return 0;
out_unregister:
unregister_blkdev(major_num, "sbd");
out:
vfree(Device.data);
return -ENOMEM;
}
static void __exit simpleblockexit(void)
{
del_gendisk(Device.gd);
put_disk(Device.gd);
unregister_blkdev(major_num, "sbd");
blk_cleanup_queue(Queue);
vfree(Device.data);
}
module_init(simpleblockinit);
module_exit(simpleblockexit);
运行结果如下:
[root@/home]#cat /proc/filesystems
nodev sysfs
nodev rootfs
nodev bdev
nodev proc
nodev binfmt_misc
nodev debugfs
nodev securityfs
nodev sockfs
nodev usbfs
nodev pipefs
nodev anon_inodefs
nodev futexfs
nodev tmpfs
nodev inotifyfs
ext3
cramfs
nodev ramfs
msdos
vfat
iso9660
nodev nfs
nodev nfs4
nodev mqueue
nodev rpc_pipefs
[root@/home]#insmod demo.ko
sbd0: unknown partition table
[root@/home]#mknod /dev/sbd b 253 0
[root@/home]#./mkfs.ext3 /dev/sbd
mke2fs 1.40.9 (27-Apr-2008)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
1280 inodes, 5120 blocks
256 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=5242880
1 block group
8192 blocks per group, 8192 fragments per group
1280 inodes per group
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 39 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
[root@/home]#mount -t ext3 /dev/sbd /mnt/u
kjournald starting. Commit interval 5 seconds
EXT3 FS on sbd0, internal journal
EXT3-fs: mounted filesystem with ordered data mode.
[root@/home]#df
Filesystem 1k-blocks Used Available Use% Mounted on
rootfs 2063504 1191136 767548 61% /
/dev/root 2063504 1191136 767548 61% /
/dev/sbd 4955 1063 3636 23% /mnt/u
[root@/home]#cd /mnt/u
[root@/mnt/u]#ls
lost+found
========