nvme_queue_rq函数分析一

news2024/11/27 8:25:23

nvme I/O请求时,数据交互分析

主要函数为nvme_queue_rq:

static blk_status_t nvme_queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data *bd)
{
	struct nvme_ns *ns = hctx->queue->queuedata;
	struct nvme_queue *nvmeq = hctx->driver_data;
	struct nvme_dev *dev = nvmeq->dev;
	struct request *req = bd->rq;
	struct nvme_command cmnd;
	blk_status_t ret;

	if (unlikely(nvmeq->cq_vector < 0))
		return BLK_STS_IOERR;

	ret = nvme_setup_cmd(ns, req, &cmnd);
	if (ret)
		return ret;

	ret = nvme_init_iod(req, dev);
	if (ret)
		goto out_free_cmd;

	if (blk_rq_nr_phys_segments(req)) { //segments物理段的个数,每个物理段的长度不一定就是4096
		ret = nvme_map_data(dev, req, &cmnd);
		if (ret)
			goto out_cleanup_iod;
	}

	blk_mq_start_request(req);
	nvme_submit_cmd(nvmeq, &cmnd);//将命令提交到sq队列,然后写db寄存器
	return BLK_STS_OK;
out_cleanup_iod:
	nvme_free_iod(dev, req);
out_free_cmd:
	nvme_cleanup_cmd(req);
	return ret;
}

这里重点是分析nvme_map_data函数,在分析该函数之前,先看一下nvme_init_iod函数,对于后面的理解会很有帮助。

nvme_init_iod函数:

static blk_status_t nvme_init_iod(struct request *rq, struct nvme_dev *dev)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(rq);
	int nseg = blk_rq_nr_phys_segments(rq);
	unsigned int size = blk_rq_payload_bytes(rq);

	iod->use_sgl = nvme_pci_use_sgls(dev, rq);//判断使用sgl还是prp
	//nseg > 2 || size > 2 * (dev)->ctrl.page_size(假设值为4096)
	if (nseg > NVME_INT_PAGES || size > NVME_INT_BYTES(dev)) {
		iod->sg = mempool_alloc(dev->iod_mempool, GFP_ATOMIC);//大小是alloc_size
		if (!iod->sg)
			return BLK_STS_RESOURCE;
	} else {
		iod->sg = iod->inline_sg; //这个感觉有点奇怪,这个变量是一个指针,后面不申请内存空间直接使用了?
	}

	iod->aborted = 0;
	iod->npages = -1;
	iod->nents = 0;
	iod->length = size;
	return BLK_STS_OK;
}

这里我们先假设iod->sg走的是第一个分支,也就是通过内存池分配的内存,大小是alloc_size,
这个值是怎么来的呢?
在nvme_probe函数里,有这一段代码:

	alloc_size = nvme_pci_iod_alloc_size(dev, NVME_MAX_KB_SZ, NVME_MAX_SEGS, true);
	WARN_ON_ONCE(alloc_size > PAGE_SIZE); //只打印一次异常信息 alloc_size = 2040

	dev->iod_mempool = mempool_create_node(1, mempool_kmalloc, mempool_kfree, (void *) alloc_size, GFP_KERNEL, node);
	if (!dev->iod_mempool) {
		result = -ENOMEM;
		goto release_pools;
	}

其中NVME_MAX_KB_SZ为4096, NVME_MAX_SEGS为127,我们接着看nvme_pci_iod_alloc_size函数,

static int nvme_npages(unsigned size, struct nvme_dev *dev)
{
	unsigned nprps = DIV_ROUND_UP(size + dev->ctrl.page_size, dev->ctrl.page_size);
	return DIV_ROUND_UP(8 * nprps, PAGE_SIZE - 8); //可能会浪费一些内存
}

static int nvme_pci_npages_sgl(unsigned int num_seg) //计算SGL段所需的页面数。例如,一个4k页面可以容纳256个SGL描述符。
{
	return DIV_ROUND_UP(num_seg * sizeof(struct nvme_sgl_desc), PAGE_SIZE); //int(A/B) + 1  ->int(127 * 16)/4096 + 1
}

