block子系统初始化
genhd_device_init为Linux内核中块设备驱动程序的整体框架进行了必要的初始化。
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; }
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 = ¤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))