目录
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_dev

本文详细介绍了Linux内核中添加磁盘到系统的过程,包括alloc_disk分配通用磁盘描述符,初始化0号分区,以及add_disk函数如何将磁盘及其分区添加到设备树和sysfs中。讲解了磁盘、分区、块设备描述符、bdev_inode之间的关系,以及如何根据设备号查找和管理这些对象。最后,文章总结了磁盘添加的步骤和涉及的主要对象。
最低0.47元/天 解锁文章
3718

被折叠的 条评论
为什么被折叠?



