驱动 | Linux | NVMe - 1. 概述

news2024/11/17 6:22:54

本文主要参考2篇相关的解析 12linux 源码 3
此处推荐一个可以便捷查看 linux 源码的网站 bootlin 4

更新:2022 / 02 / 11


驱动 | Linux | NVMe - 1. 概述与nvme_core_init函数解析

  • NVMe 的前世今生
    • NVMe Command
    • PCI 总线
    • 从架构角度看 NVMe 驱动
    • NVMe 驱动的文件构成
  • NVMe Driver 工作原理
  • 参考链接


NVMe 的前世今生

NVMe 离不开 PCIeNVMe SSDPCIeendpointPCIex86 平台上一种流行的外设总线,由于其 Plug and Play 的特性,目前很多外设都通过 PCI BusHost 通信,甚至不少CPU 的集成外设都通过 PCI Bus 连接,如 APIC 等。

NVMe SSDPCIe 接口上使用新的标准协议 NVMe,由大厂 Intel 推出并交由 nvmexpress 组织推广,现在被全球大部分存储企业采纳。


NVMe Command

NVMe HostServer )和 NVMe ControllerSSD )通过 NVMe Command 进行信息交互。NVMe Spec 中定义了 NVMe Command 的格式,占用 64 字节。

NVMe Command 分为 Admin CommandIO Command 两大类,前者主要是用于配置,后者用于数据传输。

NVMe CommandHostSSD Controller 交流的基本单元,应用的 I/O 请求也要转化成NVMe Command

或许可以将 NVMe Command 理解为英语,host和SSD controller分别为韩国人和日本人,这两个 对象 需要通过英语统一语法来进行彼此的沟通和交流,Admin Command 是语法 负责语句的主谓宾结构,IO Command是单词 构成语句的具体使用词语。一句话需要语法组织语句结构和单词作为语句的填充。


PCI 总线

  1. 在操作系统启动时,BIOS 会枚举整个 PCI 的总线,之后将扫描到的设备通过 ACPI tables 传给操作系统。
  2. 当操作系统加载时,PCI Bus 驱动则会根据此信息读取各个 PCI 设备的 PCI Header Config 空间,从 class code 寄存器获得一个特征值。

class codePCI bus 用来选择哪个驱动加载设备的唯一根据。

NVMe Spec 定义的 class code010802hNVMe SSD 内部的 Controller PCIe Headerclass code 都会设置成010802h

所以,需要在驱动中指定 class code010802h,将 010802h 放入 pci_driver nvme_driverid_table,如下所示 5

static const struct pci_device_id nvme_id_table[] = {
	{ PCI_VDEVICE(INTEL, 0x0953),	/* Intel 750/P3500/P3600/P3700 */
		.driver_data = NVME_QUIRK_STRIPE_SIZE |
				NVME_QUIRK_DEALLOCATE_ZEROES, },
	......
	{ PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2005),
		.driver_data = NVME_QUIRK_SINGLE_VECTOR |
				NVME_QUIRK_128_BYTES_SQES |
				NVME_QUIRK_SHARED_TAGS |
				NVME_QUIRK_SKIP_CID_GEN |
				NVME_QUIRK_IDENTIFY_CNS },
	{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, nvme_id_table);

之后当 nvme_driver 注册到 PCI Bus 后,PCI Bus 就知道这个驱动是给 class code=010802h 的设备使用的。

nvme_driver 中有一个 probe 函数,nvme_probe(),这个函数才是真正加载设备的处理函数。


从架构角度看 NVMe 驱动

学习 Linux NVMe Driver之前,先看一下 DriverLinux 架构中的位置,如下图所示:

在这里插入图片描述
NVMe driverBlock Layer 之下,负责与 NVMe 设备交互。

为了紧跟时代的大趋势,现在的 NVMe driver 已经很强大了,也可以支持 NVMe over Fabric 相关设备,如下图所示:

在这里插入图片描述
不过,本文还是以 NVMe over PCIe 为主。


NVMe 驱动的文件构成

最新的代码位于 linux/drivers/nvme/ 6
其文件目录构成如下所示:

nvme ----