static unsigned int nvme_pci_iod_alloc_size(struct nvme_dev *dev, unsigned int size, unsigned int nseg, bool use_sgl)
{
	size_t alloc_size;

	if (use_sgl)
		alloc_size = sizeof(__le64 *) * nvme_pci_npages_sgl(nseg);//8 * 1
	else
		alloc_size = sizeof(__le64 *) * nvme_npages(size, dev); 

	return alloc_size + sizeof(struct scatterlist) * nseg; //alloc_size + 16 * 127 映射+记录
}

因为use_sgl传进来的是true,所以走的是上面那个分支,所以最终alloc_size 的大小是像下面这样表示的,那alloc_size = sizeof(__le64 *) * nvme_pci_npages_sgl(nseg);的表示是什么意思呢?先说结论,这个是为了后面通过pool申请内存时记录这些内存用的,因为这些内存地址是64位的所以这里要用 sizeof(__le64 *) 乘以 nvme_pci_npages_sgl(nseg),至于nvme_pci_npages_sgl(nseg)这个函数不多说了,自己看看也很容易理解,这里要说的是NVME_MAX_KB_SZ和NVME_MAX_SEGS值的大小是可以调整的。而nseg1-nseg127是为了后面映射sgl时申请的内存空间,在这里记录下来映射的sgl。
在这里插入图片描述
接着回来看nvme_map_data函数。

static blk_status_t nvme_map_data(struct nvme_dev *dev, struct request *req, struct nvme_command *cmnd)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	struct request_queue *q = req->q;
	enum dma_data_direction dma_dir = rq_data_dir(req) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;//数据传输方向
	blk_status_t ret = BLK_STS_IOERR;
	int nr_mapped;

	//主要就是初始化iod->sg, blk_rq_nr_phys_segments(req)返回的时sge段的个数
	sg_init_table(iod->sg, blk_rq_nr_phys_segments(req)); 
	iod->nents = blk_rq_map_sg(q, req, iod->sg);//这个函数主要将bio里的数据转到iod->sg里
	if (!iod->nents)
		goto out;

	ret = BLK_STS_RESOURCE;
	//这里有了数据以后可以做dma mapping了
	nr_mapped = dma_map_sg_attrs(dev->dev, iod->sg, iod->nents, dma_dir, DMA_ATTR_NO_WARN);//iod->sg dma mapping
	if (!nr_mapped)
		goto out;

	if (iod->use_sgl)
		ret = nvme_pci_setup_sgls(dev, req, &cmnd->rw, nr_mapped);
	else
		ret = nvme_pci_setup_prps(dev, req, &cmnd->rw);

	if (ret != BLK_STS_OK)
		goto out_unmap;

	ret = BLK_STS_IOERR;
	//这个if语句是为metadata做map操作,然后把dma地址给到cmnd->rw.metadata成员 看起来数据量应该不是太大
	if (blk_integrity_rq(req)) { 
		if (blk_rq_count_integrity_sg(q, req->bio) != 1)
			goto out_unmap;

		sg_init_table(&iod->meta_sg, 1);
		if (blk_rq_map_integrity_sg(q, req->bio, &iod->meta_sg) != 1)
			goto out_unmap;

		if (!dma_map_sg(dev->dev, &iod->meta_sg, 1, dma_dir))
			goto out_unmap;
	}

	if (blk_integrity_rq(req))
		cmnd->rw.metadata = cpu_to_le64(sg_dma_address(&iod->meta_sg));
	return BLK_STS_OK;
out_unmap:
	dma_unmap_sg(dev->dev, iod->sg, iod->nents, dma_dir);
out:
	return ret;
}

接着先看nvme_pci_setup_sgls函数,然后在看nvme_pci_setup_prps函数。

