nvme prp模型代码处理流程分析

2023-09-22 09:35:56

以下函数是prp相关的源码。

/*
 * prp模型,除了第一个dma addr不是page_size对齐的
	其余的dma addr都要求是page_size对齐的
*/
static blk_status_t nvme_pci_setup_prps(struct nvme_dev *dev, struct request *req, struct nvme_rw_command *cmnd)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	struct dma_pool *pool;
	__le64 *prp_list;
	dma_addr_t prp_dma;
	int nprps, i;
	int length = blk_rq_payload_bytes(req);//request的数据总长度, 假设 5000
	struct scatterlist *sg = iod->sg; //数据都映射在这里了
	int dma_len = sg_dma_len(sg); //dma mapping以后的数据长度 假设 4096
	u64 dma_addr = sg_dma_address(sg); //假设4096对齐或者不对齐,也就是有offset的情况
	u32 page_size = dev->ctrl.page_size; //这个假设是4096
	int offset = dma_addr & (page_size - 1);//求offset的方法,比较巧妙,根据这个offset可以知道一个sge要传输的数据长度(在确定page大小的情况下)
	void **list = nvme_pci_iod_list(req);

	length -= (page_size - offset);//判断1个prp是否满足要求 length = length - (page_size - offset) = 5000 - 4096
	if (length <= 0) {
		iod->first_dma = 0;
		goto done;
	}

	dma_len -= (page_size - offset); //dma_len = dma_len - (page_size - offset) = 4096 - 4096 = 0
	if (dma_len) {
		dma_addr += (page_size - offset); //dma_addr = dma_addr + (page_size - offset)
	} else { 
		sg = sg_next(sg); //如果走这个分支说明 dma_len == (page_size - offset)
		dma_addr = sg_dma_address(sg);
		dma_len = sg_dma_len(sg);
	}

	//这个成立说明只需要两个prp就可以了 不需要list
	if (length <= page_size) {
		iod->first_dma = dma_addr;
		goto done;
	}

	nprps = DIV_ROUND_UP(length, page_size); // nprps = int(length / page_size) + 1
	if (nprps <= (256 / 8)) { //nprps < 32
		pool = dev->prp_small_pool; //256 / 8 = 32
		iod->npages = 0;
	} else {
		pool = dev->prp_page_pool; //4096 / 8 = 512
		iod->npages = 1; //标记是使用哪一个pool
	}

	prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);
	if (!prp_list) {
		iod->first_dma = dma_addr;
		iod->npages = -1;
		return BLK_STS_RESOURCE;
	}
	list[0] = prp_list; //这个类似 nvme_pci_iod_list(req)[0] = prp_list 记录的是虚拟地址;
	iod->first_dma = prp_dma; //记录物理地址
	i = 0;
	for (;;) {
		if (i == page_size >> 3) { //4096 >> 3 == 512, 只有pool是prp_page_pool才会进入这个分支,当然还要保证iod->sg映射的物理段足够多
			__le64 *old_prp_list = prp_list;
			prp_list = dma_pool_alloc(pool, GFP_ATOMIC, &prp_dma);//之前的记录满了,所以再次申请
			if (!prp_list)
				return BLK_STS_RESOURCE;
			list[iod->npages++] = prp_list;
			prp_list[0] = old_prp_list[i - 1];
			old_prp_list[i - 1] = cpu_to_le64(prp_dma);
			i = 1;
		}
		prp_list[i++] = cpu_to_le64(dma_addr); //prp_list里面记录的都是地址,记录一个需要8个字节
		dma_len -= page_size;
		dma_addr += page_size;
		length -= page_size;

		//这个成立说明数据已经全部转换完毕了
		if (length <= 0)
			break;
		if (dma_len > 0)
			continue;
		/*说明了sg_dma_address(sg)转换出来的地址不一定就是page_size对齐的,
			但是dma_len一定要是page_size的倍数。也就是offset为0
		*/
		if (unlikely(dma_len < 0)) 
			goto bad_sgl;
		sg = sg_next(sg);
		dma_addr = sg_dma_address(sg);
		dma_len = sg_dma_len(sg);
	}
done:
	cmnd->dptr.prp1 = cpu_to_le64(sg_dma_address(iod->sg)); //主要就是填这两个prp
	cmnd->dptr.prp2 = cpu_to_le64(iod->first_dma);//第二个prp记录数据或者下一个dma pool的起始地址
	return BLK_STS_OK;
bad_sgl:
	WARN(DO_ONCE(nvme_print_sgl, iod->sg, iod->nents), "Invalid SGL for payload:%d nents:%d\n", blk_rq_payload_bytes(req), iod->nents);
	return BLK_STS_IOERR;
}

一般来说,如果sge只有1个时,那么只需要在下发的nvme rw命令时填写prp1字段的值,这个值是64bit的,所以这个64bit的字段即需要有dma的地址,也需要有传输的长度,这个64bit所以它需要一些特定的bit记录地址,一些特定的bit记录长度,同理prp2也是。
但是如果sge段比较多,靠prp1和prp2就不够了,所以需要更多的prp,这个时候prp2里记录的地址就是prp_list的起始地址了,但是,prp_list有个限制条件,就是它的每个64位的bit记录的都是地址,而没有长度,长度都是nvme 的page_size大小。