在分析一个 driver 时,最好先看这个 driver 相关的 kconfigMakefile 文件,了解其文件架构,再阅读相关的 source code

Kconfig 文件的作用是:

  1. 控制 make menuconfig 时,出现的配置选项;
  2. 根据用户配置界面的选择,将配置结果保存在 .config 配置文件(该文件将提供给 Makefile 使用,用以决定要编译的内核组件以及如何编译)

先看一下 linux/drivers/nvme/host/Kconfig 7 的内容(NVMeOF相关内容已省略,后续不再注明),如下所示:

# SPDX-License-Identifier: GPL-2.0-only
config NVME_CORE
	tristate
	select BLK_DEV_INTEGRITY_T10 if BLK_DEV_INTEGRITY
......

接着,再看一下 linux/drivers/nvme/host/Makefile 8 的内容,如下所示:

# SPDX-License-Identifier: GPL-2.0

ccflags-y				+= -I$(src)

obj-$(CONFIG_NVME_CORE)			+= nvme-core.o
obj-$(CONFIG_BLK_DEV_NVME)		+= nvme.o
obj-$(CONFIG_NVME_FABRICS)		+= nvme-fabrics.o
obj-$(CONFIG_NVME_RDMA)			+= nvme-rdma.o
obj-$(CONFIG_NVME_FC)			+= nvme-fc.o
obj-$(CONFIG_NVME_TCP)			+= nvme-tcp.o
obj-$(CONFIG_NVME_APPLE)		+= nvme-apple.o
......

KconfigMakefile 来看,了解 NVMe over PCIe 相关的知识点,我们主要关注 core.cpci.c 就好。


NVMe Driver 工作原理

core.c 找到程序入口 module_init(nvme_core_init);,如下所示:

static int __init nvme_core_init(void)
{
	int result = -ENOMEM;					

	......
	
	// 1. 注册字符设备 "nvme"
	result = alloc_chrdev_region(&nvme_ctrl_base_chr_devt, 0,
			NVME_MINORS, "nvme");
	if (result < 0)
		goto destroy_delete_wq;
	
	// 2. 新建一个nvme class,拥有者(Owner)是为THIS_MODULE
	//	  如果有Error发生,删除字符设备nvme
	nvme_class = class_create(THIS_MODULE, "nvme");      
	if (IS_ERR(nvme_class)) {
		result = PTR_ERR(nvme_class);
		goto unregister_chrdev;
	}
	nvme_class->dev_uevent = nvme_class_uevent;

	nvme_subsys_class = class_create(THIS_MODULE, "nvme-subsystem");
	if (IS_ERR(nvme_subsys_class)) {
		result = PTR_ERR(nvme_subsys_class);
		goto destroy_class;
	}

	result = alloc_chrdev_region(&nvme_ns_chr_devt, 0, NVME_MINORS,
				     "nvme-generic");
	if (result < 0)
		goto destroy_subsys_class;

	nvme_ns_chr_class = class_create(THIS_MODULE, "nvme-generic");
	if (IS_ERR(nvme_ns_chr_class)) {
		result = PTR_ERR(nvme_ns_chr_class);
		goto unregister_generic_ns;
	}

	result = nvme_init_auth();
	if (result)
		goto destroy_ns_chr;
	return 0;

从上面来看,nvme_core_init 主要做了两件事:

  1. 调用 alloc_chrdev_region 9 函数,如下所示,注册一个名为 nvme 的字符设备。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}
  1. 调用 class_create 函数 10,如下所示,动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。创建的逻辑类位于 /sys/class/
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

#endif	/* _DEVICE_CLASS_H_ */

在注册字符设备时,涉及到了设备号的知识点:

一个字符设备或者块设备都有一个主设备号( Major )和次设备号( Minor )。主设备号用来表示一个特定的驱动程序,次设备号用来表示使用该驱动程序的各个设备。比如,我们在 Linux 系统上挂了两块 NVMe SSD,那么主设备号就可以自动分配一个数字 (比如 8 ),次设备号分别为 12

例如,在 32 位机子中,设备号共 32 位,高 12 位表示主设备号,低 20 位表示次设备号。

有了上面已经注册的字符设备,我们就可以通过 openioctrlrelease 接口对其进行操作了。

