目录
1. 前言
本专题我们开始学习通用block层的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本文介绍添加磁盘到系统的过程。什么时候分配和添加磁盘到系统?底层驱动发现设备希望当做磁盘设备来使用时或块设备驱动程序需要生成一个逻辑设备作为磁盘使用都会执行磁盘添加。在这个过程中,block子系统为磁盘和分区分配设备号,并将它注册到块设备映射域bdev_map(通用block基础学习笔记 - 1.概述),扫描磁盘分区,建立磁盘和分区、块设备描述符的关系,并注册进sysfs为磁盘和分区建立相关目录。
kernel版本:5.10
平台:arm64
注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“
2. 添加磁盘涉及的主要对象
-
磁盘(gendisk)
通用磁盘简称,抽象出以块为单位的线性排列的完整实体。一个磁盘可以创建多个分区。其中磁盘本身内嵌0号分区,它通过part_tbl指向分区表;通过queue指向了块设备驱动中分配的request_queue。另外gendisk通过全局kobj_map进行管理,每个probe管理一个磁盘设备,通过设备号可以通过bdev_map查找到probe进而通过data找到gendisk。联系下文,通过上图可以看到有两条路径可以找到gendisk
路径1:由于块设备号相同,通过设备文件(从inode)找到主inode,进一步找到block_device, 从而找到gendisk;
路径2:直接通过设备号查找bdev_map散列表获得通用磁盘gendisk -
分区表(disk_part_tbl)
包含了一个数组,描述了各个分区的属性,包括起止扇区,分区名等,其中分区0描述的就是代表磁盘的分区 -
分区(hd_struct)
是特殊的逻辑设备,每个分区覆盖了磁盘的一部分连续块,磁盘也可以理解为一个大的分区,分区编号为0,称为0号分区,其它分区从1开始编号。在对分区进行操作时,其偏移值会转换为对磁盘的偏移值,交付底层块设备执行。启动过程中,检测到磁盘会扫描分区,内存中构建磁盘和分区的关系 -
块设备描述符(block_device)
每个分区(包括0号分区)都有一个块设备描述符,通过它可以联系上层文件系统(通过次inode,它的设备号与主inode相等)和底层的IO子系统(块设备)。每个块设备描述符通过bd_disk指向通用磁盘,通过bd_container指向了父块设备描述符,如分区的块设备描述符通过bd_container指向了磁盘的块设备描述符,所有的block_device形成一个链表;磁盘和分区都可以作为块设备独立使用,它们分别对应一个块设备描述符 -
bdev_inode
是block_device和inode的复合体,它在添加磁盘时被创建,其中block_device就是磁盘或分区对应的设备描述符,inode为磁盘或分区对应的主inode,它属于bdev文件系统,它的设备号与从inode(块设备文件对应的inode)的设备号相同,因此通过设备文件可以找到主inode,进而找到block_device, 找到gendisk
3. alloc_disk
<include/linux/genhd.h>
#define alloc_disk(minors) alloc_disk_node(minors, NUMA_NO_NODE)
#define alloc_disk_node(minors, node_id) \
({ \
static struct lock_class_key __key; \
const char *__name; \
struct gendisk *__disk; \
\
__name = "(gendisk_completion)"#minors"("#node_id")"; \
\
__disk = __alloc_disk_node(minors, node_id); \
\
if (__disk) \
lockdep_init_map(&__disk->lockdep_map, __name, &__key, 0); \
\
__disk; \
})
alloc_disk为宏定义,最终将调用__alloc_disk_node
struct gendisk *__alloc_disk_node(int minors, int node_id)
|--struct gendisk *disk;
| struct disk_part_tbl *ptbl;
|--disk = kzalloc_node(sizeof(struct gendisk), GFP_KERNEL, node_id);
|--disk->part0.dkstats = alloc_percpu(struct disk_stats);
|--init_rwsem(&disk->lookup_sem);
|--disk->node_id = node_id;
| //参数为0表示0号分区,0号分区代表整个磁盘
|--disk_expand_part_tbl(disk, 0)
|--ptbl = rcu_dereference_protected(disk->part_tbl, 1);
|--rcu_assign_pointer(ptbl->part[0], &disk->part0);
|--hd_sects_seq_init(&disk->part0);
|--hd_ref_init(&disk->part0)
\--disk->minors = minors;
rand_initialize_disk(disk);
disk_to_dev(disk)->class = &block_class;
disk_to_dev(disk)->type = &disk_type;
device_initialize(disk_to_dev(disk));
alloc_disk分配通用磁盘描述符,并初始化代表通用磁盘的0号分区的device。一般在块设备驱动的probe中调用,如:对于ufs设备来讲,在scsi/sd.c的sd_probe中调用。del_disk与alloc_disk的作用相反
-
kmalloc_node(sizeof(struct gendisk)
分配创建通用磁盘struct gendisk,如果分配成功将继续下面的操作 -
disk_expand_part_tbl(disk, 0)
创建只支持一个分区的分区表,可动态扩展,这样可以节省资源。后面通过调用rescan_partitionpatioin来重新扫描分区,并再次调用此函数来创建新的分区表,将原分区表复制到新分区表,更新给gendisk->tbl,然后删除原分区表 -
rcu_assign_pointer(ptbl->part[0], &disk->part0);
将磁盘0号分区添加到磁盘分区表,作为分区表的第一个元素, 0号分区并没有额外分配空间 -
rand_initialize_disk(disk)
帮助内核产生随机数 -
disk_to_dev(disk)->class = &block_class;
设置磁盘0号分区device->class为block_class,这个的直接体现是在/sys/block下可以看到disk的name,如:/sys/block/sda,如:对于UFS来讲如下,每个LU对应一个sd节点
注:#define disk_to_dev(disk) (&(disk)->part0.__dev) -
disk_to_dev(disk)->type = &disk_type;设置磁盘0号分区device->type为disk_type
-
device_initialize(disk_to_dev(disk));
将磁盘0号分区device初始化,此为设备模型初始化的常规做法,由于对应的device没有父kobject,因此父对象设置为devices_kset->kobj,这里将type设为disk_type,区别于分区为part_type
4. add_disk
static inline void add_disk(struct gendisk *disk)
|--device_add_disk(NULL, disk, NULL);
|--__device_add_disk(NULL, disk, NULL, true);
|--if (register_queue)
| elevator_init_mq(disk->queue);
|--retval = blk_alloc_devt(&disk->part0, &devt);
| disk->major = MAJOR(devt);
| disk->first_minor = MINOR(devt);
|--disk_alloc_events(disk);
|--struct backing_dev_info *bdi = disk->queue->backing_dev_info;
|--struct device *dev = disk_to_dev(disk);
| dev->devt = devt;
|--if (disk->flags & GENHD_FL_HIDDEN) //对隐藏分区的处理
| disk->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO;
| disk->flags |= GENHD_FL_NO_PART_SCAN;
| else //对正常分区的处理
| struct backing_dev_info *bdi = disk->queue->backing_dev_info;
| struct device *dev = disk_to_dev(disk);
| dev->devt = devt;
| bdi_register(bdi, "%u:%u", MAJOR(devt), MINOR(devt));
| bdi_set_owner(bdi, dev);
| blk_register_region(disk_devt(disk), disk->minors, NULL,exact_match, exact_lock, disk);
| |--kobj_map(bdev_map, devt, range, module, probe, lock, data)
|--register_disk(parent, disk, groups); //此处从上面传下来的parent为何为NULL?
|-if (register_queue)
| blk_register_queue(disk);
|--disk_add_events(disk)
|--blk_integrity_add(disk);
add_disk将磁盘gendisk及其及分区添加到devices树及sysfs中,注意此时只会生成代表磁盘的0号分区的block_device,其它分区的block_device不会创建。其它分区的block_devcie是在代表某个分区的设备文件被打开的时候被创建。add_disk一般在块设备驱动的probe中调用,如:对于ufs设备来讲,在scsi/sd.c的sd_probe中调用
-
blk_alloc_devt:为代表磁盘的分区设备分配设备号,组合后的设备号由devt带回,通过disk->major = MAJOR(devt); 和disk->first_minor = MINOR(devt) 将major和first_minor保存在disk中,从后面的代码dev->devt = devt;可以看出最终还是保存在了代表磁盘的0号分区的dev中
-
bdi_register:注册bdi,每个请求队列都有bdi,提供专属服务,如可以派发flusher线程用于冲刷cache,参考:VFS基础学习笔记 - 7. page cache回写
-
bdi = &disk->queue->backing_dev_info;
bdi_register_dev(bdi, disk_devt(disk));
注册bdi -
blk_register_region:通过调用kobj_map建立磁盘设备号(保存在0号分区dev)和通用磁盘描述符gendisk的映射关系。
bdev_map为全局数组,struct kobj_map *类型,在上一节genhd_device_init函数中分配(通用block基础学习笔记 - 1.概述)。struct kboj_map内部维护了struct probe *probe[255]散列表,每个probe[i]指向了一个probe链表,而每个probe内嵌了gendisk通用磁盘描述符指针(data)和磁盘设备号(dev),且每个probe按照次设备号的升序排列,这样就完成了磁盘设备号与通用磁盘描述符gendisk的映射关系。如下图显示了两个 满足主设备号major%255=2的块设备通过调用blk_register_region后的情况。此函数调用完毕后形成如下图,这样通过磁盘设备号就可以直接检索到对应的gendisk磁盘描述符。
注:probe中dev为代表磁盘的设备号,range为从次设备号开始连续的设备数量,也就是说下图中的每个probe代表的是一个磁盘设备
-
register_disk:将代表整个磁盘的0号分区内嵌设备加入到sysfs中,建立磁盘与分区的关系,并将磁盘添加到系统中,期间bdget会获取整个磁盘对应的struct bdev_inode,它包含了struct block_device和主struct inode
-
blk_register_queue(disk) 在sysfs代表磁盘分区的目录下创建queue子目录,它代表通用磁盘的请求队列,每个gendisk->queue来源于块设备驱动的request_queue
-
sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj, “bdi”);
|- -register_disk
static void register_disk(struct device *parent, struct gendisk *disk,
const struct attribute_group **groups)
| //获取代表磁盘的0号分区设备
|--struct device *ddev = disk_to_dev(disk);
| ddev->parent = parent;
| dev_set_name(ddev, "%s", disk->disk_name);
| // 延迟发送事件到用户分区,直到扫描完所有分区
| dev_set_uevent_suppress(ddev, 1);
| //在sysfs中建立磁盘拓扑关系, 与alloc_disk的device_initialize对应
| device_add(ddev)
|--sysfs_create_link(block_depr, &ddev->kobj,kobject_name(&ddev->kobj));
|--pm_runtime_set_memalloc_noio(ddev, true);
|--disk->part0.holder_dir = kobject_create_and_add("holders", &ddev->kobj);
|--disk->slave_dir = kobject_create_and_add("slaves", &ddev->kobj);
|--disk_scan_partitions(disk);
| | // 磁盘容量为0,如介质移除 或 当前磁盘无分区
| |--if (!get_capacity(disk) || !disk_part_scan_enabled(disk)) return;
| |--blkdev_get_by_dev(disk_devt(disk), FMODE_READ, NULL);
| | |--struct block_device *bdev;
| | |--bdev = bdget(dev);//dev为disk_devt(disk)获取的设备号
| | |--blkdev_get(bdev, FMODE_READ, NULL);
|--dev_set_uevent_suppress(ddev, 0);
| kobject_uevent(&ddev->kobj, KOBJ_ADD);
|--while ((part = disk_part_iter_next(&piter)))
| kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
|--if (disk->queue->backing_dev_info->dev)
sysfs_create_link(&ddev->kobj,&disk->queue->backing_dev_info->dev->kobj,"bdi");
将代表磁盘的0号分区的device注册到sysfs完成了磁盘的注册,并扫描磁盘分区,创建整个磁盘对应的块设备描述符block_device,扫描分区建立磁盘与分区的关系
-
初始化通用磁盘描述符的内嵌设备
ddev->parent = parent;设置代表gendisk第一个分区的device->parent,对于SCSI磁盘为scsi_dev内嵌device
dev_set_name(ddev, disk->disk_name)设置代表gendisk的0分区的device的设备名
dev_set_uevent_suppress(ddev, 1)设置扫描完分区表再发送uevent事件,推迟发送uevent事件 -
device_add(ddev)
将代表gendisk磁盘0号分区的内嵌设备加入到sysfs,以scsi设备为例,由于父device为scsi磁盘内嵌device,因此:
在/sys/…scsi设备…/下创建block/sdx,其中sdx为通用磁盘描述符名,
以UFS为例:/sys/devices/soc/624000.ufshc/host0/target0:0:0/0:0:0:0/block/sda
在/sys/block下创建相应的符号链接:
在/dev目录生成块设备文件
注:alloc_disk时执行了device_initialize,此处执行device_add,真正将代表磁盘设备的device加入到系统中 -
在/sys/block/sdx下创建holders和slaves,分别用于记录哪些设备持有了该磁盘 和 磁盘持有哪些设备
-
disk_scan_partitions
(1) bdget根据磁盘设备号返回(或创建初始化)整个磁盘(0号分区)对应的块设备描述符block_device。
(2)blkdev_get(bdev, FMODE_READ) 根据磁盘块设备号查询块设备散列表,获取通用磁盘描述符,如果首次则扫描分区,建立磁盘与分区的关系,并添加到系统中,期间会创建各个hd_struct -
dev_set_uevent_suppress(ddev, 0) 现在磁盘和分区都已经准备好,消除延迟发送
kobject_uevent(&ddev->kobj, KOBJ_ADD) 发送uevent给用户空间,告知磁盘已添加
kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD) 发送uevent给用户空间,告知分区已添加
|- - -bdget
static struct block_device *bdget(dev_t dev)
|--struct block_device *bdev;
| struct inode *inode;
| //根据磁盘设备号(或分区设备号)获取主inode,如果不存在则创建bdev_inode,并返回inode
|--inode = iget5_locked(blockdev_superblock, hash(dev), bdev_test, bdev_set, &dev);
| //根据次inode获取bdev
|--bdev = &BDEV_I(inode)->bdev;
| //如果是新创建的主inode,则执行初始化
|--if (inode->i_state & I_NEW)
| spin_lock_init(&bdev->bd_size_lock);
| bdev->bd_contains = NULL;
| bdev->bd_super = NULL;
| bdev->bd_inode = inode;
| bdev->bd_part_count = 0;
| inode->i_mode = S_IFBLK;
| inode->i_rdev = dev;//初始化设备号,与次inode的一致
| inode->i_bdev = bdev;
| inode->i_data.a_ops = &def_blk_aops;
| mapping_set_gfp_mask(&inode->i_data, GFP_USER);
| unlock_new_inode(inode);
|-return bdev;
bdget根据设备号返回(或创建初始化)分区对应的块设备描述符block_device
-
iget5_locked
根据磁盘设备号,通过bdev文件按系统查询主inode(块设备文件对应bdev文件系统的inode),主要通过bdev_test回调,他会通过BDEV_I(inode)->bdev.bd_dev == *(dev_t *)data;来判断设备号是否相等。
如果不存在则调用blockdev_superblock->ops->alloc_inode来创建bdev文件系统下的块设备文件的bdev_inode(它包含了主inode和block_device),并返回inode。注意到address_space是内嵌在inode中,因此bdev inode对应的address_space也准备好了。注意到这里通过bdev_set回调函数会将磁盘设备号设置到block_device的bd_dev成员中,VFS的次inode(/dev/block/xxx对应的inode)就是通过设备号与主inode建立关联 -
对上步创建的bdev_inode中的block_dev和inode执行初始化
会初始化inode内嵌的address_space->a_ops为def_blk_aops,它会在读写块设备文件时被调用;
|- - -blkdev_get
//open a block device
static int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
|--if (mode & FMODE_READ)
| perm |= MAY_READ;
|--if (mode & FMODE_WRITE)
| perm |= MAY_WRITE;
|--ret = devcgroup_inode_permission(bdev->bd_inode, perm);
| if (ret)
| goto bdput;
|--ret =__blkdev_get(bdev, mode, holder, 0);
blkdev_get - open a block device。
@bdev: block_device to open
@mode: FMODE_* mask
@holder: exclusive holder identifier
static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder, int for_part)
|--struct block_device *whole = NULL, *claiming = NULL;
| struct gendisk *disk;
restart:
| //根据bdev->bd_dev获取gendisk,并将分区号保存在partno,主要通过查询bdev_map映射域
|--disk = bdev_get_gendisk(bdev, &partno);
| //对非0号分区(不代表整个磁盘),获取整个磁盘分区对应的block_device
|--if (partno)
| whole = bdget_disk(disk, 0);
|--disk_block_events(disk);
| //>>>>>>第一次打开块设备的处理
|--if (!bdev->bd_openers)
| //建立bdev与磁盘描述符的关联
| bdev->bd_disk = disk;
| bdev->bd_contains = bdev;
| bdev->bd_partno = partno;
| //对磁盘的bdev进行初始化(0号分区代表磁盘)
| if (!partno)
| bdev->bd_part = disk_get_part(disk, partno);
| ret = 0;
| if (disk->fops->open)
| ret = disk->fops->open(bdev, mode);
| bd_set_nr_sectors(bdev, get_capacity(disk));
| set_init_blocksize(bdev);
| /* If the device is invalidated, rescan partition
| * if open succeeded or failed with -ENOMEDIUM.
| * The latter is necessary to prevent ghost
| * partitions on a removed medium.
| */
| if (test_bit(GD_NEED_PART_SCAN, &disk->state) && (!ret || ret == -ENOMEDIUM))
| bdev_disk_changed(bdev, ret == -ENOMEDIUM);
| //对分区的bdev进行初始化
| else
| ret = __blkdev_get(whole, mode, NULL, 1);//递归调用__blkdev_get
| bdev->bd_contains = bdgrab(whole);
| bdev->bd_part = disk_get_part(disk, partno);
| bd_set_nr_sectors(bdev, bdev->bd_part->nr_sects);
| set_init_blocksize(bdev);
| if (bdev->bd_bdi == &noop_backing_dev_info)
| bdev->bd_bdi = bdi_get(disk->queue->backing_dev_info);
| //>>>>>> 非第一次打开块设备的处理
| else
| if (bdev->bd_contains == bdev)
| ret = 0;
| if (bdev->bd_disk->fops->open)
| ret = bdev->bd_disk->fops->open(bdev, mode);
| if (test_bit(GD_NEED_PART_SCAN, &disk->state) && (!ret || ret == -ENOMEDIUM))
| bdev_disk_changed(bdev, ret == -ENOMEDIUM);
|--bdev->bd_openers++;
| //递增bdev->bd_part_count,表示磁盘有分区正在使用中,暂时不能重新扫描分区
|--if (for_part)
| bdev->bd_part_count++;
|--if (need_restart)
goto restart;
__blkdev_get主要用于建立磁盘块设备描述符block_device,分区块设备描述符block_device, 磁盘描述符gendisk, 分区hd_struct的关系,并调用磁盘描述符的open回调。对于需要重新扫描分区的则需要解析分区表信息,并保存在磁盘的分区表(disk->part_tbl->part)。从本函数可以看出无论是磁盘还是分区都会创建相应的block_device.
-
第一次打开块设备
无论分区块设备还是磁盘块设备,都建立bdev与磁盘描述符gendisk的关联
(1) 打开整个磁盘块设备
建立磁盘块设备描述符bdev与分区描述符的关联,执行磁盘描述符的open回调。如果符合条件将bdev_disk_changed重新扫描分区
(2) 打开分区对应的块设备
建立分区块设备描述符bdev与分区描述符的关联,执行磁盘描述符的open回调? -
非第一次打开块设备
相关对象的关系已经建立,执行磁盘描述符的open回调 -
bdev_disk_changed扫描分区表并解析,更新到磁盘的分区表中(disk->part_tbl->part),对于bdev_disk_changed调用,可参考源码中的注释
int bdev_disk_changed(struct block_device *bdev, bool invalidate)
|--struct gendisk *disk = bdev->bd_disk
rescan:
| //删除磁盘设备的所有分区
|--blk_drop_partitions(bdev);
| /*
| * Historically we only set the capacity to zero for devices that
| * support partitions (independ of actually having partitions created).
| * Doing that is rather inconsistent, but changing it broke legacy
| * udisks polling for legacy ide-cdrom devices. Use the crude check
| * below to get the sane behavior for most device while not breaking
| * userspace for this particular setup.
| */
|--if (invalidate)
| if (disk_part_scan_enabled(disk) || !(disk->flags & GENHD_FL_REMOVABLE))
| set_capacity(disk, 0);
| else
| if (disk->fops->revalidate_disk)
| disk->fops->revalidate_disk(disk);
| //checks for disk size change and adjusts bdev size
|--check_disk_size_change(disk, bdev, !invalidate);
\--if (get_capacity(disk))
blk_add_partitions(disk, bdev);
else if (invalidate)
//Tell userspace that the media / partition table may have changed
kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE)
bdev_disk_changed扫描分区表并解析,更新到磁盘的分区表中(disk->part_tbl->part)
-
blk_drop_partitions:删除磁盘设备的分区
-
check_disk_size_change:根据get_capacity获取的disk size来校正bdev->bd_inode->i_size
-
blk_add_partitions:主要是通过解析分区表来获取分区信息,并将各分区信息释放给磁盘的分区表(disk->part_tbl->part)
int blk_add_partitions(struct gendisk *disk, struct block_device *bdev)
|--struct parsed_partitions *state;
|--state = check_partition(disk, bdev)
| | //分配分区描述符并初始化,分区数目为gendisk->minors
| |--state = allocate_partitions(hd);
| | state->pp_buf = (char *)__get_free_page(GFP_KERNEL);
| | state->pp_buf[0] = '\0';
| | state->bdev = bdev;
| | disk_name(hd, 0, state->name);
| | snprintf(state->pp_buf, PAGE_SIZE, " %s:", state->name);
| | //循环执行分区表check函数,如果符合此check函数检测条件,则返回1,并推出循环
| |--i = res = err = 0;
| |-- while (!res && check_part[i])
| | memset(state->parts, 0, state->limit * sizeof(state->parts[0]));
| | res = check_part[i++](state);
| | if (res < 0)
| | err = res;
| | res = 0;
| \ --如果已经解析分区表,则打印相关信息, 否则打印无法解析分区表
|--if (disk->queue->limits.zoned == BLK_ZONED_HM)
| pr_warn("%s: ignoring partition table on host managed zoned block device\n",disk->disk_name);
| ret = 0;
| goto out_free_state;
|--if (state->access_beyond_eod) {
| printk(KERN_WARNING "%s: partition table partially beyond EOD, ",disk->disk_name);
| if (disk_unlock_native_capacity(disk))
| goto out_free_state;
| //tell userspace that the media / partition table may have changed
|--kobject_uevent(&disk_to_dev(disk)->kobj, KOBJ_CHANGE);
| //预估分区个数,这样可以预分配分区表
|--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++)
if (!blk_add_partition(disk, bdev, state, p))
goto out_free_state;
blk_add_partitions主要是通过解析分区表来获取分区信息,并将各分区信息释放给磁盘的分区表(disk->part_tbl->part)
-
check_partition:检测分区表格式,如果成功检测则会将分区表信息解析并保存到struct parsed_partition
(1) allocate_partitions主要是分配struct parsed_partitions结构体,struct parsed_partitions用于保存分区表解析后的信息,它内嵌一个结构体数组,用于保存每一个解析的分区,分区的最大数目为gendisk->minors
(2) check_part:check_part为一个数组,保存着各种分区格式的check函数,循环执行check_part数组的每一个check函数,确认分区表是否符合当前check函数检查规则,如果符合,则将分区表解析到上一步分配的struct parsed_partitions结构体中,并返回1。从各个check函数可知它们通过读取0扇区的数据来验证分区格式
(3) 如果已经解析分区表,则打印相关信息, 否则打印无法解析分区表 -
kobject_uevent:tell userspace that the media / partition table may have changed
-
由于初始化时disk->part_tbl->part只有一个0号分区,这里预估分区个数,这样在通过disk_expand_part_tbl扩展分区表时(disk->part_tbl)可以预先知道需要多少个分区,这样就可以分配多少hd_struct,方便扩展
-
blk_add_partition:blk_add_partition会将check_partition时检测的parsed_partitions 结构体的分区表解析信息释放到disk->part_tbl分区表中,期间会调用add_partition来分配hd_struct结构体,指针保存在disk->part_tbl->part[p]中。这里用了一个for循环对所有分区进行添加。
5. 小结
- alloc_disk分配通用磁盘描述符,并初始化代表通用磁盘的0号分区的device。一般在块设备驱动的probe中调用
- add_disk会为磁盘分配设备号,并将磁盘gendisk及其分区添加到devices树及sysfs中,同时为磁盘创建块设备描述符;注意此时只会生成代表磁盘的0号分区的block_device,其它分区的block_device不会创建。其它分区的block_devcie是在代表某个分区的设备文件被打开的时候被创建。add_disk一般在块设备驱动的probe中调用,如:对于ufs设备来讲,在scsi/sd.c的sd_probe中调用。添加磁盘同时也会根据分区表扫描分区,对分区信息进行解析,最终建立gendisk, block_device, hd_struct的关联,见上 2. 添加磁盘涉及的主要对象
参考文档
《存储技术原理分析》
《深入Linux设备驱动程序内核架构》