块设备实现原理_disk_part_iter_init-程序员宅基地

目录

1.    主要数据结构说明    1

2.    添加磁盘和分区到系统    4

2.1磁盘的注册    4

2.1添加磁盘分区到系统    6

3.    请求队列    9

4.    IO调度    11

5.    请求提交    13

6.    块设备与文件系统的关联    16

主要数据结构说明

每个分区打开都会创建一个block_device:

struct block_device

dev_t bd_dev

块设备设备号

int bd_openers

设备打开计数

struct super_block * bd_super

设备如果有挂载文件系统bd_super指向文件系统的草超级块

void * bd_holder

块设备打开的时候指向file

unsigned bd_block_size

块大小

struct hd_struct * bd_part 

指向块设备在磁盘上所属的分区Gendisk->ptbl->part[partno]

int bd_invalidated 

指向分区是否有效,如果无效在打开的时候会rescan分区

struct gendisk * bd_disk 

指向磁盘描述结构gendisk

struct request_queue * bd_queue 

指向块设备对应的请求队列

struct list_head bd_list 

用于将block_device 添加到全局链表all_bdevs

unsigned long bd_private 

指向块设备的私有数据

struct gendisk用于描述一个磁盘

struct gendisk

int major 

磁盘主设备号

int first_minor 

第一个从设备号

int minors 

从设备号数,一个分区占有一个从设备号

char disk_name[DISK_NAME_LEN] 

磁盘名

struct disk_part_tbl __rcu *part_tbl

磁盘分区表,rescan_partitions之后分配

struct hd_struct part0 

主分区

struct block_device_operations *fops 

特定磁盘驱动的操作函数

struct request_queue *queue 

请求队列,一个磁盘一个请求队列而不是一个分区一个

void *private_data 

磁盘底层私有数据

struct hd_struct用于描述分区

struct hd_struct

sector_t start_sect 

分区起始扇区在磁盘上的起始位置

sector_t nr_sects 

分区大小

struct device __dev 

分区作为device添加到系统

int partno

分区号

 

struct request_queue

struct list_head queue_head 

经过调度器选出后等待处理的请求

struct request *last_merge 

上一次合并的请求

struct elevator_queue *elevator 

指向调度器队列

request_fn_proc *request_fn 

请求处理函数

make_request_fn *make_request_fn

根据bio创建请求

struct kobject kobj 

用于将请求队列放到kobject框架中管理

unsigned long nr_requests 

最大请求数量

struct queue_limits limits

请求队列的各种参数限制

 

struct request

struct list_head queuelist

用于链接到请求队列的request_queue ->queue_head

struct request_queue *q

请求所在的请求队列

req_flags_t rq_flags

标识读请求写请求等等各种标识

unsigned int __data_len

请求数据长度

sector_t __sector

请求的起始扇区

struct bio *bio

请求中当前处理的bio

struct bio *biotail

请求中末尾bio

unsigned short nr_phys_segments

在分散聚集操作时候最大的物理段

struct bio标识请求磁盘上一段连续的数据区间,struct bio主要分为两个部分:描述请求磁盘扇区部分;和描述数据缓存段的bvec数组。缓存可能是多个内存段比如几个不连续的页,他们由bvec来管理但是bio请求的磁盘区间是连续的所以只用指定起始扇区和请求数据长度就够了

struct bio

struct bio *bi_next

Bio在请求中组成单项链表

struct block_device *bi_bdev

Bio请求的数据所在的块设备

struct bvec_iter bi_iter

struct bvec_iter {

sector_t bi_sector; //bio请求数据在磁盘上的起始扇区编号

unsigned int bi_size; //剩余I/O数量

unsigned int bi_idx; //当前处理的bi_io_vec数组项

unsigned int bi_bvec_done;// 当前处理的bvec数组项完成的bytes

};

unsigned int bi_phys_segments

传输数据段数目

bio_end_io_t *bi_end_io

Bio传输完成调用函数bi_end_io做清理工作或者唤醒等待的进程

unsigned short bi_vcnt