static blk_status_t nvme_pci_setup_sgls(struct nvme_dev *dev, struct request *req, struct nvme_rw_command *cmd, int entries)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	struct dma_pool *pool;
	struct nvme_sgl_desc *sg_list;
	struct scatterlist *sg = iod->sg;
	dma_addr_t sgl_dma;
	int i = 0;

	cmd->flags = NVME_CMD_SGL_METABUF; //setting the transfer type as SGL
	if (entries == 1) {
		nvme_pci_sgl_set_data(&cmd->dptr.sgl, sg); //使用sgl,则使用struct nvme_sgl_desc 结构体
		return BLK_STS_OK;
	}

	//这里是根据sge的个数来指定 走那个分支的,避免内存的浪费
	if (entries <= (256 / sizeof(struct nvme_sgl_desc))) { //256 / 16 = 16 总的大小是256,除以16表示可以放几个struct nvme_sgl_desc
		pool = dev->prp_small_pool; //大小256(如果走这个分支,pool的大小是256,可以表示16个struct nvme_sgl_desc)
		iod->npages = 0;
	} else {
		pool = dev->prp_page_pool; //大小4096/16 = 256 所以可以表示 256个struct nvme_sgl_desc
		iod->npages = 1;
	}

	sg_list = dma_pool_alloc(pool, GFP_ATOMIC, &sgl_dma);
	if (!sg_list) {
		iod->npages = -1;
		return BLK_STS_RESOURCE;
	}
	//这两个操作是为了后面的释放?看起来是的
	/*
		记录下这个值,类似于*(iod->sg + blk_rq_nr_phys_segments) = sg_list
		使用二级指针可以记录的范围更多,如果使用一级指针记录的值范围不全
	*/
	nvme_pci_iod_list(req)[0] = sg_list;
	iod->first_dma = sgl_dma;

	nvme_pci_sgl_set_seg(&cmd->dptr.sgl, sgl_dma, entries); //给rw command sgl设置链的起始地址
	do {
		if (i == SGES_PER_PAGE) { //256 pool = dev->prp_page_pool; 才会走这个分支
			struct nvme_sgl_desc *old_sg_desc = sg_list;
			struct nvme_sgl_desc *link = &old_sg_desc[i - 1];

			sg_list = dma_pool_alloc(pool, GFP_ATOMIC, &sgl_dma);
			if (!sg_list)
				return BLK_STS_RESOURCE;
			i = 0;
			nvme_pci_iod_list(req)[iod->npages++] = sg_list;//记录申请的dma addr 方便后面释放
			/*因为上一个sg_list的最后一个sg_desc用来记录链表了,所以将*link这个上一个list的最后一个记录数据
				的地方,换到下一个list的第一个位置。
			*/
			sg_list[i++] = *link;
			nvme_pci_sgl_set_seg(link, sgl_dma, entries);
		}
		nvme_pci_sgl_set_data(&sg_list[i++], sg);
		sg = sg_next(sg);
	} while (--entries > 0);
	return BLK_STS_OK;
}

先看读写命令时的数据结构,NVME的命令都是64个字节的。

struct nvme_sgl_desc {
	__le64	addr;
	__le32	length;
	__u8	rsvd[3];
	__u8	type;
};

struct nvme_keyed_sgl_desc {
	__le64	addr;
	__u8	length[3];
	__u8	key[4];
	__u8	type;
};

union nvme_data_ptr {
	struct {
		__le64	prp1;
		__le64	prp2;
	};
	struct nvme_sgl_desc	sgl;
	struct nvme_keyed_sgl_desc ksgl;
};

struct nvme_rw_command {
	__u8			opcode;
	__u8			flags;
	__u16			command_id;
	__le32			nsid;
	__u64			rsvd2;
	__le64			metadata;
	union nvme_data_ptr	dptr;
	__le64			slba;
	__le16			length;
	__le16			control;
	__le32			dsmgmt;
	__le32			reftag;
	__le16			apptag;
	__le16			appmask;
};

类似这样来表示数据,所以,如果用sgl的话,addr记录地址,length记录一个sge的长度,二prp的就只有两个64位prp指针,要用它来表示地址和要传输的长度,处理起来就麻烦一些。
在这里插入图片描述

static void nvme_pci_sgl_set_data(struct nvme_sgl_desc *sge, struct scatterlist *sg)
{
	sge->addr = cpu_to_le64(sg_dma_address(sg));
	sge->length = cpu_to_le32(sg_dma_len(sg));
	sge->type = NVME_SGL_FMT_DATA_DESC << 4;
}

static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, dma_addr_t dma_addr, int entries)
{
	sge->addr = cpu_to_le64(dma_addr);
	if (entries < SGES_PER_PAGE) {
		sge->length = cpu_to_le32(entries * sizeof(*sge));
		sge->type = NVME_SGL_FMT_LAST_SEG_DESC << 4;
	} else {
		sge->length = cpu_to_le32(PAGE_SIZE);
		sge->type = NVME_SGL_FMT_SEG_DESC << 4;
	}
}
static void **nvme_pci_iod_list(struct request *req)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	//前面是记录了映射的地址,所以是iod->sg加上blk_rq_nr_phys_segments(req)
	return (void **)(iod->sg + blk_rq_nr_phys_segments(req)); 
}