在这里插入图片描述
从上面的分析当中,有两个疑问?
1:怎么知道prp2记录的是数据的地址还是prp_list的起始地址?
2:需要多个prp时,因为prp_list是只记录地址的,但是有时候一个IO请求时,数据量没那么巧怎么办?也就是prp_list最后元素记录的地址传输的数据量不是一个page_size?

关于第二点,在nvme_map_data函数里有这么一段代码,metadata有特殊用途?

//这个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;

第一点,如果有知道的同仁可以留给言。

最后看一下,关于选择sgl还是prp的依据。

static inline bool nvme_pci_use_sgls(struct nvme_dev *dev, struct request *req)
{
	struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
	int nseg = blk_rq_nr_phys_segments(req);
	unsigned int avg_seg_size;

	if (nseg == 0)
		return false;

	avg_seg_size = DIV_ROUND_UP(blk_rq_payload_bytes(req), nseg); //int(blk_rq_payload_bytes(req)/nseg) + 1
	if (!(dev->ctrl.sgls & ((1 << 0) | (1 << 1)))) //判断sgls的第0第1个bit是否为1
		return false;
	if (!iod->nvmeq->qid) //这个是管理队列
		return false;
	if (!sgl_threshold || avg_seg_size < sgl_threshold)//数据量比较小时使用prp
		return false;
	return true;
}
更多推荐

【从0学习Solidity】7. 映射类型 mapping

【从0学习Solidity】7.映射类型mapping博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!本文收录于不写代码没饭吃的学习汇报系列

穿越两大空间的调用栈

人是有经历的,软件也如此。简历记录着一个人的经历,而调用栈(callstack)则记录着软件的经历。看一个人的简历可以快速了解一个人。观察调用栈,则可以快速理解软件。因为此,我非常喜欢看软件的调用栈。每当看到一个漂亮的调用栈,我常常如获至宝,端详许久。因为对调试技术的热爱,这些年,我花了很多时间在调试器上。特别是开发了

基于movie lens-100k数据集的协同过滤算法实现

基于movielens-100k数据集的协同过滤算法实现数据集处理基于用户的协同过滤算法的实现基于物品的协同过滤算法的实现数据集处理importpandasaspdu_data=pd.read_csv('D:/PyCharmWorkSpace/ml-100k/ml-100k/u.data')u_genre=pd.rea

使用qt完善对话框功能

1、完善登录框点击登录按钮后,判断账号(admin)和密码(123456)是否一致,如果匹配失败,则弹出错误对话框,文本内容“账号密码不匹配,是否重新登录”,给定两个按钮ok和cancel,点击ok后,会清除密码框中的内容,继续进行登录;如果点击cancel按钮,则关闭界面。如果账号和密码匹配,则弹出信息对话框,给出提

zookeeper + kafka

Zookeeper概述Zookeeper是一个开源的分布式服务管理框架。存储业务服务节点元数据及状态信息,并负责通知再ZooKeeper上注册的服务几点状态给客户端Zookeeper工作机制Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观

Spring 框架的 MethodInterceptor 简介

org.springframework.cglib.proxy.MethodInterceptor是CGLIB库(CodeGenerationLibrary)中的一个接口,用于拦截方法的调用。CGLIB是一个用于生成Java字节码的代码生成库,它通常与SpringAOP一起使用,用于创建动态代理。MethodInter

IaaS,PaaS,SaaS 的区别

越来越多的软件,开始采用云服务。云服务只是一个统称,可以分成三大类。IaaS:基础设施服务,Infrastructure-as-a-servicePaaS:平台服务,Platform-as-a-serviceSaaS:软件服务,Software-as-a-serviceSaaS是软件的开发、管理、部署都交给第三方,不需

Java反序列化和PHP反序列化的区别

文章目录PHP反序列化漏洞什么是反序列化漏洞?修改序列化后的数据,目的是什么?Java反序列化漏洞那么漏洞点在哪里?漏洞成因什么是反序列化漏洞?反序列化存在的意义是为了数据传输,类是无法直接进行传输的。通过序列化后转换为字符串格式或者JSON格式进行传输。序列化与反序列化seriallization序列化:将对象转化为

Java多线程篇(4)——wait/notify和park/unPark

文章目录Object-wait/notifyobject.wait()object.notify()LockSupport-park/unparkLockSupport.park()LockSupport.unPark()Object-wait/notifyobject.wait()ObjectSynchronizer

宏任务,微任务,事件循环event loop与promise、setTimeout、async、nextTick【超详细示例讲解】

目录js单线程宏任务:在主线程上排队执行的任务,顺序执行宏任务macrotask:setTimeout,setInterval定时事件,Ajax,DOM事件,script脚本的执行、I/O操作、UI渲染等。微任务:不进入主线程、而进入"微任务列表"的任务微任务microtask(异步):Promise、async/aw

聚合支付备案对聚合支付系统及安全有何要求?

聚合支付备案,依据《收单外包服务机构备案管理办法》《收单外包服务机构自律规范》《关于加强收单外包服务市场规范管理的意见》等政策,对聚合支付系统及安全要求如下:对聚合支付机构要求一是聚合支付机构应当具备必要的、独立的系统、设施和技术,提供安全、稳定且可持续的聚合支付技术服务。其中,独立的系统是指聚合支付系统逻辑独立并与其

热文推荐