linux DRM GPU scheduler 笔记

yaongtime 2021-01-20 22:31:53
linux GPU 笔记 scheduler drm


内核文档:
 
Overview
 
The GPU scheduler provides entities which allow userspace to push jobs into software queues which are then scheduled on a hardware run queue. The software queues have a priority among them. The scheduler selects the entities from the run queue using a FIFO. The scheduler provides dependency handling features among jobs. The driver is supposed to provide callback functions for backend operations to the scheduler like submitting a job to hardware run queue, returning the dependencies of a job etc.
 
The organisation of the scheduler is the following:
1. Each hw run queue has one scheduler
2. Each scheduler has multiple run queues with different priorities (e.g., HIGH_HW,HIGH_SW, KERNEL, NORMAL)
3. Each scheduler run queue has a queue of entities to schedule
4. Entities themselves maintain a queue of jobs that will be scheduled on the hardware.
The jobs in a entity are always scheduled in the order that they were pushed.
 
原理概述:
 
众所周知,现代GPU给CPU提供了下发命令流(command stream)接口,而这些命令流用于控制GPU硬件,下发着色程序,以及传递OpenGL或vulkan所需的状态值等。
linux中的GPU scheduler正是用于GPU命令流的调度,这部分代码是从AMD GPU driver中的独立出来的。
从内核文档的描述中可知,GPU scheduler为用户程序提供了entities,可用于用户程序向其提交jobs,这些jobs先被加入到software queue上,然后再经调度器调度到hardware(GPU)上。
GPU上一个命令流通道对应一个GPU scheduler。
GPU scheduler调度策略有两个,一是按优先级调度,二是同等优先级下先入队的先调度,即FIFO模式。GPU scheduler通过回调函数的方法实现不同硬件的jobs提交。
在jobs被提交到硬件之前GPU scheduler提供了依赖项检查特性,只有当jobs的所有依赖项全部可用时,才会被提交到硬件上。
 
GPU上一个命令流通道对应一个gpu scheduler,一个gpu scheduler上包含多个run queue,这些run queue代表了不同的优先级。
当有一个新的job需要提交到GPU上时,先被提交到entities上,被提交的entity通过负载均衡算法,确定该entity最终会被调度到的gpu scheduler,并把entity加入到选中的gpu scheduler的run Queue的列表中,等待被调度。
 
 
基本使用方法:
1.GPU scheduler在能工作前,需要对其初始化,并提供与硬件操作相关的回调函数,而GPU上的一个命令流通道对应一个scheduler
2.在scheduler中有software run queue,这些run queue对应不同的优先级,优先级高的run queue优先调度
3.新的job首先被提交到entity上,而后entity被加入到scheduler的run queue的队列中。在相同优先级下,job和entity按先进先出规则调度(FIFO)
4.当scheduler开始调度时,首先从优先级最高的run queue中选出最先进入的entity,再从选出的entity中,选出最先加入的job
5.在一个job能被提交到GPU HW 上前,需要做依赖性项检测,比如即将render的framebuffer是否可用(依赖检测也是通过回调函数的实现的)
6.scheduler选出的job,最终需要通过初始化中实现的回调函数,将job提交到的GPU的hardware run queue上
7.在GPU处理完一个job后,通过dma_fence的callback通知GPU scheduler,并signal finish fence
 
1.注册sched
 
struct drm_gpu_scheduler;
int drm_sched_init(struct drm_gpu_scheduler *sched, //sched: scheduler instance
const struct drm_sched_backend_ops *ops, //ops: backend operations for this scheduler
unsigned hw_submission, //hw_submission: number of hw submissions that can be in flight
unsigned hang_limit, //hang_limit: number of times to allow a job to hang before dropping it
long timeout, //timeout: timeout value in jiffies for the scheduler
const char *name) //name: name used for debugging
通过函数drm_sched_init()完一个struct drm_gpu_scheduler *sched的初始化工作。
一旦成功完成,会启动一个内核线程,这个内核线程中实现了主要的调度逻辑。
内核线程处于等待状态,直到有新的job被提交,并唤醒调度作业。
 