最后再来看看释放的代码:

static void nvme_unmap_data(struct nvme_dev *dev, struct request *req)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	enum dma_data_direction dma_dir = rq_data_dir(req) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;

	if (iod->nents) {
		dma_unmap_sg(dev->dev, iod->sg, iod->nents, dma_dir);
		if (blk_integrity_rq(req))
			dma_unmap_sg(dev->dev, &iod->meta_sg, 1, dma_dir);
	}

	nvme_cleanup_cmd(req);
	nvme_free_iod(dev, req);
}
static void nvme_free_iod(struct nvme_dev *dev, struct request *req)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	const int last_prp = dev->ctrl.page_size / sizeof(__le64) - 1;
	dma_addr_t dma_addr = iod->first_dma, next_dma_addr;
	int i;

	if (iod->npages == 0)
		dma_pool_free(dev->prp_small_pool, nvme_pci_iod_list(req)[0], dma_addr);

	for (i = 0; i < iod->npages; i++) {
		void *addr = nvme_pci_iod_list(req)[i];//这个是虚拟地址

		if (iod->use_sgl) {
			struct nvme_sgl_desc *sg_list = addr;
			//256 - 1 最后一个描述符的addr记录的是下一个 list dma pool的起始dma地址
			next_dma_addr = le64_to_cpu((sg_list[SGES_PER_PAGE - 1]).addr);
		} else {
			__le64 *prp_list = addr;
			next_dma_addr = le64_to_cpu(prp_list[last_prp]);
		}
		dma_pool_free(dev->prp_page_pool, addr, dma_addr);
		dma_addr = next_dma_addr;
	}
	//不相等说明iod->sg是通过mempool_alloc申请内存的,这里通过mempool_free进行释放
	if (iod->sg != iod->inline_sg)
		mempool_free(iod->sg, dev->iod_mempool);
}

下一篇文章再分析prp这块的代码。

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

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

相关文章

elementui表单的验证问题

elementui表单的验证问题 element ui是基于vue的一个ui框架&#xff0c;用起来还是挺不错的&#xff0c;但是有时候还是会遇到一些摸不着头脑的情况。 ​ 我在打开一个新增数据的对话框的时候出现了一个问题&#xff0c;明明是新增&#xff0c;但是一打开就出现了错误提示&…

Linux虚拟化指南:构建虚拟化环境

虚拟化技术在计算领域具有广泛的应用&#xff0c;能够提高硬件资源的利用率、降低维护成本&#xff0c;并实现灵活的资源分配。Linux作为一种开源操作系统&#xff0c;在虚拟化方面也有多种选择和工具可供使用。下面将介绍如何构建Linux虚拟化环境&#xff0c;并提供一些建议和…

skywalking入门

参考&#xff1a; https://www.jianshu.com/p/ffa7ddcda4ab 参考&#xff1a; https://developer.aliyun.com/article/1201085 skywalking&#xff08;APM&#xff09; 调用链路分析以及应用监控分析工具 Skywalking主要由三大部分组成&#xff1a;agent、collector、webapp-…

强怼美国政府,Zlibrary高调复活,官宣2023年最新网址,免费下载海量电子书籍

去年11月&#xff0c;号称是全球最大电子图书馆的Z-Library被美国FBI封禁&#xff0c;连同下线的还有多达249个备用、影子、镜像、关联域名等。 随后&#xff0c;美司法部在阿根廷逮捕了Z-Library网站幕后的两位策划者。他们被控盗取文化作品牟利&#xff0c;经常在新书出版后几…

HTML5中使用video标签

参考链接 <videocontrolscontrolslist"nodownload noplaybackrate"disablePictureInPicture"true"disableRemotePlayback"true"src"https://www.runoob.com/try/demo_source/movie.mp4"></video>隐藏下载&#xff1a;nod…

ElasticSearch从入门到精通(一)

1. 初识 ElasticSearch 传统数据库查询的问题&#xff1a;如果使用模糊查询&#xff0c;左边有通配符&#xff0c;不会走索引&#xff0c;全表扫描&#xff0c;效率比较慢 倒排索引 将文档进行分词&#xff0c;形成词条和 id 的对应关系即为反向索引。 以唐诗为例&#xff0c…

解决域控制器的传感器配置问题

