block设备驱动之内核机制

简介: block设备驱动之内核机制

block子系统初始化

genhd_device_init为Linux内核中块设备驱动程序的整体框架进行了必要的初始化。

image.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和分区表之间关系

image.png

添加一个块设备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。

image.png

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);
}

image.png

设备文件的打开

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;
}

image.png

设备文件打开过程如下:

image.png

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;
}

blk_queue_make_request

提交请求

当文件系统需要与块设备进行数据传输或者控制时,内核需要向设备的request_queue发送请求对象,这个任务由submit_io完成:

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 = &current->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进行合并。

区别如图:

image.png

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;
};

image.png

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))
相关文章
|
存储 缓存 数据管理
深入理解Linux内核I/O机制:探索文件系统与设备驱动(上)
深入理解Linux内核I/O机制:探索文件系统与设备驱动
|
安全 调度 开发者
内核笔记](七)——内核对象(Kernel object)机制
内核笔记](七)——内核对象(Kernel object)机制
725 0
内核笔记](七)——内核对象(Kernel object)机制
|
算法 Linux 网络性能优化
深入理解Linux内核I/O机制:探索文件系统与设备驱动(下)
深入理解Linux内核I/O机制:探索文件系统与设备驱动
|
API 调度
18-FreeRTOS内核控制
18-FreeRTOS内核控制
|
存储 Linux API
Linux内核文件cache管理机制介绍
1 操作系统和文件Cache管理 操作系统是计算机上最为重要的软件,他负责管理计算机的各种硬件资源,并将这些物理资源抽象成各种接口供上层应用使用。所以从程序的角度看,操作系统就好比是一个虚拟机,这个虚拟机不提供各种硬件资源的具体细节,而仅仅提供进程、文件、地址空间以及进程间通信等等逻辑概念。 对于存储设备上的数据,操作系统向应用程序提供的逻辑概念就是“文件”。应用程序要存储或访问数据时
2042 0
|
存储 算法 Linux
Linux 内核的文件 Cache 管理机制详解(上)
Linux 内核的文件 Cache 管理机制详解
495 0
|
算法 Linux API
Linux 内核的文件 Cache 管理机制详解(下)
Linux 内核的文件 Cache 管理机制详解
305 0
|
Linux 芯片
Linux 网络驱动 phy 读写寄存器调试方法
Linux 网络驱动 phy 读写寄存器调试方法
963 0