当前bvec的数量

unsigned short bi_max_vecs

最大bvec数量

struct bio_vec *bi_io_vec

缓存段数组

在函数elevator_init_fn中分配struct elevator_queue,并付给指针 request_queue ->elevator

struct elevator_queue

struct elevator_type *type

struct elevator_type

{

union {

struct elevator_ops sq; //调度器的一组操作函数

struct elevator_mq_ops mq;//多请求队列的一组操作函数

} ops;

bool uses_mq;//是否使用多请求队列

};

void *elevator_data

指向特定调度类型的数据

struct kobject kobj

将调度器放到kobject框架中去管理

unsigned int uses_mq:1

是否使用多请求队列

 

后面讲解调度器的时候将以iosched_deadline为例,所以这里只说明iosched_deadline相关的结构deadline_data

struct deadline_data

struct rb_root sort_list[2]

用于'读' '写'的两棵红黑树。按照请求在磁盘上的位置排序,方便合并临近请求

struct list_head fifo_list[2]

用于'读' '写'的两个队列。按照请求提交的时间先后排序,方便查找到期请求

struct request *next_rq[2]

如果一个请求req被处理,这里存放的是req在sort_list中的下一个请求,因为在sort_list中的下一个请求一定是与req请求的磁盘扇区临近,这样可以节省磁盘寻道的时间

unsigned int starved

饥饿计数

int fifo_expire[2]

请求到期时间间隔

int writes_starved

写请求被读请求阻塞的次数

int front_merges

是否固定向前合并

添加磁盘和分区到系统

2.1磁盘的注册

我们以mmc驱动作为实例来讲解块设备,代码路径:linux-4.12.3/drivers/mmc/core/block.c,磁盘设备注册流程如下:


gendisk分配与初始化

我们主要关注块设备相关的逻辑,不过多讲解mmc相关的实现:

static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
                          struct device *parent,
                          sector_t size,
                          bool default_ro,
                          const char *subname,
                          int area_type)
{   
    struct mmc_blk_data *md;
    int devidx, ret;
    
    ......
    md->disk = alloc_disk(perdev_minors); //分配磁盘通用管理结构gendisk,后面展开讲解
    
    ret = mmc_init_queue(&md->queue, card, &md->lock, subname); //初始化磁盘的请求队列后续详解
    if (ret) 
        goto err_putdisk;
    md->queue.blkdata = md;
    md->disk->major = MMC_BLOCK_MAJOR; //MMC块设备的主设备号,179
    md->disk->first_minor = devidx * perdev_minors;//设置从设备号起始位置
    md->disk->fops = &mmc_bdops; //特定设备相关的一些底层操作函数,
    md->disk->private_data = md;
    md->disk->queue = md->queue.queue; //设置gendisk->queue指向请求队列
    md->parent = parent;
    
    if (mmc_card_mmc(card))
        blk_queue_logical_block_size(md->queue.queue,
                         card->ext_csd.data_sector_size);
    else
        blk_queue_logical_block_size(md->queue.queue, 512);
    set_capacity(md->disk, size); //设置磁盘容量
    ......
}

前面函数alloc_disk(perdev_minors);调用alloc_disk_node来实现gendisk分配,传入参数perdev_minors,这个参数的值是编译时候配置的,配置项CONFIG_MMC_BLOCK_MINORS

struct gendisk *alloc_disk_node(int minors, int node_id)
{
    struct gendisk *disk;

    disk = kzalloc_node(sizeof(struct gendisk), GFP_KERNEL, node_id); //分配gendisk
    if (disk) {
        ......
        disk->node_id = node_id;
// 根据分区数分配disk_part_tbl数据,由指针gendisk->part_tbl指向,这里分配数组只有1个成员
        if (disk_expand_part_tbl(disk, 0)) {
            free_part_stats(&disk->part0);
            kfree(disk);
            return NULL;
        }
// disk->part0为主分区,gendisk->part_tbl->part[0]指向主分区
        disk->part_tbl->part[0] = &disk->part0;        
        disk->minors = minors; //设置从设备号的个数
        rand_initialize_disk(disk);
        disk_to_dev(disk)->class = &block_class; //设置设备类型
        disk_to_dev(disk)->type = &disk_type; //在前面博文“Sysfs实现原理”中有讲解
        device_initialize(disk_to_dev(disk)); //初始化主分区设备gendisk->part0.__dev
    }
    return disk;
}