hw_submission:指定GPU上单个命令通道可同时提交的命令数。
 
通过函数drm_sched_init()初始化一个GPU scheduler。其中通过参数const struct drm_sched_backend_ops *ops提供平台相关的回调接口。
 
struct drm_sched_backend_ops {
struct dma_fence *(*dependency)(struct drm_sched_job *sched_job,
struct drm_sched_entity *s_entity);
struct dma_fence *(*run_job)(struct drm_sched_job *sched_job);
void (*timedout_job)(struct drm_sched_job *sched_job);
void (*free_job)(struct drm_sched_job *sched_job);
}

 

dependency:当一个job被视作下一个调度对象时,会调用该接口。如果该job存在依赖项,需返回一个dma_fence指针,GPU scheduler会在这个返回的dma_fence的callback list中添加唤醒操作,一旦该fence被signal,就能再次唤醒GPU scheduler。如果不存在任何依赖项,则返回NULL。
run_job:一旦job的所有依赖项变得可用后,会调用该接口。这个接口主要是实现GPU HW相关的命令提交。该接口成功把命令提交到GPU上后,返回一个dma_fence,gpu scheduler会向这个dma_fence的callback list中添加finish fence唤醒操作,而这个dma_fence一般会在GPU处理完毕该job后被signal。
timedout_job:当一个提交到GPU执行时间过长时,该接口会被调用,以触发GPU执行恢复的处理流程。
free_job:用做当job被处理完毕后的相关资源释放工作。
 
2.初始化entities
int drm_sched_entity_init(struct drm_sched_entity *entity,
enum drm_sched_priority priority,
struct drm_gpu_scheduler **sched_list,
unsigned int num_sched_list,
atomic_t *guilty)
初始化一个struct drm_sched_entity *entity。
priority:指定entity的优先级,目前支持的优先级有(由低到高):
  
 DRM_SCHED_PRIORITY_MIN
DRM_SCHED_PRIORITY_NORMAL
DRM_SCHED_PRIORITY_HIGH
DRM_SCHED_PRIORITY_KERNEL

 

sched_list:entity中job可以被提交的gpu scheduler列表。当gpu存在多个gpu命令流通道时,这样同一个job就有多个潜在的可被提交的通道(HW相关,需要gpu支持),sched_list中即保存了这些潜在的通道。
当一个entity有多个Gpu scheduler时,drm scheduler支持负载均衡算法。
num_sched_list:指定了sched_list中gpu scheduler的个数。
 
函数drm_sched_entity_init()可在open()函数中被调用,这样当多个应用程序调用各自open()函数后,driver会为每个应用程序创建一个entity。
如前文所述,entity是job的提交点,gpu上的一个命令流通道对应一个gpu scheduler,当有多个应用程序同时向同一个gpu命令流通道提交job时,job先被加入到各自的entity上,再等待gpu scheduler的统一调度。
 
3.初始化job
int drm_sched_job_init(struct drm_sched_job *job,
struct drm_sched_entity *entity,
void *owner)
entity:指定job会被提交到的entity。如果entity的sched_list大于一个时,会调用负载均衡算法,从entity的sched_list中挑选一个最佳的gpu scheduler进行job调度。
函数drm_sched_job_init()会为该job初始化两个dma_fence:scheduled 和finished,当scheduled fence被signaled,表明该job要被发送到GPU上,当finished fence被signaled,表明该job已在gpu上处理完毕。
所以通过这两个fence可以告知外界job当前的状态。
 
4.提交job
 
void drm_sched_entity_push_job(struct drm_sched_job *sched_job,
struct drm_sched_entity *entity)
当一个job被drm_sched_job_init()初始化后,就可以通过函数 drm_sched_entity_push_job()提交到entity的job_queue上了。
如果entity是首次被提交job到其上的job_queue上,该entity会被加入到gpu scheduler的run queue上,并唤醒gpu scheduler上的调度线程。
 
5.dma_fence的作用
 
