• 欢迎访问开心洋葱网站,在线教程,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站,欢迎加入开心洋葱 QQ群
  • 为方便开心洋葱网用户,开心洋葱官网已经开启复制功能!
  • 欢迎访问开心洋葱网站,手机也能访问哦~欢迎加入开心洋葱多维思维学习平台 QQ群
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏开心洋葱吧~~~~~~~~~~~~~!
  • 由于近期流量激增,小站的ECS没能经的起亲们的访问,本站依然没有盈利,如果各位看如果觉着文字不错,还请看官给小站打个赏~~~~~~~~~~~~~!

linux GPU上多个buffer间的同步之ww_mutex、dma_fence的使用 笔记

其他 开心洋葱 1579次浏览 0个评论

原文链接:https://www.cnblogs.com/yaongtime/p/14111134.html  
WW-Mutexes   在GPU中一次Render可能会涉及到对多个buffer的引用。 所以在command buffer提交到GPU前,需要等到所有依赖的buffer可用。 因为这些buffer可能被多个设备或进程所共享,所以相比单个buffer,增加了deadlock的风险。 这不能简单地通过一个 buffer mutex锁来等待buffer可用,因为这些buffer通常受控于应用程序. 比如Vertex shader中用到的vertex data: input attributes buffer 和 vertex index buffer,或者是fragment shader中用到的texel buffer、uniform buffer等,以及用作render的frambuffer。 所以没有一个机制能保证这些buffer以相同的顺序出现在各个共享进程中。 为解决这样的问题linux kernel中引入了WW-Mutexes锁。 WW-Mutexes与mutex是本质上是相同的,加锁的方式也类似。   WW-Mutexes的工作机制大概是,首先将要引用的buffer的锁加入到一个list里面,然后依次对list中的锁进行上锁操作,对单个锁的获取可能会失败,即该锁已被其他人占用。 当出现锁获取失败时,接下来WW-Mutexes会有分两种情况来解决冲突: 1.如果当前正在加锁的进程(transaction)比已加锁的进程新(younger),那么当前进程的加锁操作会被终止,停止之前释放(unlock)已成功获取到的锁,然后等待重新对list的锁进行再次加锁操作。 2.如果当前正在加锁的进程(transaction)比已加锁的进程旧(older),那么当前进程会等待,直到占用该锁的进程释放锁。   举个例子: A:     lock-list: B0, B1, B2, B3     locked:    B0, B1, B2     locking:   B3 B:     lock-list: B1, B3, B4     locked:    B3     locking:   B1 1.如上有A、B两个进程,假如B比A晚(younger)启动,B进程正在对B1进行加锁,但是B1已被A进程上锁了,所以B进程加锁失败,因为B比A新,所以B需要释放到它已获取到的锁B4,然后重新等待对B1,B3,B4的加锁。 2.相反,如果B比A早(older)启动,那么B对B1加锁失败后,B会等待,直到B1被A释放。接着看A的情况,A正在对B3进行加锁(假设A在B开始等待后对B3加锁),但B3已被B锁住,按同样的规则,这是A比B新,那么A要被中断,并释放掉已获取到的B0、B1、B2,并重新开始下一轮对B0, B1, B2, B3进行加锁。因为A释放掉B1,那么B就能停止等待,获取到B1了。一旦获取到B lock-list中所有buffer的锁后,B就能对这些buffer进行相应的操作,完毕后再释放掉所有的锁,A进程也就有机会重新获取到所需的锁了。   使用方法: 官方文档列举了3种用法,这里只列出一种,其他请参考:https://www.kernel.org/doc/html/latest/locking/ww-mutex-design.html   /* 静态初始化一个 ww_class */ static DEFINE_WW_CLASS(ww_class);   /* 要被加锁的对象,在其中嵌入 struct ww_mutex lock */ struct obj {       struct ww_mutex lock;       /* obj data */ };   /* 需要获取的对象组成的list */ struct obj_entry {       struct list_head head;       struct obj *obj; };   int lock_objs(struct list_head *list, struct ww_acquire_ctx *ctx) {       struct obj *res_obj = NULL;       struct obj_entry *contended_entry = NULL;       struct obj_entry *entry;          /* 加锁前对ww_acquire_ctx进行初始化 */       
ww_acquire_init(ctx, &ww_class);     retry:       /* 一次从list中取出要加锁的对象,并对其进行加锁操作 */       list_for_each_entry (entry, list, head) {               if (entry->obj == res_obj) {                       res_obj = NULL;                       continue;               }           /* 加锁操作,如果出现冲突,且当前进程较旧,会等待在 ww_mutex_lock()中,与mutex_lock()类似 */               ret =
ww_mutex_lock(&entry->obj->lock, ctx);               if (ret < 0) {             /* 加锁失败,并且当前进行较新,当前进行将被终止继续获取剩余的锁,记录下冲突对象 */                       contended_entry = entry;                       goto err;               }       }         ww_acquire_done(ctx);       return 0;   err:       /* 在进行下一轮加锁前,释放掉已获取到的锁 */       list_for_each_entry_continue_reverse (entry, list, head)               
ww_mutex_unlock(&entry->obj->lock); /* 与mutex_unlock类似 */         if (res_obj)               ww_mutex_unlock(&res_obj->lock);         if (ret == -EDEADLK) {         /* 在开始下一轮的加锁前,使用ww_mutex_lock_slow()获取上一轮有冲突的锁,ww_mutex_lock_slow()会一直休眠,直到该锁可用为止 */               /* we lost out in a seqno race, lock and retry.. */               
ww_mutex_lock_slow(&contended_entry->obj->lock, ctx);               res_obj = contended_entry->obj;         /* 跳转到下一轮的加锁操作 */               goto retry;       }       ww_acquire_fini(ctx);         return ret; }   void unlock_objs(struct list_head *list, struct ww_acquire_ctx *ctx) {     struct obj_entry *entry;       list_for_each_entry (entry, list, head)         ww_mutex_unlock(&entry->obj->lock);  //依次释放list中的锁       ww_acquire_fini(ctx); }  
dma_resv   GEM object主要是提供了graphics memory manager,正是前文中提到的GPU buffer对象(linux kernel中还有其他的buffer管理对象)。 本文主要整理了GEM的中用到的同步方法,不对其他方面做讲解。 GEM中主要用到WW-Mutexes和dma-fence来做同步,而这两者被封装到dma_resv中。 而dma_resv实际上是提供了所谓的隐式同步(implicit synchronization、implicit fence)。 reservation object提供了管理共享和独占fence的机制。 一个reservation object上只能添加一个独占fence(通常对于写操作),或添加多个共享fence(读操作)。 这类似于RCU的概念,一个reservation object管理的对象能支持并发的read操作,但是只支持同时一个写入操作。   Dma-fence是用在kernel内部的跨设备(cross-device)的DMA操作同步原语,比如GPU向framebuffer做rendering,而displaying在读取framebuffer前需要确保GPU已完成rendering操作,即读操作之前,确保写操作已完成。 Dma-fence通常有两种状态,signaled 和 unsignaled。在这里,通常unsignaled表示buffer还在被使用,signaled表示buffer已使用完毕。 因为Dma-fence是为跨设备间的同步而设计,这里有多种使用dma-fence方式: 1、explicit fencing:单个dma-fence通过以文件描述符(file descriptor)的形式暴露给用户层,用户层可以把该文件描述符传递给其他进程,因为是对应用层可见的,所以叫这类dma-fence为explicit fencing。 2、implicit fencing:其实就是对用户层不可见的dma-fence,通常存储在dma_resv中,在通过dma_buf在内核中传递。   GEM buffer object的定义如下(省略了与本文无关的成员): struct drm_gem_object {     … …   struct dma_resv *resv;   struct dma_resv _resv;     … … };   resv     Pointer to reservation object associated with the this GEM object.     Normally (resv == &**_resv**) except for imported GEM objects. _resv     A reservation object for this GEM object.     This is unused for imported GEM objects.   GEM中对WW-Mutexes和dma-fence是通过dma_resv来实现的,dma_resv的定义如下: struct dma_resv {     struct ww_mutex lock;     seqcount_ww_mutex_t seq;       struct dma_fence __rcu *fence_excl;     struct dma_resv_list __rcu *fence; };   我们最终要关注的对象实际上是dma_resv。 简单的说,我们关注的buffer对象,在这里就是一个GEM对象,而这个GEM对象的同步操作是由GEM中的dma_resv提供的。 因为在这片文章中,不会涉及buffer同步以为的内容(例如backing memory),所以接下来在讨论dma_resv时,实际上就是在讨论单个GEM对象的同步,也即是单个buffer对象的同步。   前文已将谈到,在GPU的操作中涉及到多buffer的同步互斥问题,需要一次性准备好GPU的pipeline上所需要的buffer。 当使用这组buffer时,很可能这组buffer也被其他人使用。 如果针对单个buffer加锁(如mutex),会有死锁的风险(deadlock),比如A、B两个进程都需要同时引用两个buffer,分别对两个buffer加锁,A获得buffer0,B获得buffer1,当A在对buffer1加锁就会死锁,同样的B也会在加锁buffer0时死锁。 所以就引入了WW-Mutexes来解决这样的冲突,Linux DRM中的GEM提供了对WW-Mutexes的支持。。 进一步,我们发现在GPU上,对buffer的操作有读有写,比如texture buffer、uniform buffer是只读的,framebuffer可读可写。 写操作必须是独占式的,但读操作却可以被共享,所以又引入了dma-fence来达到这样的目的。 dma_resv把WW-Mutexes和dma-fence相结合,达到多buffer间同步的最优化。   使用步骤: kernel中已经做了很好的封装,涉及到几个函数的调用,我总结的步骤如下: 1、调用drm_gem_lock_reservations()获取GPU一次rendering所用到的buffer的锁ww_mutex 2、成功获取到所有buffer的ww_mutex锁后,针对每个buffer在GPU中的使用情况添加不同的dma-fence,      如果GPU中会读取某个buffer,则通过函数dma_resv_add_shared_fence()添加一个共享dma-fence;      如果GPU会写每个buffer,则通过函数dma_resv_add_excl_fence()添加一个独占的dma-fence。       注意在调用dma_resv_add_excl_fence()前,需要确保在这之前添加的share fence均处于unsignaled状态,就是确保写之前,读操作已全比完成。 3、完成fence的添加后,调用drm_gem_unlock_reservations()释放这组buffer的ww_mutex 4、接下来,当其他进程或设备要对某个buffer做操作前,需要判断dma-fence的情况。      假如我要读取framebuffer的内容用于屏幕显示,那就是读之前,需要确保写结束,调用函数dma_resv_get_excl_rcu(), 读取独占dma-fence,确保其为unsignaled状态。      例如,我们看看KMS的atomic中的plane frambuffer 的操作:      读取独占dma-fence:      int drm_gem_fb_prepare_fb(struct drm_plane *plane,                   struct drm_plane_state *state)     {         struct drm_gem_object *obj;         struct dma_fence *fence;           if (!state->fb)             return 0;           obj = drm_gem_fb_get_obj(state->fb, 0);         
fence = dma_resv_get_excl_rcu(obj->resv);         drm_atomic_set_fence_for_plane(state, fence);           return 0;     }        在KMS的atomic操作中,会等待独占dma-fence被signal,代码如下:          int drm_atomic_helper_wait_for_fences(struct drm_device *dev,                           struct drm_atomic_state *state,                           bool pre_swap)     {         struct drm_plane *plane;         struct drm_plane_state *new_plane_state;         int i, ret;             for_each_new_plane_in_state(state, plane, new_plane_state, i) {             if (!new_plane_state->fence)                 continue;                 WARN_ON(!new_plane_state->fb);                 /*              * If waiting for fences pre-swap (ie: nonblock), userspace can              * still interrupt the operation. Instead of blocking until the              * timer expires, make the wait interruptible.              */             
ret = dma_fence_wait(new_plane_state->fence, pre_swap);             if (ret)                 return ret;                 dma_fence_put(new_plane_state->fence);             new_plane_state->fence = NULL;         }             return 0;     }   代码简析: 函数drm_gem_lock_reservations()的加锁过程就是,上文中提到的ww_mutexes的典型用法代码如下: int drm_gem_lock_reservations(struct drm_gem_object **objs, int count,               struct ww_acquire_ctx *acquire_ctx) {     int contended = -1;     int i, ret;      
 ww_acquire_init(acquire_ctx, &reservation_ww_class);   retry:     if (contended != -1) {         struct drm_gem_object *obj = objs[contended];           ret =
dma_resv_lock_slow_interruptible(obj->resv,                                  acquire_ctx);         if (ret) {             ww_acquire_done(acquire_ctx);             return ret;         }     }       for (i = 0; i < count; i++) {         if (i == contended)             continue;           ret =
dma_resv_lock_interruptible(objs[i]->resv,                                 acquire_ctx);         if (ret) {             int j;               for (j = 0; j < i; j++)                 
dma_resv_unlock(objs[j]->resv);               if (contended != -1 && contended >= i)                 dma_resv_unlock(objs[contended]->resv);               if (ret == -EDEADLK) {                 contended = i;                 goto retry;             }               ww_acquire_done(acquire_ctx);             return ret;         }     }       
ww_acquire_done(acquire_ctx);       return 0; }   参考文档: https://www.kernel.org/doc/html/latest/locking/ww-mutex-design.html https://www.kernel.org/doc/html/latest/gpu/drm-mm.html  


开心洋葱 , 版权所有丨如未注明 , 均为原创丨未经授权请勿修改 , 转载请注明linux GPU上多个buffer间的同步之ww_mutex、dma_fence的使用 笔记
喜欢 (0)

您必须 登录 才能发表评论!

加载中……