gpu加速计划 下载东西有时会报没有apt-utils&#xff0c;所以最好先给它下了&#xff1a; sudo apt-get install apt-utils验证&#xff1a; python #输入库 import torch #查看版本 print(torch.__version__) #查看gpu是否可用 torch.cuda.is_available() #返回设备gpu个数…

跨端开发方案之桌面应用小程序

小程序容器技术的未来是充满希望的&#xff0c;它为我们开辟了一个全新的数字世界&#xff0c;连接了桌面操作系统和移动生态系统之间的界限。正如技术不断演进&#xff0c;我们可以期待着更多的创新和发展&#xff0c;为用户带来更加便捷和多样化的应用体验。这一技术的推广和…

用C++写一个生成n个m之内的随机整数的函数

#include <iostream> #include <cstdlib> #include <ctime>using namespace std;void generateRandomNumbers(int n, int m) {srand(time(NULL)); // 初始化随机数种子for (int i 0; i < n; i) {int num rand() % m 1; // 生成 1 到 m 之间的随机整数c…

windwos10系统搭建我的世界服务器,内网穿透实现联机游戏Minecraft

文章目录 1. Java环境搭建2.安装我的世界Minecraft服务3. 启动我的世界服务4.局域网测试连接我的世界服务器5. 安装cpolar内网穿透6. 创建隧道映射内网端口7. 测试公网远程联机8. 配置固定TCP端口地址8.1 保留一个固定tcp地址8.2 配置固定tcp地址 9. 使用固定公网地址远程联机 …

麒麟信安的2023世界计算大会时刻

9月15至16日&#xff0c;由工业和信息化部、湖南省人民政府主办的2023世界计算大会在长沙隆重举行。麒麟信安连续五年亮相世界计算大会&#xff0c;本届大会麒麟信安作为计算产业的重要建设者、国家新一代自主安全计算系统产业集群内核心企业&#xff0c;在展览展示、主题演讲、…

基于SSM的金鱼销售平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

LeetCode 23. 合并 K 个升序链表

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 首先我们实现一个合并两个有序链表的操作&#xff0c;然后使用归并的思想对数组中的链表进行排序。 代码 /*** Definition for singly-linked list.* struct ListNode {* in…

基础组件(线程池、内存池、异步请求池、Mysql连接池)

文章目录 1、概述2、线程池2、异步请求池3、内存池 1、概述 池化技术&#xff0c;减少了资源创建次数&#xff0c;提高了程序响应性能&#xff0c;特别是在高并发场景下&#xff0c;当程序7*24小时运行&#xff0c;创建资源可能会出现耗时较长和失败等问题&#xff0c;池化技术…

Spring事件机制之ApplicationEvent

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

vue3 - 前端 Vue 项目提交GitHub 使用Actions自动化部署

GitHub Demo 地址 在线预览 参考文章 使用GithubActions发布Vue网站到GithubPage 使用Github Actions将Vue项目部署到Github Pages 前端使用github pages 部署自己的网站 GitHub Actions自动化部署前端项目指南 前言 vue前端项目写好之后&#xff0c;想部署到线上通过在线地址…

中秋接月饼

hellow大家好&#xff0c;中秋佳节到了&#xff0c;欢乐度节的同时&#xff0c;技术也要跟上呀&#xff0c;这次我们通过canvas实现一个中秋接月饼的小游戏&#xff0c;三连不迷路哦~ 展示一下游戏成品&#xff1a; 准备游戏背景 首先我们将游戏背景界面绘制出来。 游戏背景…

滚雪球学Java(37):深入了解Java方法作用域和生命周期,让你写出更高效的代码

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

微信重磅更新!有人已经涨粉好几万了!

公众号又更新了。 一个是发布功能升级&#xff0c;新发布的内容将展示在公众号主页&#xff0c;并有机会获得平台推荐。但仍然不会推送给用户&#xff0c;这是除了次数以外&#xff0c;和群发文章仅有的区别了。 ▲ 图片来源&#xff1a;微信公众号平台 不过目前仅支持用网页…

Android调用相机拍照,展示拍摄的图片

调用相机&#xff08;隐式调用&#xff09; //自定义一个请求码 这里我设为10010int TAKE_PHOTO_REQUEST 10010;int RESULT_CANCELED 0;//定义取消码//触发监听&#xff0c;调用相机image_camera.setOnClickListener(new View.OnClickListener() {Overridepublic void onCli…