文章目录
前言
本系列内容力求将nvdla的用户态驱动整理清楚,如果有分析不对的请指出。
前面已经花了不少章节详细解释NVDLA内核态驱动代码,链接分别如下:
系列文章1:NVDLA内核态驱动代码整理一
系列文章2:NVDLA内核态驱动代码整理二
系列文章3:NVDLA内核态驱动代码整理三
系列文章4:NVDLA内核态驱动代码整理四
系列文章5:NVDLA内核态驱动代码整理五
系列文章6:NVDLA内核态驱动代码整理六
系列文章7:NVDLA内核态驱动代码整理七
系列文章8:NVDLA内核态驱动代码整理八
系列文章8:NVDLA内核态驱动代码整理汇总篇
欢迎阅读硬件信号和架构分析系列文章1:
架构开篇介绍文章:NVDLA内核态驱动代码整理三
系列文章1:NVDLA硬件信号和架构设计整理一
系列文章2:NVDLA硬件信号和架构设计整理二
系列文章3:NVDLA硬件信号和架构设计整理三
这是用户态解读的第二篇,也是收官之作。(不过后面有机会解读行为级代码的时候另说,可能会花大把时间搞明白RTL的寄存器是啥规范!)
一、Runtime.cpp代码解读
按照官网的描述,使用运行时接口进行推测任务需要完成以下步骤:
1、Create NVDLA runtime instance
# 包括IRuntime *nvdla::createRuntime()函数
2、Get NVDLA device information
# 包括NvU16 nvdla::IRuntime::getMaxDevices()函数、NvU16 nvdla::IRuntime::getNumDevices()函数
3、Load network data
# 包括NvError nvdla::IRuntime::load(const NvU8 *buf, int instance)函数
4、Get input and output tensors information
# 包括NvError nvdla::IRuntime::getNumInputTensors(int *input_tensors)函数、NvError nvdla::IRuntime::getInputTensorDesc(int id, NvDlaTensor *tensors)函数、NvError nvdla::IRuntime::getNumOutputTensors(int *output_tensors)函数、NvError nvdla::IRuntime::getOutputTensorDesc(int id, NvDlaTensor *tensors)函数
5、Update input and output tensors information
# 包括NvError nvdla::IRuntime::setInputTensorDesc(int id, const NvDlaTensor *tensors)函数、NvError nvdla::IRuntime::setOutputTensorDesc(int id, const NvDlaTensor *tensors)函数
6、Allocate memory for input and output tensors
# 包括NvDlaError allocateSystemMemory(void **h_mem, NvU64 size, void **pData)函数
7、Bind memory handle with tensor
# 包括NvError nvdla::IRuntime::bindInputTensor(int id, void *hMem)函数、NvError nvdla::IRuntime::bindOutputTensor(int id, void *hMem)函数
8、Submit task for inference
# 包括NvError nvdla::IRuntime::submit()函数
9、Unload network resources
# 包括NvError nvdla::IRuntime::unload(int instance)函数
我们掌握到这一层足矣!
二、nvdla.c函数整理
本来打算机械式逐个解释nvdla.c
的函数,后来想到其实这个文件内的所有函数直接看函数名就可以理解。尽管用户态函数确实定义十分清楚,但是我们始终没有搞明白几件事情!因此接下来我打算就几件事情拨开nvdla.c
函数的某些有意思的点。
关于nvdla_os.c
的函数不再赘述。直接看函数名就可以理解函数功能,和Runtime.cpp
和nvdla.c
类似。
三、由nvdla.c引发的几个问题
这3个问题分别是怎么联系用户态和内核态?明明我只insmod
了一个.ko
,但为什么出现了2个不同结构体下的fd
?为什么memset
和mmap
没有同时出现在fops
内?可能这三个问题会帮助理解一部分究竟什么是用户态?
3.1 两个不同的结构体都有fd,但只insmod了1个ko?
关于第1个结构体:NvDlaMemHandleRec
,使用情况如下:
关于第2个结构体:NvDlaMemHandle
,使用情况如下:
(以上2个结构体完全一致!)
关于第3个结构体:NvDlaHandleRec
,使用情况如下:无
关于第4个结构体:NvDlaHandle
,使用情况如下:无
(以上2个结构体完全一致!)
关于第5个结构体:NvDlaContextRec
,使用情况如下:无
关于第6个结构体:NvDlaContext
,使用情况如下:
关于第7个结构体:NvDlaDeviceHandle
,使用情况如下:
(以上3个结构体完全一致!)
所以答案显而易见,有两套fd,那哪一套对应哪一套?
先做个整理:
驱动对应fd | 结构体1 | 结构体2 |
---|---|---|
1 | NvDlaMemHandleRec | NvDlaMemHandle |
2 | NvDlaContext | NvDlaDeviceHandle |
两套fd
究竟怎么来的?先观察在加载opendla.ko
时打印的输出情况:
我们在这里介绍DRM时看到一张久违的图片:
DRM
框架成功加载后,会创建一个设备文件/dev/dri/card0
,上层用户应用可以通过该文件节点,获取显卡的各种操作。【不过始终无法解释一个问题:为什么用户态中/dev/dri/renderD128
在open
的时候加载了,DRM
的card0
却不需要加载,但是其中一步close
关闭了?我怀疑有一种可能就是renderD128
这个opendla
的驱动已经包含了DRM
的card0
驱动!】
这也就解释了为什么出现了两个fd
。
3.2 怎么联系用户态和内核态?
想到这个问题的原因是,用户态也有和内核态一样的open
等操作。那到底是怎么回事?
从ioctl
函数出发,从用户态再看到内核态。先找找该文件下的ioctl
出现的情况:
第一处:
其中宏DRM_IOCTL_NVDLA_GEM_CREATE
指的是:
第二处:
然而此处的宏DRM_IOCTL_PRIME_HANDLE_TO_FD
并不在上述截图中,而是在drm.h
中:
第三处:
根据linux
官方对于ioctl
的解释:
ioctl()系统调用用于操纵特殊文件的底层设备参数。特别是,许多字符特殊文件(例如终端)的操作特性可以通过ioctl()请求进行控制。参数fd必须是一个打开的文件描述符。第2个参数是一个与设备相关的请求代码。第3个参数是一个untyped的内存指针。通常,成功时返回零。一些ioctl()请求将返回值用作输出参数,并在成功时返回非负值。发生错误时,返回-1,并设置errno以指示错误。
所以用户态传入的参数本质上补充第二个参数:
继续追溯宏函数:
在linux官网继续追溯:
继续:
基本上可以确认本质就是四个参数做移位!那么用户态ioctl
怎么对应内核态ioctl
?
在内核态找到ioctl
:
随后该nvdla_drm_ioctls
提交给nvdla_drm_driver
结构体:
注意尽管在用户态代码中出现memset
、malloc
这些函数,结合fs.h
中的file operation
结构体基本可以确认这些函数不是file operation
中的。
ioctl
或许是一个特例,我们再看看open
是怎么一回事?
用户态的open
函数=>高级版本抽象的do_sys_open
函数=>借助file operation
下的特定文件系统类型
的open
函数。
所以调用链就是:用户态open
调用=>内核态do_sys_open
函数=>文件系统的open
函数。
自此,案件已破!
3.3 memset/malloc和mmap没有同时出现在fops内?
到这里为止,大部分问题我们已经搞明白了!从看NVDLA
的代码以来,一直很疑惑内核态和用户态到底是怎么关联的?我也遇到过各种奇奇怪怪的bug,但所幸有着自己的耐心一路推过去。也有各种各样的疑惑,有时候疑惑满脑子,有种剪不断、理还乱的感觉,还好,认知内各种各样的问题都一一去尝试解释。可以说,这一章的内容,突破了同辈们在搭建NVDLA
过程中实现全栈建立的已有知识范畴,加入了我自己的理解和感悟!如果您对我的想法有疑惑或者质疑,欢迎提出,非常乐意交流。
总结
总结,撒花,欢迎阅读!