nvme 字符设备的文件操作结构体 nvme_dev_fops 11 定义如下:

static const struct file_operations nvme_dev_fops = {
	.owner		= THIS_MODULE,
	.open		= nvme_dev_open,
	.release	= nvme_dev_release,
	.unlocked_ioctl	= nvme_dev_ioctl,
	.compat_ioctl	= compat_ptr_ioctl,
	.uring_cmd	= nvme_dev_uring_cmd,
};


参考链接


  1. Linux中nvme驱动详解 ↩︎

  2. Linux NVMe Driver学习笔记之1:概述与nvme_core_init函数解析 ↩︎

  3. linux ↩︎

  4. bootlin ↩︎

  5. linux/drivers/nvme/host/pci.c ↩︎

  6. linux/drivers/nvme/ ↩︎

  7. linux/drivers/nvme/host/Kconfig ↩︎

  8. linux/drivers/nvme/host/Makefile ↩︎

  9. linux/fs/char_dev.c ↩︎

  10. linux/include/linux/device.h ↩︎

  11. linux/drivers/nvme/host/core.c ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端开发中如何处理接口数据过大的问题

题引&#xff1a; 当我们在公司做项目的时候&#xff0c;难免会遇到后端接口直接给你返回成千上万的数据进行渲染。如果我们直接一股脑遍历添加的话&#xff0c;就会导致空白页面的等待时间是很长且异常卡顿&#xff0c;那么对于数据过大的渲染就需要进行特殊的处理。这也是一…

PyQt5数据库开发1 4.1 SQL Server 2008 R2如何开启数据库的远程连接

文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服务器 4 设置sa用户属性 5 配置服务器 6 重新登录 7 配置SSCM 8 确认防火墙设置 注意事项 前言 SQL Server 2008 R2如何开启数据库的远程连接 SQL Server 2008默认是不允许远程连…

ExecutorService、Callable、Future实现有返回结果的多线程原理解析

在并发多线程场景下&#xff0c;存在需要获取各线程的异步执行结果&#xff0c;这时&#xff0c;就可以通过ExecutorService线程池结合Callable、Future来实现。 我们先来写一个简单的例子—— public class ExecutorTest {public static void main(String[] args) throws Ex…

KMP 算法

1 应用场景-字符串匹配问题  字符串匹配问题&#xff1a;&#xff1a; 有一个字符串 str1 ““硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好””&#xff0c;和一个子串 str2“尚硅谷你尚硅 你” 2) 现在要判断 str1 是否含有 str2, 如果存在&#xff0c;就返回第一次出现…

数据与C(limits.h数据常数介绍)

本章简单的介绍一下limits.h的数据常量&#xff0c;这里简单了解一下就好了 目录 一.limits.h 二.float.h头文件 一.limits.h CHAR_BIT char类型的位数 CHARMAX char类型的最大值 CHAR_MIN char类型的最小值 SCHAR_MAX signed char类型的最大…

SpringBoot图片上传和访问路径映射

图片上传和静态资源映射编写controller层接口上传到文件夹相关配置1 application.properties配置文件&#xff1a;2 Constant类&#xff1a;文件的资源映射配置WebMvcConfigurer的继承类注意测试编写controller层接口 ApiOperation("图片上传功能")PostMapping(&quo…

Java笔记-volatile和AtomicInteger

目录1. volatile1.1.什么是volatile1.2.JMM-Java内存模型2 验证volatile的特性2.1 可见性2.2.验证volatile不保证原子性2.3 volatile实现禁止指令重排序3.使用AtomicInteger解决volatile的不能实现原子性的问题3.2 AtomicInteger的方法说明&#xff1a;3.3 CAS3.4 应用1. volat…

linux-进程1-进程概述

写在最前 记录一下linux的进程学习专题 1. 程序和进程的区别 1.1 程序 程序是包含一系列信息的文件&#xff0c;这些信息描述了如何在运行时创建一个进程&#xff1a; 二进制格式标识&#xff1a;每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解 释文…

Redis实战-session共享之修改登录拦截器