gendisk添加到系统


blk_alloc_devt:为主分区disk->part0分配设备号

bdi_register_owner:将disk->queue->backing_dev_info添加到全局链表bdi_list

blk_register_region:根据设备号将gendisk添加到哈希表bdev_map中

register_disk:完成gendisk注册的主要工作blkdev_get会扫描分区并将其添加到系统

blk_register_queue:将request_queue->kobj添加到kobject管理框架中去,并且在sys下创建相关目录文件

 

2.1添加磁盘分区到系统

static void register_disk(struct device *parent, struct gendisk *disk)
{
    struct device *ddev = disk_to_dev(disk);
    struct block_device *bdev;
    struct disk_part_iter piter;
    struct hd_struct *part;
    int err;
    
    ddev->parent = parent;
    dev_set_uevent_suppress(ddev, 1); //禁止上报uevent事件等rescan之后统一上报
    
    ......
    bdev = bdget_disk(disk, 0);
    if (!bdev)
        goto exit;

    bdev->bd_invalidated = 1; //设置分区无效标志,blkdev_get中检测到这个标志就会rescan分区
    err = blkdev_get(bdev, FMODE_READ, NULL);//主要工作是rescan分区
    if (err < 0)
        goto exit;

exit:
    dev_set_uevent_suppress(ddev, 0); //允许上报uevent事件
    kobject_uevent(&ddev->kobj, KOBJ_ADD);

    disk_part_iter_init(&piter, disk, 0);
    while ((part = disk_part_iter_next(&piter))) //遍历每一个分区上报uevent
        kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
    disk_part_iter_exit(&piter);
}

前面函数blkdev_get会调用到__blkdev_get:

