linux DRM GPU scheduler 筆記

itread01 2021-01-21 01:44:40
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。不再需要應用程式的參與。
更詳細的解釋請參考該文章:https://www.collabora.com/news-and-blog/blog/2016/09/13/mainline-explicit-fencing-part-1/。
 
在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 }

 

參考資料:
 
https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#gpu-scheduler
https://rosenzweig.io/blog/from-bifrost-to-panfrost.html
https://www.collabora.com/news-and-blog/blog/2017/01/26/mainline-explicit-fencing-part-3/
&nbs
版权声明
本文为[itread01]所创,转载请带上原文链接,感谢
https://www.itread01.com/content/1611162242.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课程百度云