在上一篇中Redis实战之session共享&#xff0c;我们知道了通过Redis实现session共享了&#xff0c;那么token怎么续命呢&#xff1f;怎么刷新用户呢&#xff1f;本来咱们就通过拦截器来实现这两个功能。 登录拦截器优化&#xff1a; 先来看看现在拦截器情况&#xff1a; 拦截…

JavaScipt基础学习(1)

1. JavaScript特点 JavaScript是脚本编写语言&#xff1b;所有主流浏览器都支持JavaScript&#xff1b;JavaScript基于对象语言&#xff1b;JavaScriptb变量类型是弱类型&#xff0c;没有如Java一样严格的数据类型&#xff1b;变量是弱类型的。因此定义变量时&#xff0c;只使…

WindowsServer服务器系列:部署FTP文件服务

1、点击“开始”菜单&#xff0c;选择“服务器管理器” 2、在接下来弹出页面中选择“添加角色和功能” 3、接下来点击“下一步” 4、接下来选择“基于角色或基于功能的安装”并点击“下一步” 5、选择“从服务器池中选择服务器”并点击“下一步” 6、接下来选中“Web 服务器(II…

【数模比赛】2023美国大学生数学建模比赛(思路、代码......)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

虚拟存储管理(6)

虚拟存储管理 前面介绍的存储管理方案要求作业全部装入内存才可运行。但这会出现两种情况&#xff1a; 有的作业因太大&#xff0c;内存装不下而无法运行。系统中作业数太多&#xff0c;因系统容量有限只能让少数作业先运行。 1 局部性原理 定义&#xff1a; 程序执行时&a…

TCP网络编程中connect()、listen()和accept()三者之间的关系

基于 TCP 的网络编程开发分为服务器端和客户端两部分&#xff0c;常见的核心步骤和流程如下&#xff1a; connect()函数 对于客户端的 connect() 函数&#xff0c;该函数的功能为客户端主动连接服务器&#xff0c;建立连接是通过三次握手&#xff0c;而这个连接的过程是由内核…

LeetCode题目笔记——24. 两两交换链表中的节点

文章目录题目描述题目链接题目难度——中等方法一&#xff1a;迭代代码/C代码/python方法二&#xff1a;递归代码/C总结题目描述 或许这也是个经典的面试题&#xff0c;记录一手 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在…

模电学习6. 常用的三极管放大电路

模电学习6. 常用的三极管放大电路一、判断三极管的工作状态1. 正偏与反偏的概念2. 工作状态的简单判断二、三种重要的放大电路1. 共射电路2. 共集电极放大电路3. 共基极放大电路一、判断三极管的工作状态 1. 正偏与反偏的概念 晶体管分P区和N区&#xff0c; 当P区电压大于N区…

[设计模式] 建造者模式

文章目录什么是建造者模式建造者模式建造者模式中的角色UML类图代码实现建造者模式与工厂模式的区别什么是建造者模式 建造者模式(Builder Pattern)是一种创建型的设计模式&#xff0c;它将一个复杂对象的构建与它的表示分离&#xff0c;也就是复杂的构建隐藏起来&#xff0c;…

即时通讯系列-4-如何设计写扩散下的同步协议方案

1. 背景信息 上篇提到了, IM协议层是主要解决会话和消息的同步, 在实现上, 以推模式为主, 拉模式为辅. 本文Agenda: (How)如何同步(How)如何设计同步位点如何设计 Gap过大(SyncGapOverflow) 机制如何设计Ack机制总结 提示: 本系列文章不会单纯的给出结论, 希望能够分享的是&…

SpringCloud-Netflix学习笔记13——Zuul路由网关

什么是Zuul? Zuul包含了对请求的路由和过滤两个最主要的功能。 其中路由功能负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础&#xff0c;而过滤器功能则负责对请求的处理过程进行干预&#xff0c;是实现请求校验&#xff0c;服务聚合等功能…

最详细教你注册 ChatGPT,不会来找我

超强人工智能 ChatGPT 震撼来袭&#xff0c;它是美国人工智能研究实验室 OpenAI 新推出的一种自然语言处理工具&#xff0c;不想来体验一下嘛&#xff01;最详细教程手把手教你注册&#xff0c;不会来找我&#xff01; 准备工作 一个可以科学上网的工具&#xff0c;提供非 Ch…