内核文档:
 
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 }
参考资料:
 
 

linux DRM GPU scheduler 笔记的更多相关文章

  1. linux DRM 之 GEM 笔记

    原文链接:https://www.cnblogs.com/yaongtime/p/14418357.html 在GPU上的各类操作中涉及到多种.多个buffer的使用. 通常我们GPU是通过图像API ...

  2. Linux DRM KMS 驱动简介【转】

    转自:https://blog.csdn.net/yangkuanqaz85988/article/details/48689521 Whoops,上次写完<Linux DRM Graphic ...

  3. linux DRM/KMS 测试工具 modetest、kmscude、igt-gpu-tools (一)

    这里整理几个在学习Linux DRM/KMS中用到的几个工具,modetest.kmscude.igt-gpu-tools. 简介: modetest 是由libdrm提供的测试程序,可以查询显示设备 ...

  4. linux DRM/KMS 测试工具 modetest、kmscude、igt-gpu-tools (二)

    kmscube   kmscube is a little demonstration program for how to drive bare metal graphics without a c ...

  5. linux 2.6 驱动笔记(一)

    本文作为linux 2.6 驱动笔记,记录环境搭建及linux基本内核模块编译加载. 环境搭建: 硬件:OK6410开发板 目标板操作系统:linux 2.6 交叉编译环境:windows 7 + v ...

  6. Linux内核分析课程笔记(一)

    linux内核分析课程笔记(一) 冯诺依曼体系结构 冯诺依曼体系结构实际上就是存储程序计算机. 从两个层面来讲: 从硬件的角度来看,冯诺依曼体系结构逻辑上可以抽象成CPU和内存,通过总线相连.CPU上 ...

  7. Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)

    Linux进程间通信IPC学习笔记之同步二(SVR4 信号量)

  8. Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

    Linux进程间通信IPC学习笔记之同步二(Posix 信号量)

  9. Linux进程间通信IPC学习笔记之消息队列(SVR4)

    Linux进程间通信IPC学习笔记之消息队列(SVR4)

随机推荐

  1. Docker安装RabbitMQ与Kafka

    RabbitMq安装(dokcer) 下载镜像 docker pull rabbitmq 创建并启动容器 docker run -d --name rabbitmq -p 5672:5672 -p 1 ...

  2. AOP基本概念

    连接点joinpoint(类中所有方法) 切入点pointcut(缺少共性代码的方法) 通知advice(被抽取的共性功能的代码逻辑,通知有位置区分,也就是从切入点方法中被抽取代码的前面还是后面抽象出 ...

  3. IOS开发中设置导航栏主题

    /** * 系统在第一次使用这个类的时候调用(1个类只会调用一次) */ + (void)initialize { // 设置导航栏主题 UINavigationBar *navBar = [UINa ...

  4. C#跳过工作日,计算几个工作日之后到期的方法

    需求:消费者投诉企业,企业在2个工作日之内做出应答. 分析:1.工作日要刨去周末和法定节假日,而且每年的节假日不一样. 2.消费者可以在任意时间发起投诉,如果在非工作日发起了投诉,那么计算时间应该从工 ...

  5. 一段小代码秒懂C++右值引用和RVO(返回值优化)的误区

    关于C++右值引用的参考文档里面有明确提到,右值引用可以延长临时变量的周期.如: std::string&& r3 = s1 + s1; // okay: rvalue referen ...

  6. JDBC中SQL语句与变量的拼接

    变量为 keyWords String sql = "select id from t_user order by id + '"+keyWords+"';";

  7. Linux 搭建ELK日志收集系统

    在搭建ELK之前,首先要安装Redis和JDK,安装Redis请参考上一篇文章. 首先安装JDK及配置环境变量 1.解压安装包到/usr/local/java目录下[root@VM_0_9_cento ...

  8. javascript笔记day01

    JavaScript基础语法 HTML :标记语言 JavaScript :编程语言 序言 JavaScript发展历史(JS) 1. 1994年,网景公司(Netscape)发布了Navigator ...

  9. 解决No Python interpreter for the object

    1. File --> Settings 2. Project:[项目名] --> Project Interpreter --> 点击齿轮 3. 点击齿轮出现Add,点击Add 4 ...

  10. MPEG2TS文件格式概述

    总结TS文件格式,早在几个月前就有了这个想法,但一直拖到今天才真正准备写一篇博文来介绍. 再不介绍的话,估计几月后又要去故纸堆里翻东西了,毕竟个人笔记中总结记录的东西太多,搞不好哪天给意外弄丢了. 1 ...