人生像是在跑马拉松,能够完赛的都是不断地坚持向前迈进;人生就是像在跑马拉松,不断调整步伐,把握好分分秒秒;人生还是像在跑马拉松,能力决定了能跑短程、半程还是全程。人生其实就是一场马拉松,坚持不懈,珍惜时间。
分类: LINUX
2015-12-03 01:45:24
前面已经分析了slub算法的初始化、缓存区的创建、对象的分配、对象的回收,最后分析一下slub分配算法的slab销毁具体实现。
Slab销毁的入口函数为kmem_cache_destroy(),其实现:
该函数中kmem_cache_destroy_memcg_children()删除memcg中相关联的子cache数据,而get_online_cpus()是对cpu_online_map的加锁,其与末尾的put_online_cpus()是配对使用的。接着的mutex_lock()用于获取slab_mutex互斥锁,该锁主要用于全局资源保护。然后对kmem_cache的引用计数refcount自减操作,如果自减后if (!s->refcount)为true,即引用计数为0,表示该缓冲区不存在slab别名挂靠的情况,那么其kmem_cache结构可以删除,否则表示有其他缓冲区别名挂靠,仍有依赖,那么将会解锁slab_mutex并put_online_cpus()释放cpu_online_map锁,然后退出。
if (!s->refcount)为true的分支中,先list_del()将该slab管理结构kmem_cache从slab_caches全局链表中摘除,然后__kmem_cache_shutdown()删除kmem_cache结构信息。如果__kmem_cache_shutdown()执行成功则将返回0,继而if (!__kmem_cache_shutdown(s))为true,将会通过memcg_unregister_cache()去注册memcg的cache,并且memcg_free_cache_params()释放创建时申请的memcg_params资源空间,而kfree()和kmem_cache_free()释放slub的名称空间以及slab空间。如果__kmem_cache_shutdown()执行失败,那么将会把slab重新挂回至slab_caches链表,同时记录日志信息。
由此slab销毁完毕。
kmem_cache_destroy()的核心函数是__kmem_cache_shutdown(),深入分析__kmem_cache_shutdown()的实现:
该函数主要通过kmem_cache_close()删除slab的管理数据kmem_cache,如果执行成功,继而进入if分支对sysfs模块的slab做移除操作。
具体看一下kmem_cache_close()的实现:
该函数通过flush_all()释放本地CPU的缓存区,即kmem_cache_cpu管理的缓存区空间;然后通过for_each_node_state()遍历各节点,转而get_node()获取节点下的kmem_cache_node管理结构,然后将其半满队列中的缓存区进行释放free_partial();最后将kmem_cache的每CPU缓存管理kmem_cache_cpu通过free_percpu()归还给系统,同时通过free_kmem_cache_nodes()释放各内存节点node的缓存管理结构kmem_cache_node占用的空间释放。
最后分析一下较为复杂的flush_all()的实现:
看似封装了on_each_cpu_cond()函数,实际上on_each_cpu_cond()并不执行任何与资源释放的操作,其主要是遍历各个CPU,然后执行作为入参传入的函数has_cpu_slab(),以判断各个处理器上的资源是否存在,如果存在,继而将会通过flush_cpu_slab()对该处理器上的资源进行释放处理。
照例,还是详细看一下on_each_cpu_cond()函数实现:
该函数的入参cond_func是一个钩子函数,用于根据调用者传入的CPU信息参数来判断是否需要打断该CPU以执行入参func的操作;而入参info是作为cond_func和func处理函数的入参;至于入参wait则是一个bool类型,用以判断是否需要等待func在各CPU上执行完毕,如果为true将会等待;最后的gfp_flags入参是作为申请cpumask空间的标识。
了解完参数的意思,那么具体看一下其实现,首先might_sleep_if()判断是否需要休眠等待,继而通过zalloc_cpumask_var()申请cpumask的空间;申请到空间后,preempt_disable()禁止内核抢占后,将for_each_online_cpu()遍历各个CPU,根据cond_func()(即has_cpu_slab())判断是否需要对该CPU进行打断处理,如果需要则cpumask_set_cpu()对该CPU进行标志;标志完后,根据前面的标志,通过on_each_cpu_mask()打断各个标志位对应的CPU去执行func()的操作(即flush_cpu_slab());完了将会恢复抢占,释放cpumask空间。至于zalloc_cpumask_var()申请不到空间,将会逐个处理器进行打断再进行处理,其最终功能和作用与申请到空间的情况都是一致的,具体实现就不分析了。
相应看一下作为on_each_cpu_cond()入参的钩子函数has_cpu_slab()的实现:
可以看到该函数主要是用于判断本地CPU是否占有缓存区,如果有则返回true。也即意味着该CPU需要被打断去执行其本地的缓存区释放操作。
至于on_each_cpu_cond()另一钩子函数flush_cpu_slab()的实现:
该函数封装了__flush_cpu_slab(),实现为:
函数实现很简单,主要用于将本地CPU的缓存区进行释放。其首先获取本地CPU的kmem_cache_cpu管理结构,如果本地CPU存在缓存区的占用,将会通过flush_slab()去释放本地缓存区,继而通过unfreeze_partials()将本地CPU半满缓存列表进行释放。
而flush_slab()具体实现:
其主要是通过deactivate_slab()去激活本地缓存区,也即是将缓存区进行释放操作。具体deactivate_slab()的实现:
if (page->freelist)判断slab的空闲链表freelist是否为空,如果为空,意味着该缓存区的对象已经全部分配到了CPU的kmem_cache_cpu中freelist链表中;如果不为空,那么表示该CPU的slab对象被其他CPU释放了,将会更新统计同时设置tail标识为DEACTIVATE_TO_TAIL。
接下来的while循环是去激活本地CPU的slab步骤一,其主要是通过while循环遍历CPU上的freelist链表get_freepointer()获取空闲对象,继而通过内部的do-while循环,借用__cmpxchg_double_slab()比较交换将对象以插入缓存区页面的freelist空闲链表头的方式归还回去。__cmpxchg_double_slab()前面已经介绍过了的原子操作,这里将不再赘述。不过有个点值得注意的是该步骤的释放操作,其并未将所有的对象都归还回去,这是由于nextfree = get_freepointer(s, freelist)该步骤取下一个空闲对象时得到空指针,那么将会退出while循环;也就意味着如果deactivate_slab()入参中freelist不为空,那么while循环退出时,其也必定不为空,其具体用意稍后再分析。简而言之该步骤其目的是,当页面还处于冻结状态,将会释放每CPU的所有可用的对象回到缓冲区的空闲列表中。
然后是步骤二,即redo标签以下的动作,其首先将缓存区的freelist以及counters信息存到临时old结构中以备后用,接着if (freelist)如果为true,将会把前面步骤一未被归还的那个对象归还到缓冲区中,同时更新new信息,此时new.freelist持有该缓存区的所有空闲对象。往下new.frozen = 0将临时缓存区状态设置为非冻结;然后if (!new.inuse && n->nr_partial > s->min_partial) 表示该slab缓存区中无对象被使用,且部分满slab个数大于最小值,意味着该缓存区需要被销毁,标识m为M_FREE;而else if (new.freelist)表示freelist不为空,仅使用了部分对象,则标识m为M_PARTIAL;至于最后的else分支,表示freelist为空,该缓存区所有对象均已被使用,m标识为M_FULL。再往下if (l != m)的比较是用于判断上一次的缓存区状态l与接下来的操作状态m是否一致,不一致则意味着需要发生变更,其将会先判断l的状态为M_PARTIAL或M_FULL,继而采取对应的remove_partial()或remove_full()链表摘除操作;继而根据m的状态,往半满链表中添加add_partial()还是往满载链表中添加add_full(),接着将l的状态更新为m。现在到了if (!__cmpxchg_double_slab()),这里是用于判断自redo到此,缓存区是否发生过对象操作变更,如果没发生过的话,将会把new暂存的空闲对象挂载到缓存区中以及更新counters,否则将会跳转回redo标签重新执行前面的操作。至此,顺利的话,缓存区已经去激活完毕了。
最后如果m的状态为M_FREE,则表示该缓存区不需要再使用了,将通过discard_slab()将其销毁。
至此,slub算法分析完毕。