static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
{
    struct gendisk *disk;
    struct module *owner;
    int ret;
    int partno;
    int perm = 0;

 restart:

    disk = get_gendisk(bdev->bd_dev, &partno);
    if (!disk)
        goto out;

    if (!bdev->bd_openers) {
        bdev->bd_disk = disk;
        bdev->bd_queue = disk->queue;
        bdev->bd_contains = bdev;

        if (!partno) {
            ret = -ENXIO; 
            bdev->bd_part = disk_get_part(disk, partno);
            if (!bdev->bd_part)
                goto out_clear;

            ret = 0;
            if (disk->fops->open) {
                ret = disk->fops->open(bdev, mode); //调用特定设备的open函数
            }

            if (bdev->bd_invalidated) { //前面函数register_disk中有设置bd_invalidated
                if (!ret)
                    rescan_partitions(disk, bdev);//重建分区
                else if (ret == -ENOMEDIUM)
                    invalidate_partitions(disk, bdev);
            }

        ......
}

rescan_partitions是建立磁盘分区的核心函数:

int rescan_partitions(struct gendisk *disk, struct block_device *bdev)
{   
    struct parsed_partitions *state = NULL;
    struct hd_struct *part;
    int p, highest, res;
rescan:
    if (!get_capacity(disk) || !(state = check_partition(disk, bdev)))//检查磁盘存在的分区
        return 0;
    ...... 
    for (p = 1, highest = 0; p < state->limit; p++)
        if (state->parts[p].size)
            highest = p;

    disk_expand_part_tbl(disk, highest);

    for (p = 1; p < state->limit; p++) {//将每一个分区添通过函数add_partition加到系统
        sector_t size, from;

        size = state->parts[p].size;
        if (!size)
            continue;

        from = state->parts[p].from;
        ...... 
        part = add_partition(disk, p, from, size,
                     state->parts[p].flags,
                     &state->parts[p].info);
        ......
    }
    return 0;
}

扫描磁盘分区

结构体parsed_partitions用于暂存磁盘分区信息

struct parsed_partitions {
	……
    struct {
        sector_t from; //起始扇区
        sector_t size; //分区大小
	……
    } *parts; //分区信息数组
	……
};
struct parsed_partitions *
check_partition(struct gendisk *hd, struct block_device *bdev)
{
    struct parsed_partitions *state;
    int i, res, err;

    state = allocate_partitions(hd); //分配state用于暂时管理分区信息
    if (!state)
        return NULL;
    state->pp_buf[0] = '\0';
    state->bdev = bdev;
	……
    while (!res && check_part[i]) {
        memset(state->parts, 0, state->limit * sizeof(state->parts[0]));
        res = check_part[i++](state); //获取分区信息并填充state
        if (res < 0) {
            err = res;
            res = 0;
        }
    }
    ……
}

check_part是一个指针函数数组,不同的分区划分方式需要不同的函数来解决分区信息。

static int (*check_part[])(struct parsed_partitions *) = {
    ......
#ifdef CONFIG_CMDLINE_PARTITION
    cmdline_partition,// 通过命令行获取分区信息
#endif
#ifdef CONFIG_EFI_PARTITION
    efi_partition,  //通过MBR获取分区信息
#endif  
    ......
    NULL
};

添加分区到系统

struct hd_struct *add_partition(struct gendisk *disk, int partno,
                sector_t start, sector_t len, int flags,
                struct partition_meta_info *info)
{
    struct hd_struct *p;
    dev_t devt = MKDEV(0, 0);
    struct device *ddev = disk_to_dev(disk);
    struct device *pdev; 
    struct disk_part_tbl *ptbl;
    const char *dname;
    int err;
    
    err = disk_expand_part_tbl(disk, partno); //扩展磁盘分区
    if (err)
        return ERR_PTR(err);
    ptbl = disk->part_tbl;
    
    p = kzalloc(sizeof(*p), GFP_KERNEL); //分配结构体hd_struct用于描述一个分区
    if (!p)
        return ERR_PTR(-EBUSY);

    pdev = part_to_dev(p);
    
    p->start_sect = start; //分区起始位置
    p->nr_sects = len; //分区大小
    p->partno = partno;//分区编号
    p->policy = get_disk_ro(disk);

    dev_set_name(pdev, "%s%d", dname, partno);

    device_initialize(pdev); //初始化分区对应的hd_struc->__dev
    pdev->class = &block_class; //….
    pdev->type = &part_type;//…
    pdev->parent = ddev;

    err = blk_alloc_devt(p, &devt); //分配分区设备号
    if (err) 
        goto out_free_info;
    pdev->devt = devt;
    
    dev_set_uevent_suppress(pdev, 1); //禁止uevent事件
    err = device_add(pdev); //添加hd_struc->__dev到系统
    if (err) 
        goto out_put;

    dev_set_uevent_suppress(pdev, 0);

    rcu_assign_pointer(ptbl->part[partno], p);
    if (!dev_get_uevent_suppress(ddev)) 
        kobject_uevent(&pdev->kobj, KOBJ_ADD);
    return p;
    ......
}

请求队列

这里仍然以mmc驱动为例:

int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
           spinlock_t *lock, const char *subname)
{
    struct mmc_host *host = card->host;
    u64 limit = BLK_BOUNCE_HIGH;
......
//分配请求队列设置请求处理函数为mmc_request_fn
mq->queue = blk_init_queue(mmc_request_fn, lock);   
if (!mq->queue)
        return -ENOMEM;
    ......
    mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
        host->index, subname ? subname : ""); //后面再请求处理的时候会介绍

    return 0;
}

请求队列处理流程:



blk_alloc_queue_node:分配请求队列结构体request_queue,并初始化相关字段

q->request_fn = rfn:设置请求处理函数,这里实例中函数为mmc_request_fn