DMA fence是linux中用于不同内核模块DMA同步操作的原语,常用于GPU rendering、displaying buffer等之间的同步。
使用DMA FENCE可以减少在用户态的等待,让数据的同步在内核中进行。例如GPU rendering和displaying buffer的同步,GPU rendering负责向framebuffer中写入渲染数据,displaying负责将frambuffer中数据显示到屏幕上。那么displaying需要等待GPU rendering完成后,才能读取framebuffer中的数据(反过来也一样,gpu rendering也需要等displaying显示完毕后,才能在framebuffer上画下一帧图像)。我们可以在应用程序中去同步两者,即等rendering结束后,才调用displaying模块。在等待的过程中应用程序往往会休眠,不能做其他事情(比如准备下一帧framebuffer的渲染)。等displaying显示完毕后,再调用GPU rendering也会使gpu不饱和,处于空闲状态。
使用DMA fence后,将GPU rendering和displaying的同步放到内核中,应用程序调用GPU rendering后,返回一个out fence,不用等GPU rendering的完成,即可调用displaying,并把GPU rendering的out fence传递给displaying模块作为in fence。这样在内核中displaying模块要做显示输出前会等待in fence被signal,而一旦GPU rendering完成后,就会signal该fence。不再需要应用程序的参与。
 
在Gpu scheduler中,一个job在被调度前要确定其是否有依赖项,一个job被调度后需要告知外界自己当前的状态,这两者均是通过fence来实现的。
job的依赖项叫 in_fence,job自身状态报告叫out_fence,本质上都是相同的数据结构。
当存在in fence且不处于signaled状态,该job需要继续等待(这里的等待方式不是通过调用dma_fence_wait()),直到所有的in fence被signal。在等待前Gpu scheduler会注册一个新的回调接口dma_fence_cb到in fence上(其中包含唤醒Gpu scheduler调度程序的代码),当in fence被signal时,这个callback会被调用,在其中再次唤醒对该job的调度。
一个job有两个out fence:scheduled 和finished,当scheduled fence被signaled,表明该job要被发送到GPU上,当finished fence被signaled,表明该job已在gpu上处理完毕。
 
以下在VC4上的测试代码:
Vc4 driver没有使用drm的gpu scheduler作为调度器。VC4在in fence的处理上是blocking式的,即应用程序会阻塞在这里,这里似乎不符合fence的初衷。且当前driver也只支持单个in fence,而vulkan上可能传入多个依赖项。
实际的测试发现,与原本的driver相比使用gpu scheduler没有带来明显的性能变化,但可以解决fence的问题,当然主要的目的还是练手,主要参考了v3d driver的代码。
 
1.首先是调用 drm_sched_init()创建scheduler。Vc4上对应两个命令入口,bin和render,所以这里创建两个scheduler。
 
 1 static const struct drm_sched_backend_ops vc4_bin_sched_ops = {
 2 .dependency = vc4_job_dependency,
 3 .run_job = vc4_bin_job_run,
 4 .timedout_job = NULL,
 5 .free_job = vc4_job_free,
 6 };
 7
 8 static const struct drm_sched_backend_ops vc4_render_sched_ops = {
 9 .dependency = vc4_job_dependency,
10 .run_job = vc4_render_job_run,
11 .timedout_job = NULL,
12 .free_job = vc4_job_free,
13 };
14
15 int vc4_sched_init(struct vc4_dev *vc4)
16 {
17 int hw_jobs_limit = 1;
18 int job_hang_limit = 0;
19 int hang_limit_ms = 500;
20 int ret;
21
22 ret = drm_sched_init(&vc4->queue[VC4_BIN].sched,
23 &vc4_bin_sched_ops,
24  hw_jobs_limit,
25  job_hang_limit,
26  msecs_to_jiffies(hang_limit_ms),
27 "vc4_bin");
28 if (ret) {
29 dev_err(vc4->base.dev, "Failed to create bin scheduler: %d.", ret);
30 return ret;
31  }
32
33 ret = drm_sched_init(&vc4->queue[VC4_RENDER].sched,
34 &vc4_render_sched_ops,
35  hw_jobs_limit,
36  job_hang_limit,
37  msecs_to_jiffies(hang_limit_ms),
38 "vc4_render");
39 if (ret) {
40 dev_err(vc4->base.dev, "Failed to create render scheduler: %d.", ret);
41  vc4_sched_fini(vc4);
42 return ret;
43  }
44
45 return ret;
46 }
 
