block子系统初始化
genhd_device_init为Linux内核中块设备驱动程序的整体框架进行了必要的初始化。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SYyEzN2EGO0cDZxYTZwkTOmZ2M0UGM5QDM5cTO1QzYy8CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
static int __init genhd_device_init(void)
{
int error;
// 为block_class指定其dev_kobj
// sysfs_dev_block_kobj是一个指向kobject的指针,devices_init中初始化
block_class.dev_kobj = sysfs_dev_block_kobj;
error = class_register(&block_class);
if (unlikely(error))
return error;
// 初始化devmap,过程类似chardev_init
// 用来把新的block设备添加到这个hash表中
bdev_map = kobj_map_init(base_probe, &block_class_lock);
blk_dev_init();
register_blkdev(BLOCK_EXT_MAJOR, "blkext");
#ifndef CONFIG_SYSFS_DEPRECATED
block_depr = kobject_create_and_add("block", NULL);
#endif
return 0;
}
sysfs_dev_block_kobj的初始化
/dev/block
/dev/char
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
return 0;
}
blk_dev_init初始化(kblockd)
int __init blk_dev_init(void)
{
BUILD_BUG_ON(__REQ_NR_BITS > 8 *
sizeof(((struct request *)0)->cmd_flags));
// 创建block子系统的工作队列
kblockd_workqueue = create_workqueue("kblockd");
if (!kblockd_workqueue)
panic("Failed to create kblockd\n");
// 创建request的缓冲
request_cachep = kmem_cache_create("blkdev_requests",
sizeof(struct request), 0, SLAB_PANIC, NULL);
// 创建request_queue的缓冲
blk_requestq_cachep = kmem_cache_create("blkdev_queue",
sizeof(struct request_queue), 0, SLAB_PANIC, NULL);
return 0;
}
一个block驱动实例 (ramdisk)
#include <linux/module.h> //支持动态添加和卸载模块
#include <linux/kernel.h> //驱动要写入内核,与内核相关的头文件
#include <linux/init.h> //初始化头文件
#include <linux/fs.h> //包含了文件操作相关struct的定义
#include <linux/types.h> //对一些特殊类型的定义
#include <linux/fcntl.h> //定义了文件操作等所用到的相关宏
#include <linux/vmalloc.h> //vmalloc()分配的内存虚拟地址上连续,物理地址不连续
#include <linux/blkdev.h> //采用request方式 块设备驱动程序需要调用blk_init_queue 分配请求队列
#include <linux/hdreg.h> //硬盘参数头文件,定义访问硬盘寄存器端口、状态码和分区表等信息。
#define RAMHD_NAME "ramdisk" //设备名称
#define RAMHD_MAX_DEVICE 2 //最大设备数
#define RAMHD_MAX_PARTITIONS 4 //最大分区数
#define RAMHD_SECTOR_SIZE 512 //扇区大小
#define RAMHD_SECTORS 16 //扇区数 http://www.embedu.org/Column/Column863.htm
#define RAMHD_HEADS 4 //磁头数
#define RAMHD_CYLINDERS 256 //磁道(柱面)数
#define RAMHD_SECTOR_TOTAL (RAMHD_SECTORS * RAMHD_HEADS * RAMHD_CYLINDERS) //总大小
#define RAMHD_SIZE (RAMHD_SECTOR_SIZE * RAMHD_SECTOR_TOTAL) //8MB
typedef struct{
unsigned char *data; //设备数据空间首地址
struct request_queue *queue; //设备请求队列
spinlock_t lock; //互斥自旋锁
struct gendisk *gd; //通用磁盘结构体
} RAMHD_DEV;
static char *sdisk[RAMHD_MAX_DEVICE]; //分配内存的首地址
static RAMHD_DEV *rdev[RAMHD_MAX_DEVICE]; //分配内存的首地址
static dev_t ramhd_major; //主设备号
static int ramhd_space_init(void)
{
int i;
int err = 0;
for(i = 0; i < RAMHD_MAX_DEVICE; i++){
sdisk[i] = vmalloc(RAMHD_SIZE); //申请RAMBLK_SIZE内存 物理地址不连续,虚拟地址连续
if(!sdisk[i]){
err = -ENOMEM; //errno:12 内存不足
return err;
}
memset(sdisk[i], 0, RAMHD_SIZE); //用0来初始化分配的内存空间
}
return err;
}
static void ramhd_space_clean(void)
{
int i;
for(i = 0; i < RAMHD_MAX_DEVICE; i++){
vfree(sdisk[i]); //释放分配的内存
}
}
static int alloc_ramdev(void)
{
int i;
for(i = 0; i < RAMHD_MAX_DEVICE; i++){
rdev[i] = kzalloc(sizeof(RAMHD_DEV), GFP_KERNEL); //向内核申请存放RAMHD_DEV结构体的内存空间
if(!rdev[i])
return -ENOMEM; //errno:12 内存不足
}
return 0;
}
static void clean_ramdev(void)
{
int i;
for(i = 0; i < RAMHD_MAX_DEVICE; i++){
if(rdev[i])
kfree(rdev[i]); //释放分配的内存
}
}
int ramhd_open(struct block_device *bdev, fmode_t mode) //设备打开用到
{
return 0;
}
void ramhd_release(struct gendisk *gd, fmode_t mode) //设备关闭用到
{
}
static int ramhd_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) //IO控制
{
int err;
struct hd_geometry geo; //hd_geometry结构体包含磁头,扇区,柱面等信息
switch(cmd)
{
case HDIO_GETGEO: //获取块设备的物理参数
err = !access_ok(VERIFY_WRITE, arg, sizeof(geo));//检查指针所指向的存储块是否可写
if(err) return -EFAULT; //errno:14 地址错
geo.cylinders = RAMHD_CYLINDERS; //柱面数
geo.heads = RAMHD_HEADS; //磁头数
geo.sectors = RAMHD_SECTORS; //扇区数
geo.start = get_start_sect(bdev); //起始地址
if(copy_to_user((void *)arg, &geo, sizeof(geo)))
//把内核地址&geo指示的数据复制到arg指代的用户空间的地址上
return -EFAULT; //errno:14 地址错
return 0;
}
return -ENOTTY; //errno:25 不适当的IO控制操作
}
static struct block_device_operations ramhd_fops = //用来描述一个块设备的操作函数集
{ .owner = THIS_MODULE,//“加点”这种方式称为指定初始化 源自ISO C99标准 初始化不必严格按照定义时的顺序
.open = ramhd_open,
.release = ramhd_release,
.ioctl = ramhd_ioctl,
};
void ramhd_req_func (struct request_queue *q) //处理传递给这个设备的请求
{
struct request *req; //用来提取req
RAMHD_DEV *pdev;
char *pData;
unsigned long addr, size, start;
req = blk_fetch_request(q); //从块设备队列提取存储的req;
//blk_fetch_request()可以多次调用,如果queue里面没有内容,req将返回NULL
while (req) { //判断当前request是否合法 循环从请求队列中获取下一个要处理的请求
start = blk_rq_pos(req); // 获取当前request结构的起始扇区
pdev = (RAMHD_DEV *)req->rq_disk->private_data; //获得设备结构体指针
pData = pdev->data;//设备地址
addr = (unsigned long)pData + start * RAMHD_SECTOR_SIZE;//计算地址
size = blk_rq_cur_bytes(req); //访问 req 的下一段数据
if (rq_data_dir(req) == READ) //获得数据传送方向.返回0表示从设备读取,否则表示写向设备.
memcpy(req->buffer, (char *)addr, size); //读
else
memcpy((char *)addr, req->buffer, size); //写
if(!__blk_end_request_cur(req, 0)) //这个函数处理完返回false
req = blk_fetch_request(q); //继续取出请求队列中的请求
}
}
int ramhd_init(void) //初始化
{
int i;
ramhd_space_init();
alloc_ramdev();
ramhd_major = register_blkdev(0, RAMHD_NAME); //块设备驱动注册到内核中
//major为0,内核会自动分配一个新的主设备号(ramhd_major )
for(i = 0; i < RAMHD_MAX_DEVICE; i++)
{
rdev[i]->data = sdisk[i];
rdev[i]->gd = alloc_disk(RAMHD_MAX_PARTITIONS);
spin_lock_init(&rdev[i]->lock); //初始化自旋锁
rdev[i]->queue = blk_init_queue(ramhd_req_func, &rdev[i]->lock);//初始化将ramhd_req_func函数与队列绑定
rdev[i]->gd->major = ramhd_major;
rdev[i]->gd->first_minor = i * RAMHD_MAX_PARTITIONS;
rdev[i]->gd->fops = &ramhd_fops; //关联到这个设备的方法集合
rdev[i]->gd->queue = rdev[i]->queue;
rdev[i]->gd->private_data = rdev[i]; //使用这个成员来指向分配的数据
sprintf(rdev[i]->gd->disk_name, "ram_MaoRi_%c", 'a'+i);
set_capacity(rdev[i]->gd, RAMHD_SECTOR_TOTAL);
add_disk(rdev[i]->gd); //向系统中添加这个块设备
}
return 0;
}
void ramhd_exit(void) //模块卸载函数
{
int i;
for(i = 0; i < RAMHD_MAX_DEVICE; i++)
{
del_gendisk(rdev[i]->gd); //删除gendisk结构体
put_disk(rdev[i]->gd); //减少gendisk结构体的引用计数
blk_cleanup_queue(rdev[i]->queue); //清除请求对列
}
unregister_blkdev(ramhd_major,RAMHD_NAME); ; //注销块设备
clean_ramdev();
ramhd_space_clean();
}
module_init(ramhd_init);
module_exit(ramhd_exit);
MODULE_AUTHOR("MaoRi");
MODULE_DESCRIPTION("The Ramdisk implementation with request function");
MODULE_LICENSE("GPL");
Makefile文件如下:
ifneq ($(KERNELRELEASE),)
obj-m += ramdisk.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif
编译:
make -C /root/alicpp/built/kernel/sdks/3.10.0-327.ali2000.alios7.x86_64 M=$PWD
insmode
dmesg
块设备的注册与管理
这个函数很简单:根据name在major_names查找是否有重复,并返回第一个槽位做为主设备号。
虽然这个函数看起来很像往系统中添加一个快设备,但事实上他仅仅是用来跟踪块设备号的使用情况。
int register_blkdev(unsigned int major, const char *name)
{
struct blk_major_name **n, *p;
int index, ret = 0;
mutex_lock(&block_class_lock);
if (major == 0) {
for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
if (major_names[index] == NULL)
break;
}
major = index;
ret = major;
}
p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
if (p == NULL) {
ret = -ENOMEM;
goto out;
}
p->major = major;
strlcpy(p->name, name, sizeof(p->name));
p->next = NULL;
index = major_to_index(major);
for (n = &major_names[index]; *n; n = &(*n)->next) {
if ((*n)->major == major)
break;
}
if (!*n)
*n = p;
else
ret = -EBUSY;
return ret;
}
block_device
内核用block_device表示一个逻辑块设备,这个结构可以表示一个完整的逻辑块设备,也可以表示逻辑块设备上的一个分区。
当块设备对应的设备文件被打开时,内核就会创建一个block_device对象。
block_device在内核中主要用来沟通文件系统和设备驱动程序,起到粘合剂的作用,使得块设备驱动程序很少与之直接打交道。
struct block_device {
dev_t bd_dev;
struct inode * bd_inode;
struct super_block * bd_super;
int bd_openers;
struct mutex bd_mutex;
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;
struct gendisk * bd_disk;
struct list_head bd_list;
unsigned long bd_private;
int bd_fsfreeze_count;
struct mutex bd_fsfreeze_mutex;
};
gendisk
gendisk表示一个实际磁盘设备的抽象,它是直接被设备驱动程序分配和操作。
struct gendisk {
// 主设备号,不要直接使用,通过disk_devt()使用,原因你懂得
// 主设备号也表示了当前设备对应的驱动程序
int major;
int first_minor;
// minors表示当前设备所能包含的最大的从设备的分区数
// minors=1表示不能被分区
int minors;
// 块设备名称,显示与sysfs中
char disk_name[DISK_NAME_LEN];
char *(*devnode)(struct gendisk *gd, mode_t *mode);
// 磁盘的分区信息
struct disk_part_tbl *part_tbl;
// 当前设备上的第一个分区,如果没有分区则表示整个设备
struct hd_struct part0;
// 操作集
const struct block_device_operations *fops;
// 块设备上的IO请求队列
struct request_queue *queue;
void *private_data;
int flags;
struct device *driverfs_dev;
struct kobject *slave_dir;
struct timer_rand_state *random;
atomic_t sync_io;
struct work_struct async_notify;
int node_id;
};
hd_struct
内核使用该结构表示设备上的一个分区
struct hd_struct {
// 分区的起始扇区号
sector_t start_sect;
// 分区的扇区数
sector_t nr_sects;
sector_t alignment_offset;
unsigned int discard_alignment;
// 分区也是一个设备
struct device __dev;
struct kobject *holder_dir;
// 分区号
int policy, partno;
unsigned long stamp;
int in_flight[2];
struct disk_stats *dkstats;
struct rcu_head rcu_head;
};
alloc_disk分配gen_disk
struct gendisk *alloc_disk(int minors)
{
return alloc_disk_node(minors, -1);
}
struct gendisk *alloc_disk_node(int minors, int node_id)
{
struct gendisk *disk;
disk = kmalloc_node(sizeof(struct gendisk),
GFP_KERNEL | __GFP_ZERO, node_id);
if (disk) {
if (!init_part_stats(&disk->part0)) {
kfree(disk);
return NULL;
}
disk->node_id = node_id;
// 动态扩展分区,因为分区是变化的
if (disk_expand_part_tbl(disk, 0)) {
free_part_stats(&disk->part0);
kfree(disk);
return NULL;
}
// gendisk中的part0是hd_struct结构
// 用来标识一个分区,同时标识整个磁盘设备
disk->part_tbl->part[0] = &disk->part0;
// 当前设备所允许的最大分区数
disk->minors = minors;
rand_initialize_disk(disk);
// 初始化设备驱动模型
// #define disk_to_dev(disk) (&(disk)->part0.__dev)
// 可以看到,disk->part0是所属设备的代表
disk_to_dev(disk)->class = &block_class;
disk_to_dev(disk)->type = &disk_type;
device_initialize(disk_to_dev(disk));
INIT_WORK(&disk->async_notify,
media_change_notify_thread);
}
return disk;
}
gendisk和分区表之间关系
添加一个块设备add_disk
内核用设备号来标识一个块设备。通常,主设备号代表驱动程序,次设备号对应设备驱动程序所管理设备上的一个分区。
磁盘上的一个独立分区被看做一个设备,对应/dev/下的一个设备节点。
Linux设备号的类型为dev_t。
假设将一个未分区的gendisk添加到内核时,内核将试图读取设备上的分区信息,对每个有效分区形成一个设备模型中的device对象,并通过device_add添加到系统,但此时这些分区并不会产生block_device对象,直到分区设备被打开。由于要加入的设备没有有效的分区,所以在add_disk时内核无法获取到分区信息,会打印一条:
[97144.328832] ram_MaoRi_a: unknown partition table
add_disk完成以下动作:
1) 把gendisk添加到bdev_map中;
2) 在/dev下生成设备文件节点(device_add完成);
3) 动态生成一个block_device对象,和隶属于"bdev"文件系统的inode节点。
4) 如果有分区,给每个分区生成一个device结构,device_add添加到/dev下面,但是并不生成block_device。
void add_disk(struct gendisk *disk)
{
struct backing_dev_info *bdi;
dev_t devt;
int retval;
disk->flags |= GENHD_FL_UP;
// 构造设备号
retval = blk_alloc_devt(&disk->part0, &devt);
disk_to_dev(disk)->devt = devt;
disk->major = MAJOR(devt);
disk->first_minor = MINOR(devt);
bdi = &disk->queue->backing_dev_info;
bdi_register_dev(bdi, disk_devt(disk));
// 把gendisk添加到bdev_map中
blk_register_region(disk_devt(disk), disk->minors, NULL,
exact_match, exact_lock, disk);
// 设备驱动模型的设备对象的注册
// 在/dev/下面给这个设备生成一个设备节点比如,/dev/hda
register_disk(disk);
// 初始化IO队列在sysfs中的文件
blk_register_queue(disk);
WARN_ON_ONCE(blk_get_queue(disk->queue));
retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
"bdi");
WARN_ON(retval);
}
void register_disk(struct gendisk *disk)
{
// 获取gendisk->part0对应的device对象
struct device *ddev = disk_to_dev(disk);
struct block_device *bdev;
struct disk_part_iter piter;
struct hd_struct *part;
int err;
ddev->parent = disk->driverfs_dev;
dev_set_name(ddev, disk->disk_name);
dev_set_uevent_suppress(ddev, 1);
// 设备驱动模型的设备对象的操作
// 在/dev下添加设备节点文件
if (device_add(ddev))
return;
disk->part0.holder_dir = kobject_create_and_add("holders", &ddev->kobj);
disk->slave_dir = kobject_create_and_add("slaves", &ddev->kobj);
if (!disk_partitionable(disk))
goto exit;
if (!get_capacity(disk))
goto exit;
// 分配block_device结构和inode,关系如图11-5
bdev = bdget_disk(disk, 0);
if (!bdev)
goto exit;
disk->flags |= GENHD_FL_INVALIDATED;
// 初始化bdev和gendisk的关系
// 扫描分区表,如果发现新分区就在/dev/下面生成设备文件节点,并不构造block_device结构
err = blkdev_get(bdev, FMODE_READ);
if (err < 0)
goto exit;
blkdev_put(bdev, FMODE_READ);
exit:
dev_set_uevent_suppress(ddev, 0);
kobject_uevent(&ddev->kobj, KOBJ_ADD);
disk_part_iter_init(&piter, disk, 0);
while ((part = disk_part_iter_next(&piter)))
kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
disk_part_iter_exit(&piter);
}
设备文件的打开
static int blkdev_open(struct inode * inode, struct file * filp)
{
struct block_device *bdev;
int res;
filp->f_flags |= O_LARGEFILE;
if (filp->f_flags & O_NDELAY)
filp->f_mode |= FMODE_NDELAY;
if (filp->f_flags & O_EXCL)
filp->f_mode |= FMODE_EXCL;
if ((filp->f_flags & O_ACCMODE) == 3)
filp->f_mode |= FMODE_WRITE_IOCTL;
// 动态生成block_device和inode
bdev = bd_acquire(inode);
if (bdev == NULL)
return -ENOMEM;
filp->f_mapping = bdev->bd_inode->i_mapping;
// 建立block_device和gendisk的关联
// gendisk一个磁盘设备只有一个,关系如图11-7
res = blkdev_get(bdev, filp->f_mode);
if (res)
return res;
if (filp->f_mode & FMODE_EXCL) {
res = bd_claim(bdev, filp);
if (res)
goto out_blkdev_put;
}
return 0;
out_blkdev_put:
blkdev_put(bdev, filp->f_mode);
return res;
}
设备文件打开过程如下:
blk_init_queue设备IO请求队列的初始化
先过目一下request_queue这个庞大的结构:
struct request_queue
{
// 双向队列表头,所有的request被串在这个list上
struct list_head queue_head;
struct request *last_merge;
struct elevator_queue *elevator;
struct request_list rq;
// 请求处理函数
// 当文件系统需要发起读或写时,会调用这个函数(request方式)
request_fn_proc *request_fn;
// 这个函数为request_queue产生新的request
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unprep_rq_fn *unprep_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;
sector_t end_sector;
struct request *boundary_rq;
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;
gfp_t bounce_gfp;
// 当前请求队列的状态
unsigned long queue_flags;
spinlock_t __queue_lock;
spinlock_t *queue_lock;
struct kobject kobj;
unsigned long nr_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;
unsigned int sg_timeout;
unsigned int sg_reserved_size;
int node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
struct blk_trace *blk_trace;
#endif
/*
* DEPRECATED: please use "for flush operations" members below!
* These members (through orig_bar_rq) are preserved purely
* to maintain kABI.
*/
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;
/* For future extensions */
void *pad;
#ifndef __GENKSYMS__
/*
* for flush operations
*/
unsigned int flush_flags;
unsigned int flush_not_queueable:1;
unsigned int flush_queue_delayed:1;
unsigned int flush_pending_idx:1;
unsigned int flush_running_idx:1;
unsigned long flush_pending_since;
struct list_head flush_queue[2];
struct list_head flush_data_in_flight;
struct request flush_rq;
struct throtl_data *td;
};
队列中存放块设备的IO请求,每个请求是request对象,先看一下request结构:
struct request {
// 用来把request加到blk_plug链表
struct list_head queuelist;
struct call_single_data csd;
// 当前request对应的IO队列
struct request_queue *q;
// 请求除了可以读写之外,还可以是一些控制类的命令
unsigned int cmd_flags;
enum rq_cmd_type_bits cmd_type;
unsigned long atomic_flags;
int cpu;
// 当前请求要传输的数据的总量
unsigned int __data_len;
// 当前请求在磁盘上的起始扇区
sector_t __sector;
// 内核将一个bio信息转存在一个request上,或者是合并到已经有的bio上,request就是通过*bio把这些bio串起来
// rq_for_each_segment可以遍历这些bio
// blk_rq_map_sg为DMA建立分散/聚集映射
struct bio *bio;
struct bio *biotail;
struct hlist_node hash; /* merge hash */
union {
struct rb_node rb_node; /* sort/lookup */
void *completion_data;
};
union {
void *elevator_private[3];
struct {
unsigned int seq;
struct list_head list;
} flush;
};
struct gendisk *rq_disk;
unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
unsigned long long start_time_ns;
unsigned long long io_start_time_ns;
#endif
unsigned short nr_phys_segments;
unsigned short ioprio;
int ref_count;
void *special;
char *buffer;
int tag;
int errors;
unsigned char __cmd[BLK_MAX_CDB];
unsigned char *cmd;
unsigned short cmd_len;
unsigned int extra_len;
unsigned int sense_len;
unsigned int resid_len;
void *sense;
unsigned long deadline;
struct list_head timeout_list;
unsigned int timeout;
int retries;
rq_end_io_fn *end_io;
void *end_io_data;
struct request *next_rq;
void *pad;
};
下面看看如何初始化request_queue
struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
struct request_queue *uninit_q, *q;
// 分配request_queue结构体,并做一些基本的初始化
uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);
// 初始化request_fn等重要成员
q = blk_init_allocated_queue_node(uninit_q, rfn, lock, node_id);
return q;
}
struct request_queue *
blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,
spinlock_t *lock, int node_id)
{
if (!q)
return NULL;
q->node = node_id;
if (blk_init_free_list(q))
return NULL;
// 初始化请求处理函数
q->request_fn = rfn;
q->prep_rq_fn = NULL;
q->unprep_rq_fn = NULL;
q->unplug_fn = generic_unplug_device;
q->queue_flags = QUEUE_FLAG_DEFAULT;
if (lock)
q->queue_lock = lock;
// 提供一个默认的make_request: blk_queue_bio()
blk_queue_make_request(q, blk_queue_bio);
q->sg_reserved_size = INT_MAX;
// 选择一个调度算法
// 通过 cat /sys/block/sda/queue/scheduler 一个设备的io调度算法
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
return NULL;
}
blk_queue_make_request
提交请求
当文件系统需要与块设备进行数据传输或者控制时,内核需要向设备的request_queue发送请求对象,这个任务由submit_io完成:
struct request_queue *
blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,
spinlock_t *lock, int node_id)
{
if (!q)
return NULL;
q->node = node_id;
if (blk_init_free_list(q))
return NULL;
// 初始化请求处理函数
q->request_fn = rfn;
q->prep_rq_fn = NULL;
q->unprep_rq_fn = NULL;
q->unplug_fn = generic_unplug_device;
q->queue_flags = QUEUE_FLAG_DEFAULT;
if (lock)
q->queue_lock = lock;
// 提供一个默认的make_request: blk_queue_bio()
blk_queue_make_request(q, blk_queue_bio);
q->sg_reserved_size = INT_MAX;
// 选择一个调度算法
// 通过 cat /sys/block/sda/queue/scheduler 一个设备的io调度算法
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
return NULL;
}
void submit_bio(int rw, struct bio *bio)
{
int count = bio_sectors(bio);
bio->bi_rw |= rw;
...
...
...
generic_make_request(bio);
}
void generic_make_request(struct bio *bio)
{
// 加入到当前进程的bio_tail
if (current->bio_tail) {
*(current->bio_tail) = bio;
bio->bi_next = NULL;
current->bio_tail = &bio->bi_next;
return;
}
// 遍历bio
do {
current->bio_list = bio->bi_next;
if (bio->bi_next == NULL)
current->bio_tail = ¤t->bio_list;
else
bio->bi_next = NULL;
__generic_make_request(bio);
bio = current->bio_list;
} while (bio);
current->bio_tail = NULL;
}
static inline void __generic_make_request(struct bio *bio)
{
struct request_queue *q;
do {
char b[BDEVNAME_SIZE];
// 通过bio->bi_bdev->gendisk->request_queue获取到bio对应的queue
q = bdev_get_queue(bio->bi_bdev);
// 重新映射bio的偏移量,因为向/dev/sda2写入数据,要转换成/dev/sda的偏移量
// 一个设备只有一个request_queue
blk_partition_remap(bio);
trace_block_bio_queue(q, bio);
// 调用系统默认的make_request或者驱动程序自定义的make_request()
// 这个函数在哪里找呢?blk_init_queue_node的时候设置为blk_queue_bio()
ret = q->make_request_fn(q, bio);
} while (ret);
return;
end_io:
bio_endio(bio, err);
}
// 调用系统默认的make_request
int blk_queue_bio(struct request_queue *q, struct bio *bio)
{
spin_lock_irq(q->queue_lock);
if (bio->bi_rw & (BIO_FLUSH | BIO_FUA)) {
where = ELEVATOR_INSERT_FLUSH;
goto get_rq;
}
if (elv_queue_empty(q))
goto get_rq;
// 调用IO调度算法做merge
el_ret = elv_merge(q, &req, bio);
switch (el_ret) {
case ELEVATOR_BACK_MERGE:
if (!ll_back_merge_fn(q, req, bio))
break;
case ELEVATOR_FRONT_MERGE:
if (!ll_front_merge_fn(q, req, bio))
break;
default:
;
}
get_rq:
// 把bio转换成request
init_request_from_bio(req, bio);
spin_lock_irq(q->queue_lock);
if (queue_should_plug(q) && elv_queue_empty(q))
blk_plug_device(q);
// 把request加入到q中
__elv_add_request(q, req, where, 0);
return 0;
}
可以看到块设备驱动处理请求有两种方式: requst方式和make_request方式。
如果是request方式,bio->__make_request->request。内核会在调用驱动中的request之前调用__make_request,对bio进行合并。
区别如图:
bio结构
bio在文件系统与block子系统之间来回流动,把要读写的带来带去。
通过bio对象,块设备驱动在执行IO的过程中无须和创建这个bio的进程相关联。
bio对象只表示从一个扇区开始若干连续的扇区。
struct bio {
// 本次传输扇区的起始号
sector_t bi_sector;
// 下一个bio
struct bio *bi_next;
// 与请求相关量的块设备,引导submit_bio将请求发送到哪个设备上
struct block_device *bi_bdev;
unsigned long bi_flags;
unsigned long bi_rw;
// bi_io_vec中数组的个数
unsigned short bi_vcnt;
// 当前处理的io_vec的下标
unsigned short bi_idx;
unsigned int bi_phys_segments;
unsigned int bi_size;
unsigned int bi_seg_front_size;
unsigned int bi_seg_back_size;
unsigned int bi_max_vecs;
unsigned int bi_comp_cpu;
atomic_t bi_cnt;
// 指向一个IO向量的数组,每个元素对应一个page
struct bio_vec *bi_io_vec;
bio_end_io_t *bi_end_io;
void *bi_private;
bio_destructor_t *bi_destructor;
struct bio_vec bi_inline_vecs[0];
};
// IO向量结构
struct bio_vec {
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
};
bio在request以链表的形式存在,为什么是链表呢?这是IO调度器合并的结果。
每个bio对应若干个page上一段待写入磁盘的数据。每个page上的一段数据成为segment。
内核提供了一些遍历函数:
bio_for_each_segment(bvl, bio, i): 遍历一个bio上的segment
#define bio_iovec_idx(bio, idx) (&((bio)->bi_io_vec[(idx)]))
#define bio_iovec(bio) bio_iovec_idx((bio), (bio)->bi_idx)
#define bio_page(bio) bio_iovec((bio))->bv_page
#define bio_offset(bio) bio_iovec((bio))->bv_offset
#define bio_segments(bio) ((bio)->bi_vcnt - (bio)->bi_idx)
#define bio_sectors(bio) ((bio)->bi_size >> 9)
#define bio_empty_barrier(bio) (bio_rw_flagged(bio, BIO_RW_BARRIER) && !bio_has_data(bio) && !bio_rw_flagged(bio, BIO_RW_DISCARD))