blk_init_allocated_queue:请求队列初始化和io调度类的选择和初始化

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
    q->nr_requests = BLKDEV_MAX_RQ;//设置最大请求数为128
    q->make_request_fn = mfn;//用于根据bio创建请求,这里是函数blk_queue_bio
    blk_queue_dma_alignment(q, 511); //dma传送时候的对齐限制
    blk_queue_congestion_threshold(q);
    q->nr_batching = BLK_BATCH_REQ; //
    blk_set_default_limits(&q->limits); //设置一些io请求的最大限制
    blk_queue_bounce_limit(q, BLK_BOUNCE_HIGH); //I/O操作时候能够访问的最高物理内存
}

int elevator_init(struct request_queue *q, char *name)
{   
    struct elevator_type *e = NULL;
    int err;
    ......
    if (!e) {
        if (q->mq_ops) {
            ......
        } else
	// 所有的io调度策略都放在elv_list上,elevator_get根据策略名查找相关策略
            e = elevator_get(CONFIG_DEFAULT_IOSCHED, false); //
            ......
    }

    if (e->uses_mq)
        err = blk_mq_init_sched(q, e);
    else
        err = e->ops.sq.elevator_init_fn(q, e); //分配并初始化 q->elevator
    if (err)
        elevator_put(e);
    return err;
}

IO调度

内核中提供了多种调度策略,这里以iosched_deadline为例来讲解调度策略的具体实现:

static struct elevator_type iosched_deadline = {
    .ops.sq = {
        .elevator_merge_fn =        deadline_merge, //检查bio是否可以合并到现有请求中
        .elevator_merged_fn =       deadline_merged_request,//请求和bio合并之后重启插入红黑树中,因为请求的数据量发生变化了
        .elevator_merge_req_fn =    deadline_merged_requests,//两个请求发生了合并就删除next请求,修改req->fifo_time
        .elevator_dispatch_fn =     deadline_dispatch_requests,//从请求队列中选择下一个需要调度的请求
        .elevator_add_req_fn =      deadline_add_request,//添加请求到请求队列
        .elevator_former_req_fn =   elv_rb_former_request,//返回给定请求的前一个请求
        .elevator_latter_req_fn =   elv_rb_latter_request,//返回给定请求的后一个请求
        .elevator_init_fn =     deadline_init_queue,//队列初始化
        .elevator_exit_fn =     deadline_exit_queue,
    },

    .elevator_attrs = deadline_attrs,
    .elevator_name = "deadline", //调度策略名
    .elevator_owner = THIS_MODULE,
};

static int __init deadline_init(void)
{
    return elv_register(&iosched_deadline); //将iosched_deadline添加到全局链表elv_list中
}

static int deadline_init_queue(struct request_queue *q, struct elevator_type *e)
{
    struct deadline_data *dd; //用于管理请求的各种链表和参数
    struct elevator_queue *eq; //请求队列对象

    eq = elevator_alloc(q, e);  
    if (!eq)
        return -ENOMEM;

    dd = kzalloc_node(sizeof(*dd), GFP_KERNEL, q->node);
    if (!dd) {
        kobject_put(&eq->kobj);
        return -ENOMEM;
    }
    eq->elevator_data = dd; //

    INIT_LIST_HEAD(&dd->fifo_list[READ]);
    INIT_LIST_HEAD(&dd->fifo_list[WRITE]);
    dd->sort_list[READ] = RB_ROOT; //用于管理读请求的红黑树
    dd->sort_list[WRITE] = RB_ROOT;// 用于管理写请求的红黑树
    dd->fifo_expire[READ] = read_expire; //请求在提交后到被调度的截止时间,0.5s
    dd->fifo_expire[WRITE] = write_expire;// 请求在提交后到被调度的截止时间,5s
    dd->writes_starved = writes_starved;//在写请求被调度之前最多连续发起几次读调度,默认为2
    dd->front_merges = 1; //向前合并
    dd->fifo_batch = fifo_batch;//一次处理请求的数量

    spin_lock_irq(q->queue_lock);
    q->elevator = eq; //让请求队列的q->elevator指向elevator_queue
    spin_unlock_irq(q->queue_lock);
    return 0;
}
static void
deadline_add_request(struct request_queue *q, struct request *rq)
{
    struct deadline_data *dd = q->elevator->elevator_data;
    const int data_dir = rq_data_dir(rq);
    deadline_add_rq_rb(dd, rq); //根据请求数据在磁盘上的位置将请求添加到红黑树dd->sort_list中
    rq->fifo_time = jiffies + dd->fifo_expire[data_dir];//更新请求到期时间
    list_add_tail(&rq->queuelist, &dd->fifo_list[data_dir]);//将请求添加到dd->fifo_list
}