2.在drm driver的open回调接口中添加,entity的初始代码。
 1 static int vc4_open(struct drm_device *dev, struct drm_file *file)
 2 {
 3 struct vc4_dev *vc4 = to_vc4_dev(dev);
 4 struct vc4_file *vc4file;
 5 struct drm_gpu_scheduler *sched;
 6 int i;
 7
 8 vc4file = kzalloc(sizeof(*vc4file), GFP_KERNEL);
 9 if (!vc4file)
10 return -ENOMEM;
11
12  vc4_perfmon_open_file(vc4file);
13
14 for (i = 0; i < VC4_MAX_QUEUES; i++) {
15 sched = &vc4->queue[i].sched;
16 drm_sched_entity_init(&vc4file->sched_entity[i],
17  DRM_SCHED_PRIORITY_NORMAL,
18 &sched, 1,
19  NULL);
20  }
21
22 file->driver_priv = vc4file;
23
24 return 0;
25 }
 
3.在driver完成了job的打包后,就可以向entity上提交job了。
 1 static void vc4_job_free(struct kref *ref)
 2 {
 3 struct vc4_job *job = container_of(ref, struct vc4_job, refcount);
 4 struct vc4_dev *vc4 = job->dev;
 5 struct vc4_exec_info *exec = job->exec;
 6 struct vc4_seqno_cb *cb, *cb_temp;
 7 struct dma_fence *fence;
 8 unsigned long index;
 9 unsigned long irqflags;
 10
 11 xa_for_each(&job->deps, index, fence) {
 12  dma_fence_put(fence);
 13  }
 14 xa_destroy(&job->deps);
 15
 16 dma_fence_put(job->irq_fence);
 17 dma_fence_put(job->done_fence);
 18
 19 if (exec)
 20 vc4_complete_exec(&job->dev->base, exec);
 21
 22 spin_lock_irqsave(&vc4->job_lock, irqflags);
 23 list_for_each_entry_safe(cb, cb_temp, &vc4->seqno_cb_list, work.entry) {
 24 if (cb->seqno <= vc4->finished_seqno) {
 25 list_del_init(&cb->work.entry);
 26 schedule_work(&cb->work);
 27  }
 28  }
 29
 30 spin_unlock_irqrestore(&vc4->job_lock, irqflags);
 31
 32  kfree(job);
 33 }
 34
 35 void vc4_job_put(struct vc4_job *job)
 36 {
 37 kref_put(&job->refcount, job->free);
 38 }
 39
 40 static int vc4_job_init(struct vc4_dev *vc4, struct drm_file *file_priv,
 41 struct vc4_job *job, void (*free)(struct kref *ref), u32 in_sync)
 42 {
 43 struct dma_fence *in_fence = NULL;
 44 int ret;
 45
 46 xa_init_flags(&job->deps, XA_FLAGS_ALLOC);
 47
 48 if (in_sync) {
 49 ret = drm_syncobj_find_fence(file_priv, in_sync, 0, 0, &in_fence);
 50 if (ret == -EINVAL)
 51 goto fail;
 52
 53 ret = drm_gem_fence_array_add(&job->deps, in_fence);
 54 if (ret) {
 55  dma_fence_put(in_fence);
 56 goto fail;
 57  }
 58  }
 59
 60 kref_init(&job->refcount);
 61 job->free = free;
 62
 63 return 0;
 64
 65 fail:
 66 xa_destroy(&job->deps);
 67 return ret;
 68 }
 69
 70 static int vc4_push_job(struct drm_file *file_priv, struct vc4_job *job, enum vc4_queue queue)
 71 {
 72 struct vc4_file *vc4file = file_priv->driver_priv;
 73 int ret;
 74
 75 ret = drm_sched_job_init(&job->base, &vc4file->sched_entity[queue], vc4file);
 76 if (ret)
 77 return ret;
 78
 79 job->done_fence = dma_fence_get(&job->base.s_fence->finished);
 80
 81 kref_get(&job->refcount);
 82
 83 drm_sched_entity_push_job(&job->base, &vc4file->sched_entity[queue]);
 84
 85 return 0;
 86 }
 87
 88 /* Queues a struct vc4_exec_info for execution. If no job is
 89 * currently executing, then submits it.
 90 *
 91 * Unlike most GPUs, our hardware only handles one command list at a
 92 * time. To queue multiple jobs at once, we'd need to edit the
 93 * previous command list to have a jump to the new one at the end, and
 94 * then bump the end address. That's a change for a later date,
 95 * though.
 96 */
 97 static int
 98 vc4_queue_submit_to_scheduler(struct drm_device *dev,
 99 struct drm_file *file_priv,
100 struct vc4_exec_info *exec,
101 struct ww_acquire_ctx *acquire_ctx)
102 {
103 struct vc4_dev *vc4 = to_vc4_dev(dev);
104 struct drm_vc4_submit_cl *args = exec->args;
105 struct vc4_job *bin = NULL;
106 struct vc4_job *render = NULL;
107 struct drm_syncobj *out_sync;
108  uint64_t seqno;
109 unsigned long irqflags;
110 int ret;
111
112 spin_lock_irqsave(&vc4->job_lock, irqflags);
113
114 seqno = ++vc4->emit_seqno;
115 exec->seqno = seqno;
116
117 spin_unlock_irqrestore(&vc4->job_lock, irqflags);
118
119 render = kcalloc(1, sizeof(*render), GFP_KERNEL);
120 if (!render)
121 return -ENOMEM;
122
123 render->exec = exec;
124
125 ret = vc4_job_init(vc4, file_priv, render, vc4_job_free, args->in_sync);
126 if (ret) {
127  kfree(render);
128 return ret;
129  }
130
131 if (args->bin_cl_size != 0) {
132 bin = kcalloc(1, sizeof(*bin), GFP_KERNEL);
133 if (!bin) {
134  vc4_job_put(render);
135 return -ENOMEM;
136  }
137
138 bin->exec = exec;
139
140 ret = vc4_job_init(vc4, file_priv, bin, vc4_job_free, args->in_sync);
141 if (ret) {
142  vc4_job_put(render);
143  kfree(bin);
144 return ret;
145  }
146  }
147
148 mutex_lock(&vc4->sched_lock);
149
150 if (bin) {
151 ret = vc4_push_job(file_priv, bin, VC4_BIN);
152 if (ret)
153 goto FAIL;
154
155 ret = drm_gem_fence_array_add(&render->deps, dma_fence_get(bin->done_fence));
156 if (ret)
157 goto FAIL;
158  }
159
160  vc4_push_job(file_priv, render, VC4_RENDER);
161
162 mutex_unlock(&vc4->sched_lock);
163
164 if (args->out_sync) {
165 out_sync = drm_syncobj_find(file_priv, args->out_sync);
166 if (!out_sync) {
167 ret = -EINVAL;
168 goto FAIL;;
169  }
170
171 drm_syncobj_replace_fence(out_sync, &bin->base.s_fence->scheduled);
172 exec->fence = render->done_fence;
173
174  drm_syncobj_put(out_sync);
175  }
176
177  vc4_update_bo_seqnos(exec, seqno);
178
179  vc4_unlock_bo_reservations(dev, exec, acquire_ctx);
180
181 if (bin)
182  vc4_job_put(bin);
183  vc4_job_put(render);
184
185 return 0;
186
187 FAIL:
188 return ret;
189 }

 

参考资料:
 
 
版权声明
本文为[yaongtime]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/yaongtime/p/14305463.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云