块设备实现原理_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

智能推荐

Pytorch加载本地自己整理好的cifar10或cifar100数据集,并进行训练_cifar100 pytorch-程序员宅基地

文章浏览阅读6.7k次,点赞11次,收藏49次。Pytorch加载本地自己整理好的cifar10数据集,并进行训练这里写自定义目录标题1.下载数据集2.解压3.复制移动4.修改tv.datasets.CIFAR10源码使用pytorch在线下载cifar10数据集时,经常报错,而且很慢,倘若下载cifar100,那等待时间可想而知了。为了不浪费时间等待,可以将数据集先下载到本地,在自行加载,下面介绍一种修改源码简单的方法。1.下载数据集(随意下载,官网地址:http://www.cs.toronto.edu/~kriz/cifar-10-pytho_cifar100 pytorch

大象机器人开源六轴协作机械臂myCobot 320 手机摄影技术!

这些问题标志着我后续研究的重点方向,需要我继续深入学习AVFoundation框架的使用,特别是其控制摄像头的具体方法,并探索如何将这些控制整合到机械臂的运动调整中,以确保最终拍摄出的视频质量符合预期。尽管目前市场上有许多稳定设备如平衡环架(gimbal)来辅助拍摄,以求达到稳定和多角度的拍摄效果,但在此篇文章中,我将探索一种独特的解决方案:通过将手机安装在机械臂的末端来进行拍摄,以实现那些传统方法难以捕捉的特殊视角。随着人工智能技术的不断进步和普及,AI与机器人的结合无疑将成为未来技术发展的重要趋势。

【计算机毕业设计】springboot党员之家服务系统小程序-程序员宅基地

文章浏览阅读342次,点赞6次,收藏8次。党员之家服务系统小程序的功能已基本实现,主要包括首页、个人中心、学生管理、教师管理、任务信息管理、报名信息管理、任务排名管理、学习资料管理、每日打卡管理、交流信息管理、回复信息管理、积极分子管理、党员信息管理、交流论坛、系统管理等。论文主要从系统的分析与设计 、数据库设计和系统的详细设计等几个方面来进行论述,系统分析与设计部分主要论述了系统的功能分析、系统的设计思路,数据库设计主要论述了数据库的设计,系统的详细设计部分主要论述了几个主要模块的详细设计过程。

Failed to discover available identity versions when contacting http://controller:35357/v3. 错误解决方式_caused by newconnectionerror('<urllib3.connection.-程序员宅基地

文章浏览阅读8.3k次,点赞5次,收藏12次。作为 admin 用户,请求认证令牌,输入如下命令openstack --os-auth-url http://controller:35357/v3 --os-project-domain-name default --os-user-domain-name default --os-project-name admin --os-username admin token issue报错Failed to discover available identity versions whe._caused by newconnectionerror('

学校机房统一批量安装软件的方法来了_教室电脑 一起装软件-程序员宅基地

文章浏览阅读4.5k次。​可以在桌面安装云顷还原系统软件,利用软件中的网络对拷功能部署批量对拷环境,进行电脑教室软件的批量对拷安装与增量对拷安装。​_教室电脑 一起装软件

消息队列(kafka/nsq等)与任务队列(celery/ytask等)到底有什么不同?_任务队列和消息队列-程序员宅基地

文章浏览阅读3.1k次,点赞5次,收藏7次。原文链接:https://www.ikaze.cn/article/43写这篇博文的起因是,我在论坛宣传我开源的新项目YTask(go语言异步任务队列)时,有小伙伴在下面回了一句“为什么不用nsq?”。这使我想起,我在和同事介绍celery时同事说了一句“这不就是kafka吗?”。那么YTask和nsq,celery和kafka?他们之间到底有什么不同呢?下面我结合自己的理解。简单的分析一..._任务队列和消息队列

随便推点

rsync+inotify实现NFS实时同步数据以及压力测试_centos6.5 rsync ino-程序员宅基地

文章浏览阅读1.2k次。1第一步:搭建NFS服务器关于NFS服务器的搭建请参考http://blog.csdn.net/qq_30256711/article/details/78463940第二步:rsync同步实现关于rsync同步请参考http://blog.csdn.net/qq_30256711/article/details/78539342第三步:inotify结合rsync实现实时同步_centos6.5 rsync ino

第11章 软件工程

2.可重复级:建立了基本的项目管理过程和实践来跟踪项目费用,进度,功能特性。5.优化级:加强了定量分析,通过过程质量反馈,新观念,新技术的反馈。1.初始级:软件过程杂乱无章,没有明确定义的步骤,英雄式核心人物。定量管理(CL4):已定量管理的过程的制度化。已管理(CL2):已管理的过程的制度化。已定义(CL3):已定义的过程的制度化。3.已定义级:过程文档化,标准化。4.已管理级:软件过程和产品质量。定量管理的:已度量和控制。优化的:集中于过程改进。已管理的:为项目服务。已定义的:为组织服务。

论文阅读--Search to Distill

标准的知识蒸馏(KD)方法将笨重的教师模型的知识蒸馏到具有预定义架构的学生模型的参数中。然而,神经网络的知识,即网络在给定输入条件下的输出分布,不仅取决于其参数,还取决于其架构。因此,对于KD的一种更广义的方法是将教师的知识蒸馏到学生的参数和架构中。为了实现这一点,我们提出了一种新的基于架构的知识蒸馏(AKD)方法,该方法找到最适合蒸馏给定教师模型的学生模型(对于教师来说是珍珠)。具体来说,我们利用带有我们的KD引导奖励的神经架构搜索(NAS)来搜索最适合给定教师模型的学生架构。

Docker知识点汇总表格总结

Docker知识比较全面的总结,使用表格总结结合示例,一目了然,运维参考以及面试参考皆可

Matplotlib.pyplot库引入失败?_如何导入 matplotlib.pyplot 导入失败-程序员宅基地

文章浏览阅读174次。用Python的人总少不了与Matplotlib接触,可是我们在引入时Python少不了报错。此时,我们就需要在错误中寻找线索。_如何导入 matplotlib.pyplot 导入失败

uni-app,uni-table表格操作_uniapp table-程序员宅基地

文章浏览阅读8.5k次,点赞2次,收藏11次。使用uni-ui UI框架实现表格加分页功能,uni-table 和uni-pagination 组件的使用示例加完整代码。_uniapp table

推荐文章

热门文章

相关标签