static int deadline_dispatch_requests(struct request_queue *q, int force)
{
    struct deadline_data *dd = q->elevator->elevator_data;
    const int reads = !list_empty(&dd->fifo_list[READ]);
    const int writes = !list_empty(&dd->fifo_list[WRITE]);
    struct request *rq;
    int data_dir;
    // next_rq缓存的是最近一次访问的请求在dd->sort_list中的next节点
    if (dd->next_rq[WRITE])
        rq = dd->next_rq[WRITE];
    else
        rq = dd->next_rq[READ];
//如果dd->next_rq有缓存请求并且dd->batching小于dd->fifo_batch就直接选择该请求发起io调度
//因为next_rq存放的请求与最近一次处理请求的数据位置接近,可以减少寻道时间
    if (rq && dd->batching < dd->fifo_batch)
        goto dispatch_request; 
 //优先处理读请求,但是如果已经连续处理两次读请求为避免饿死写请求,就发起一次读请求调度
    if (reads) { 
        if (writes && (dd->starved++ >= dd->writes_starved))
            goto dispatch_writes;

        data_dir = READ;

        goto dispatch_find_request;
    }
    //处理写请求
    if (writes) {
dispatch_writes:
        dd->starved = 0;
        
        data_dir = WRITE;
        
        goto dispatch_find_request;
    }
    return 0;
dispatch_find_request:
    if (deadline_check_fifo(dd, data_dir) || !dd->next_rq[data_dir]) {
        rq = rq_entry_fifo(dd->fifo_list[data_dir].next);//从dd->fifo_list中取出到期的请求
    } else { 
        rq = dd->next_rq[data_dir];
    }
    dd->batching = 0;
dispatch_request:
    dd->batching++;
//1,取出rq 在dd->sort_list中的下一个请求放到dd->next_rq中;2,将当前请求从队列adline_data的各
//删除;3,将请求添加到请求队列的ue ->queue_head准备io调度
    deadline_move_request(dd, rq);
    
    return 1;
}

请求提交

下面讲解请求提交的例子来源于linux-4.12.3/fs/mpage.c


mpage_alloc:分配bio并设置请求的起始扇区bio->bi_iter.bi_sector = first_sector;

bio_add_page:设置bio的数据buffer,buffer可能不是连续的,这些不连续的缓存用一个数组来管理

            bio->bi_io_vec,但是一个bio请求的数据在磁盘上是连续的。

mpage_bio_submit:将bio提交到请求队列。

bio->bi_end_io = mpage_end_io:bio处理之后会调用到,遍历bio的每一个页,设置相关标志比如

            SetPageUptodate。然后通知等待在这个页上的进程。

