天天看点

线上问题的一次锁思考

线上问题的一次锁思考

目前可以看出: 核心问题为:多线程fd close/open 以及 skb的slab_cache以及 slab_free

  fd close /open 所引发的的问题已经有相关解决办法;主要是slab_free这个?

内核的​

​slab​

​来自一种很简单的思想,即事先准备好一些会频繁分配,释放的数据结构。然而标准的slab实现太复杂且维护开销巨大,因此便分化 出了更加小巧的slub;

后面所有提到slab的地方,指的都是slub, 现在内核都是使用的slub,对外提供的接口都是slabxxx但是实现有slub 或者slob等

对于单核CPU的slab简单;主要看多核CPU的slab;

  由于是预分配cache_mem,所以多核存在竞争, 为了解决这个竞争,最经典的就是per_cpu变量,但是使用per_cpu变量存在一个问题,

如果分配销毁的动作都是在cpu0上, cpu1上没有或者很少,就会导致cpu0会用很多内存,由于是per_cpu 变量,所以cpu1也会刷一份副本内存,但是这个内存是空闲的;

也就导致了:存在单独某个CPU的slab缓存没有对象可分配了,但是其它CPU的slab缓存仍有大量空闲对象的情况。因为对单独一种slab的需求是和该CPU上执行的进程/线程紧密相关的,比如如果CPU0只处理网络,那么它就会对skb等数据结构有大量的 需求;

  所以设计第一要点就是:需要在CPU间均衡slab,并且这些必须靠slab内部的机制自行完成,对于slab,完全取决于使用者自身,只要对象仍然在使用,就不能剥 夺使用者继续使用的权利,除非使用者自己释放。因此slab的负载均衡必须设计成合作型的,而不是抢占式的(也就是在使用的时候不能被打断)。

  谁也不能保证分配slab对象的CPU和释放slab对象的CPU是同一个CPU,谁也不能保证一个CPU在一个slab对象的生命周期内没有分配新的 page(s),这期间的复杂操作谁也没有规定。这些问题该怎么解决呢?分层slab cache---对比cpu  cache

  目前slab缓存对象大多数都是不超过1个页面的小结构(不仅仅slab系统,超过1个页面的内存需求相比1个页面的内存需求,很少),因此会有大量的针 对1个页面的内存分配需求。从伙伴系统的分配原理可知,如果持续大量分配单一页面,会有大量的order大于0的页面分裂成单一页面,在单核心CPU上, 这不是问题,但是在多核心CPU上,由于每一个CPU都会进行此类分配,而伙伴系统的分裂,合并操作会涉及大量的链表操作,这个锁开销是巨大的,因此需要 优化!

Linux内核对伙伴系统针对单一页面的分配需求采取的批量分配“每CPU单一页面缓存”的方式!

每一个CPU拥有一个单一页面缓存池,需要单一页面的时候,可以无需加锁从当前CPU对应的页面池中获取页面。而当池中页面不足时,系统会批量从伙伴系统中拉取一堆页面到池中,反过来,在单一页面释放的时候,会择优将其释放到每CPU的单一页面缓存中。

为了维持“每CPU单一页面缓存”中页面的数量不会太多或太少(太多会影响伙伴系统,太少会影响CPU的需求),系统保持了两个值,当缓存页面数量低于 low值的时候,便从伙伴系统中批量获取页面到池中,而当缓存页面数量大于high的时候,便会释放一些页面到伙伴系统中。

  对于多核优化操作的不二法门就是禁止或者尽量减少锁的操作。随之而来的思路就是为共享的关键数据结构创建”每CPU的缓存“,而这类缓存分为两种类型:

1).数据通路缓存。

比如路由表之类的数据结构,你可以用RCU锁来保护,当然如果为每一个CPU都创建一个本地路由表缓存,也是不错的,现在的问题是何时更新它们,因为所有的缓存都是平级的,因此一种批量同步的机制是必须的。

2).管理机制缓存。

比 如slab对象缓存这类,其生命周期完全取决于使用者,因此不存在同步问题,然而却存在管理问题。采用分级cache的思想是好的,这个非常类似于CPU 的L1/L2/L3缓存,采用这种平滑的开销逐渐增大,容量逐渐增大的机制,并配合以设计良好的换入/换出等算法,效果是非常明显的。

一页一页申请物理内存,再为刚才申请的虚拟地址空间分配物理页表映射

继续阅读