《存储IO路径》专题:NVME盘加载的过程

在深入了解NVMe(NVM Express)SSD(固态硬盘)在Linux系统的加载过程之前,让我们先听一个引人入胜的故事。

在未来的世界里,时间不再是线性流动的,而是呈现出多维度交织的形态。在这个世界中,数据传输的速度超越了光速,人们可以通过意识直接交流。在这个时代的科技支撑下,一位名叫NVMe的勇士诞生了。

NVMe出生在一个被称为“存储谷”的地方,这里充满了智能存储设备,其中不乏一些强大的对手。然而,NVMe有着一种独特的天赋,那就是他能够与Linux系统进行无缝沟通。

在Linux系统的世界里,NVMe发现了一个被称为“存储栈”的地方。这个地方包含了从硬件层到应用层的所有存储技术。NVMe决定深入探索这个神秘的地方,希望能够找到提升存储效率的秘诀。

经过一番探索,NVMe发现了一个叫做“请求队列”的地方。他发现,这里就像是一个时空隧道,能够将来自应用层的请求瞬间传输到硬件层。NVMe心生敬畏,决定深入研究这个奇妙的地方。

于是,NVMe踏上了新的旅程,他深入学习如何使用请求队列,如何优化存储路径,如何提高数据传输速度。在这个过程中,NVMe逐渐成长为一位存储领域的专家。

最后,NVMe成功地掌握了所有技能,他成为了一名存储大师。他将这个多维度的存储世界连接在一起,让数据的流动变得无比顺畅。

听完故事,不够过瘾的话,咱们结合代码进一步挖掘nvme ssd加载过程:

  1. 设备发现和初始化:
    • 在Linux内核中,NVMe设备的发现和初始化主要由PCI子系统和NVMe子系统共同完成。
    • PCI子系统通过扫描PCI总线,发现NVMe设备,并为其创建相应的pci_dev结构体。
    • NVMe子系统通过注册的probe函数(通常为nvme_probe)来初始化NVMe设备,创建相应的nvme_dev结构体,并设置设备的读写策略等属性。
static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)  
{  
    struct nvme_dev *dev;  
    int err;  
  
    dev = kzalloc(sizeof(struct nvme_dev), GFP_KERNEL);  
    if (!dev)  
        return -ENOMEM;  
  
    err = nvme_init_device(pdev, dev);  
    if (err)  
        goto free_dev;  
  
    pci_set_drvdata(pdev, dev);  
  
    return 0;  
  
free_dev:  
    kfree(dev);  
    return err;  
}

上述代码是NVMe子系统的probe函数,用于初始化NVMe设备。它首先分配一个nvme_dev结构体,然后调用nvme_init_device函数进行设备初始化。最后,将设备实例作为pci_drvdata关联到PCI设备上。

  1. 注册硬盘设备:
    • 在NVMe子系统中,会创建一个名为nvme_queue的结构体,用于管理设备的I/O队列。
    • 通过调用blk_add_disk函数,将NVMe设备注册为一个新的硬盘设备,并创建相应的device_t结构体。
    • 这个结构体包含了设备的属性和管理接口,可以被块层用来管理硬盘的读写操作。
int nvme_register_disk(struct nvme_dev *dev, struct gendisk *disk)  
{  
    int ret;  
  
    // 初始化请求队列,并设置请求处理函数为nvme_ops  
    ret = blk_init_queue(request_queue_callee(disk), &nvme_ops);  
    if (ret)  
        return ret;  
  
    // 将NVMe设备注册为一个新的硬盘设备  
    ret = add_disk(disk);  
    if (ret)  
        blk_cleanup_queue(request_queue_callee(disk));  
  
    return ret;  
}

上述代码是NVMe子系统注册硬盘设备的函数。它首先调用blk_init_queue函数初始化请求队列,并设置请求处理函数为nvme_ops。然后调用add_disk函数将NVMe设备注册为一个新的硬盘设备。如果注册失败,则清理请求队列。

  1. 加载文件系统:
    • 这部分代码通常不在NVMe子系统中,而是在文件系统相关的模块中。
    • 文件系统的加载过程取决于具体的文件系统类型和配置。例如,对于UBI文件系统,会通过ubi_scan函数扫描并解析UBI引导扇区或超级块。
    • 创建文件系统实例的过程可能会涉及到分配内存、初始化文件系统结构等操作。
int ubi_scan(const char *name, int verbose)  
{  
    // 扫描并解析UBI引导扇区或超级块  
    // ...  
  
    // 创建UBI文件系统实例  
    ubi = ubi_alloc(&ubi->vols[0].eba_tbl, vols, vcnt, UBI_EMULATION_FLAGS);  
    if (!ubi) {  
        ubi_err("failed to allocate memory for UBI volume structure");  
        return -ENOMEM;  
    }  
    // ...  
}
  1. 挂载文件系统:
    • 在VFS(Virtual File System)模块中,通过调用mount函数来挂载文件系统。
    • 这个函数会创建VFS节点和目录项,并将其关联到文件系统实例上。
    • 挂载成功后,操作系统就可以通过标准的文件读写接口来访问硬盘了。
  1. 访问硬盘:
    • 在块层中,所有的硬盘读写操作都会被抽象为请求(request)结构体。
    • 当应用程序调用read、write等系统调用时,块层会将这些请求封装为一个请求结构体,并将其加入到NVMe设备的I/O队列中。
    • NVMe子系统通过处理这个队列,将请求发送给硬件进行实际的读写操作。
// 请求结构体  
typedef struct request {  
    // 请求相关字段  
} request;  
  
// 系统调用处理函数  
void system_call_handler(void) {  
    // 封装请求结构体  
    request req;  
    // ...  
    // 将请求加入到NVMe设备的I/O队列中  
    enqueue(nvme_io_queue, &req);  
}  
  
// NVMe子系统处理函数  
void nvme_subsystem_handler(void) {  
    // 处理I/O队列  
    while (!empty(nvme_io_queue)) {  
        request *req = dequeue(nvme_io_queue);  
        // 发送请求给硬件进行实际的读写操作  
        hardware_io(req);  
    }  
}

你可能感兴趣的:(java,服务器,前端)