blk_qc_t generic_make_request(struct bio *bio)
{
    struct bio_list bio_list_on_stack[2];
    blk_qc_t ret = BLK_QC_T_NONE;
//如果当前进程正在处理bio提交的流程,再次提交bio就先放到current->bio_list中, 在请求提交的底层机
//制中可能将bio拆分成几个bio重新提交
if (current->bio_list) {
    bio_list_add(¤t->bio_list[0], bio);
        goto out;
    }

    bio_list_init(&bio_list_on_stack[0]);
    current->bio_list = bio_list_on_stack; //设置current->bio_list用于存放在bio提交流程中再次提交bio
    do {
        struct request_queue *q = bdev_get_queue(bio->bi_bdev);

        if (likely(blk_queue_enter(q, false) == 0)) {//潘丹请求队列是否繁忙
            struct bio_list lower, same;

            bio_list_on_stack[1] = bio_list_on_stack[0];
            bio_list_init(&bio_list_on_stack[0]);
//在第2节请求队列中有设置make_request_fn 为blk_queue_bio,这个函数后面细讲
            ret = q->make_request_fn(q, bio); 

            blk_queue_exit(q);

            bio_list_init(&lower);
            bio_list_init(&same);
            while ((bio = bio_list_pop(&bio_list_on_stack[0])) != NULL)
//将缓存在current->bio_list上的bio取出来,请求队列与当前处理bio相同的放到same,不同的放lower
                if (q == bdev_get_queue(bio->bi_bdev)) 
                    bio_list_add(&same, bio);
                else
                    bio_list_add(&lower, bio);
//将链表lower,same和bio_list_on_stack[1]拼接起来放到bio_list_on_stack[0]上
            bio_list_merge(&bio_list_on_stack[0], &lower);
            bio_list_merge(&bio_list_on_stack[0], &same);
            bio_list_merge(&bio_list_on_stack[0], &bio_list_on_stack[1]);
        } else {
            bio_io_error(bio);
        }
        bio = bio_list_pop(&bio_list_on_stack[0]); //弹出bio提交到请求队列直到bio_list_on_stack[0]为空
    } while (bio);
    current->bio_list = NULL; /* deactivate */

out:
    return ret;
}

static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio)
{
    struct blk_plug *plug;
    int where = ELEVATOR_INSERT_SORT;
    struct request *req, *free;
    unsigned int request_count = 0;
    unsigned int wb_acct;
//检查bio中page是否满足内存区间限制,如果不满足就clone一个bio将page分配在规定的区间
    blk_queue_bounce(q, &bio); 
//检查bio是否超过一些边界限制,如果超过了就将bio拆分,并且将拆分后的bio重新提交
    blk_queue_split(q, &bio, q->bio_split);
//如果请求队列禁止合并就尝试将bio合并到current->plug中的请求中。在进行大量数据读写的时候如果每
//一次bio都获取q->queue_lock进入请求队列处理效率不高,可以在数据读写前blk_start_plug(&plug);,后
//面的bio就会合并到current->plug中,数据请求结束调用blk_finish_plug(&plug);将对current->plug中的bio
//请求统一处理 
    if (!blk_queue_nomerges(q)) {
        if (blk_attempt_plug_merge(q, bio, &request_count, NULL))
            return BLK_QC_T_NONE;
    } else
        request_count = blk_plug_queued_count(q);

    spin_lock_irq(q->queue_lock);
//根据bio->bi_iter.bi_sector和 rq->__sector以及bio和请求的读写方向来判断合并的类型,向前、向后或者
//不能合并
    switch (elv_merge(q, &req, bio)) {
    case ELEVATOR_BACK_MERGE:
        if (!bio_attempt_back_merge(q, req, bio)) //尝试将bio合并到req
            break;
        elv_bio_merged(q, req, bio);
//因为合并bio之后req大小扩展了,再尝试向后一个请求next合并
        free = attempt_back_merge(q, req);
        if (free)
            __blk_put_request(q, free);//释放掉被合并的请求
        else
            elv_merged_request(q, req, ELEVATOR_BACK_MERGE); //将req重新插入红黑树
        goto out_unlock;
    case ELEVATOR_FRONT_MERGE: //向前合并与前面向后合并逻辑相同
        if (!bio_attempt_front_merge(q, req, bio))
            break;
        elv_bio_merged(q, req, bio);
        free = attempt_front_merge(q, req);
        if (free)
            __blk_put_request(q, free);
        else
            elv_merged_request(q, req, ELEVATOR_FRONT_MERGE);
        goto out_unlock;
    default:
        break;
}
get_rq:
    wb_acct = wbt_wait(q->rq_wb, bio, q->queue_lock);
//前面的合并尝试都没有成功,所以需要分配一个req给bio
    req = get_request(q, bio->bi_opf, bio, GFP_NOIO);
    if (IS_ERR(req)) {
        __wbt_done(q->rq_wb, wb_acct);
        bio->bi_error = PTR_ERR(req);
        bio_endio(bio);
        goto out_unlock;
    }

    wbt_track(&req->issue_stat, wb_acct);
//用bio初始化req,日中req->__sector = bio->bi_iter.bi_sector rq->__data_len和rq->__data_len = bio->bi_iter.bi_size对理解请求提交的逻辑比较重要
    blk_init_request_from_bio(req, bio);
//如果设置了current->plug,就将这个请求添加到current->plug中
    plug = current->plug;
    if (plug) {
        if (!request_count || list_empty(&plug->list))
            trace_block_plug(q);
        else {
            struct request *last = list_entry_rq(plug->list.prev);
            if (request_count >= BLK_MAX_REQUEST_COUNT ||
                blk_rq_bytes(last) >= BLK_PLUG_FLUSH_SIZE) {
                blk_flush_plug_list(plug, false);
                trace_block_plug(q);
            }
        }
        list_add_tail(&req->queuelist, &plug->list);
        blk_account_io_start(req, true);
    } else {
        spin_lock_irq(q->queue_lock);
// 如果前面的都没有满足就字节插入红黑树,ELEVATOR_INSERT_SORT
        add_acct_request(q, req, where);
        __blk_run_queue(q); //调用q->request_fn(q)处理请求队列上的请求
out_unlock:
        spin_unlock_irq(q->queue_lock);
    }
    return BLK_QC_T_NONE;
}

