nvme_queue_rq函数分析一

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物理段的个数,每个物理段的长度不一定就是4096ret = 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_sizeif (!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 = 2040dev->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 * 1elsealloc_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 mappingif (!nr_mapped)goto out;if (iod->use_sgl)ret = nvme_pci_setup_sgls(dev, req, &cmnd->rw, nr_mapped);elseret = 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 SGLif (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_descpool = 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_desciod->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.mzph.cn/news/84155.shtml

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

相关文章

js 事件流、事件冒泡、事件捕获、阻止事件的传播

事件流 js 事件的执行过程分为捕获阶段&#xff08;由外层节点传播到内层节点&#xff09;和冒泡阶段&#xff08;由内层节点传播到外层节点&#xff09;&#xff0c;即先执行捕获阶段的代码&#xff0c;后执行冒泡阶段的代码 事件冒泡 js 事件中的代码默认在冒泡阶段执行&…

自动化发布npm包小记

1.注册npm账号 打开npm官网&#xff0c;并注册自己的npm账号 2.申请AccessToken 1.登录npm官网&#xff0c;登录成功后&#xff0c;点开右上角头像&#xff0c;并点击Access Tokens选项 2.点开Generate New Token下拉框&#xff0c;点击Classic Token(和Granular Access To…

VS2019创建GIt仓库时剔除文件或目录

假设本地有解决方案“SomeSolution” 1、首先”团队资源管理器“-“创建Git存储库”&#xff0c;选择“仅限本地”、“创建” VS会在解决方案目录下自动生成.gitattributes、.gitignore 2、编辑gitignore&#xff0c;直接拖到VS里或者用记事本打开。添加要剔除的文件或文件夹…

轻松自定义文件,悦享文件管理与格式转换!

大家好&#xff01;厌倦了繁琐的文件命名和格式转换过程吗&#xff1f;现在&#xff0c;我们为您推出一款智能文件管理工具&#xff0c;让您能够轻松自定义文件改名&#xff0c;并将视频文件格式转换为MP3&#xff0c;让您的文件管理更加高效便捷&#xff01; 首先&#xff0c…

Redis核心数据结构实战与高性能解析

目录 一、安装Redis 二、Redis线程与高性能 2.1 Redis是单线程么&#xff1f; 2.2 Redis读写是单线程为何这么快&#xff1f; 2.3 Redis如何处理并发操作命令&#xff1f; 三、核心数据结构实战 3.1 字符串常用操作实战 SET 存入键值对 SETNX SETEX MSET 批量存入键…

.Net IDE智能提示汉化(.Net6、AspNetCore)

先上现成的.net6汉化文件&#xff0c;可以手动下载后参照 如何为 .NET 安装本地化的 IntelliSense 文件 进行安装。或者使用后文的工具进行自动安装。 无对照英文在前中文在前 汉化内容来自 官方在线文档 &#xff0c;某些内容可能存在明显的机翻痕迹。 上一些效果图&#x…

c语言基础知识+OS+数据结构

c语言&#xff1a; memory section&#xff1a; .bss&#xff1a; uninitialized or zero-initialized global and static variables .data: initialized global and static variables .text: Read only, code and const C语言编译流程&#xff1a; pre-compiler: …

快速安装和测试混淆后的IPA文件:使用Ipa Guard的签名和安装功能

​ 目录 转载&#xff1a;怎么保护苹果手机移动应用程序ipa中文件安全&#xff1f; 前言 1. 对敏感文件进行文件名称混淆 ​编辑 2. 更改文件的MD5值 3. 增加不可见水印处理 3. 对html&#xff0c;js&#xff0c;css等资源进行压缩 5. 删除可执行文件中的调试信息 转载&…

kubesphere中间件部署

微服务部署前中间件部署 一、MySQL部署 1.1 使用Docker实现MySQL主从复制 docker run -p 3307:3306 --name mysql-master \ -v /mydata/mysql/master/log:/var/log/mysql \ -v /mydata/mysql/master/data:/var/lib/mysql \ -v /mydata/mysql/master/conf:/etc/mysql \ -e My…

企业架构LNMP学习笔记59

目录介绍&#xff1a; bin&#xff1a;存放的是启动和关闭tomcat的脚本文件&#xff1b; conf&#xff1a;存放tomcat服务器的各种全局配置文件&#xff0c;其中最重要的是server.xml和web.xml lib: 存放的是tomcat服务器所需要的各种jar文件。java打包类库。 logs&#xff…

Vue项目前端代码防止被调试

项目背景 被安全测试针对了&#xff0c;总是调试我这不太安全的代码。前端代码深度混淆转成十六进制还不行&#xff0c;仍然找到加密方法&#xff0c;对后端数据进行解密。这次就修改了思路换种方法: 我承认阁下很强&#xff0c;但假如, 我是说假如打开控制台是空白页面&…

OSCP系列靶场-Esay-Gaara保姆级

OSCP系列靶场-Esay-Gaara 目录 OSCP系列靶场-Esay-Gaara总结准备工作信息收集-端口扫描目标开放端口收集目标端口对应服务探测 信息收集-端口测试22-SSH端口的信息收集22-SSH端口版本信息与MSF利用22-SSH协议支持的登录方式22-SSH弱口令爆破(待定)22-SSH手动登录尝试(无) 80-HT…

“三高”论文完美复现!基于PSO-VMD-MCKD方法的风机轴承微弱故障诊断,实现早期微弱故障诊断,MATLAB代码实现...

声明&#xff1a;对于作者的原创代码&#xff0c;禁止转售倒卖&#xff0c;违者必究&#xff01; 本期文章思路来自振动测试与诊断期刊的一篇三高论文&#xff0c;点击链接可跳转。https://mp.weixin.qq.com/s/hmmDj5IwpaozeL4F0iI-2g 文章摘要如下&#xff1a; 针对风机滚动轴…

在qml中将一个16进制表示的颜色加上透明度

在qml中&#xff0c;我们在指定控件的颜色时&#xff0c;可以直接通过16进制的字符串来表示&#xff0c;比如"#ff0000"; 这种方式也比较符合UI设计人员的使用习惯。 但是假如要在此颜色的基础上&#xff0c;加个透明度的话&#xff0c;就要重新计算一番&#xff0c;比…

【iOS逆向与安全】插件开发之某音App直播间自动发666

1.目标 由于看直播的时候主播叫我发 666&#xff0c;支持他&#xff0c;我肯定支持他呀&#xff0c;就一直发&#xff0c;可是后来发现太浪费时间了&#xff0c;能不能做一个直播间自动发 666 呢&#xff1f;于是就花了几分钟做了一个。 2.操作环境 越狱iPhone一台 frida ma…

c++ 纯虚函数、抽象类

一、 纯虚函数 抽象类 只要有一个纯虚函数&#xff0c;这个类称为抽象类 抽象类的特点 1、无法实例化 2、抽象类的子类&#xff0c;必须要重写父类中的纯虚函数&#xff0c;否者也属于抽象类 例子一 #include <iostream> #include <string.h> using namespa…

tensorflow cuda gpu 安装

Windows 安装 CUDA/cuDNN 需要注意的是一定要选择 TensorFlow 和 CUDA相匹配&#xff0c;还需要查看下自己GPU的驱动版本&#xff0c;如果不匹配会出现很多问题。GPU驱动的版本可在 NVIDIA控制面板里找到&#xff1a; CUDA个版本与驱动的关系如下&#xff1a; GPU版本的 Tensor…

Laravel框架 - IOC容器详解

IOC 容器代码 好了&#xff0c;说了这么多&#xff0c;下面要上一段容器的代码了. 下面这段代码不是laravel 的源码&#xff0c; 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长&#xff0c; 小伙伴们要耐心看. …

JMeter断言之JSON断言

JSON断言 若服务器返回的Response Body为JSON格式的数据&#xff0c;使用JSON断言来判断测试结果是较好的选择。 首先需要根据JSON Path从返回的JSON数据中提取需要判断的实际结果&#xff0c;再设置预期结果&#xff0c;两者进行比较得出断言结果。 下面首先介绍JSON与JSON…

【深度学习】LeNet网络架构

文章目录 什么是LeNet代码实现网络架构 什么是LeNet LeNet是一种经典的卷积神经网络&#xff0c;由Yann LeCun等人在1998年提出。它是深度学习中第一个成功应用于手写数字识别的卷积神经网络&#xff0c;并且被认为是现代卷积神经网络的基础。 LeNet模型包含了多个卷积层和池…