前面第2节我们讲了mmc驱动的例子,里面设置q->request_fn为mmc_request_fn,这里再贴一遍代码

int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
           spinlock_t *lock, const char *subname)
{
......
//分配请求队列设置请求处理函数为mmc_request_fn
mq->queue = blk_init_queue(mmc_request_fn, lock);   
if (!mq->queue)
        return -ENOMEM;
......
//请求处理函数mmc_request_fn通过调用函数q->elevator->type->ops.sq.elevator_dispatch_fn从队列上
//选取一个请求,然后唤醒下面内核线程做真正的请求处理
    mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
        host->index, subname ? subname : ""); 

    return 0;
}

块设备与文件系统的关联

struct inode {
    umode_t         i_mode; //文件类型,例如S_IFCHR(字符设备),S_IFBLK(块设备)
    ......
    dev_t           i_rdev; //如果是设备文件表示设备号,普通文件表示文件所在存储设备的设备号
    ......
    const struct file_operations    *i_fop; //文件数据操作函数集
    union {
        struct block_device *i_bdev;//指向块设备对应的block_device
        struct cdev     *i_cdev; //指向字符设备对应的cdev
        ......
    };
    ......
};
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
    inode->i_mode = mode;
if (S_ISCHR(mode)) {
    inode->i_fop = &def_chr_fops;
        inode->i_rdev = rdev;
    } else if (S_ISBLK(mode)) {
        inode->i_fop = &def_blk_fops; //如果是字符设备设置i_fop指向def_chr_fops
        inode->i_rdev = rdev;
    } else if (S_ISFIFO(mode))
    ......
}

通用块设备操作函数集如下:

const struct file_operations def_blk_fops = {
    .open       = blkdev_open,
    .release    = blkdev_close,
    .llseek     = block_llseek,
    .read_iter  = blkdev_read_iter,
    .write_iter = blkdev_write_iter,
    .mmap       = generic_file_mmap,
    .fsync      = blkdev_fsync,
    ......
};



bdget:从伪文件系统"bdev"中获取块设备对应的bdev_inode,其中包含一个block_device和inode

bdev->bd_inode->i_mapping:就是def_blk_aops,包含了直接通过块设备而不是文件系统读写数据的方法
__blkdev_get:从根据设备号从bdev_map中获取gendisk,关联block_device和gendisk,bdev->bd_disk = disk;

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chenying126/article/details/78177358

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan