snifer

【原创】基于嵌入式系统的slab分配器

0
阅读(2275)
Linux内核中有许多内存动态分配的需求,而其中的对象大小也参差不齐,Linux内核提供了slab层,扮演了通用数据结构缓存层的角色。 slab层跟据对象的类型来分组不同的cache, 每个cache存放不同类型的对象,例如一个cache被用来存储task_struct, 而另一个存放inode等。这些cache包含几个slab, 而slab由一个或多个物理上连续的page组成。对于一般的数据结构,每个slab只有一个页即可。
每个slab都包含一些对象成员,即被管理的数据结构。系统分配对象时就从slab中取得。首先从这个cache中部分满的slab中分配,如果没有这样的slab, 便从空的slab中分配,如果也没有,就创建一个新的slab来分配即可。

因为每个slab是包含同一种对象的cache块, 它对对象的分配和释放会变得更为容易和快速。另外,由于每个对象在释放时几乎处于分配好并且初始化好的状态,还可以节省不少初始化的时间。比如说分配inode变量, 首先要一块malloc(sizeof(inode)) 大小的内存, 然后初始化inode中的数据成员, 在使用完毕后用free释放分配的内存。实际上,在free之后内存中的内容和刚刚初始化时差不多, 比如说inode的引用计数count一定是降为零等等。 


kernel有许多数据结构都是还原到和初始化时一样的时候才会free掉, 例如一个mutex lock初始化时和释放时都处于unlock状态。因此,只要在cache初始化的时候就将对象置于合法状态,以后每次分配对象的这些field一定是确定的,从而不必重复初始化,可以节省不少开销。为了这个目的,linux的kmem_cache_create中有一个参数ctor初始化函数, 可以被用作这一目的,但linux似乎并没有使用slab的这一特性(因为它没用调用ctor函数)。
每个cache都用struct kmem_cache_s表示,其中有一个重要的成员变量struct kmem_list3 lists。这个结构体中存放了该cache中空、部分满、满的slab的队列:

slab描述符(struct slab对象)本身也要占用内存。它们可以放在slab自身开始的地方,或者在slab之外另行分配。slab分配器创建新的slab正是通过上面的页分配器进行的。 

1)缓冲区高速缓存
缓冲区高速缓存中包含了被块设备驱动使用的数据缓冲,这些缓冲的单元的大小一般固定(比如512 字节),包含从块设备读出或者写入的信息块。块设备是仅能够以固定大小块进行读写操作的设备。所有的硬盘都是块设备。利用设备标志符和所需块号作索引可以在缓冲区高速缓存中迅速地找到数据块。块设备只能够通过缓冲区高速缓存来存取。如果数据在缓冲区缓存中可以找到,则无需从物理块设备(如硬盘)中读取,这样可以加速设备的访问。
2)页高速缓存
页高速缓存用来加速块设备上可执行映象文件与数据文件的存取。它每次缓冲一个页面的文件内容。页面从块设备上读入内存后缓存在页高速缓存中。
3)交换高速缓存
只有修改过的页面存储在交换文件中。只要这些页面在写入到交换文件后没有被修改则下次此页面被交换出内存时就不必再进行更新写操作这些页面都可以简单的丢弃在交换频繁发生的系统中。交换高速缓存可以省下很多不必要且耗时的块设备操作。
4)硬件高速缓存

一个常见的硬件高速缓存是处理器中的指令和数据cache,它们缓存CPU最近访问过的指令和数据,使CPU不需要到内存中获取数据。实际上它是CPU与内存之间的桥梁。 


常见的cpu中cache的实现按读写方式分类:
贯穿读出式(Look Through)
该方式将Cache隔在CPU与主存之间,CPU对主存的所有数据请求都首先送到Cache,由Cache自行在自身查找。如果命中,则切断CPU对主存的请求,并将数据送出;不命中,则将数据请求传给主存。该方法的优点是降低了CPU对主存的请求次数,缺点是延迟了CPU对主存的访问时间。
旁路读出式(Look Aside)
在这种方式中,CPU发出数据请求时,并不是单通道地穿过Cache,而是向Cache和主存同时发出请求。由于Cache速度更快,如果命中,则Cache在将数据回送给CPU的同时,还来得及中断CPU对主存的请求;不命中,则Cache不做任何动作,由CPU直接访问主存。 它的优点是没有时间延迟,缺点是每次CPU对主存的访问都存在,这样,就占用了一部分总线时间。
写穿式(Write Through)
}任一从CPU发出的写信号送到Cache的同时,也写入主存,以保证主存的数据能同步地更新。 它的优点是操作简单,但由于主存的慢速,降低了系统的写速度并占用了总线的时间。
回写式(Copy Back)
为了克服贯穿式中每次数据写入时都要访问主存,从而导致系统写速度降低并占用总线时间的弊病,尽量减少对主存的访问次数,又有了回写式。 它的工作原理为:数据一般只写到Cache,这样有可能出现Cache中的数据得到更新而主存中的数据不变(数据陈旧)的情况。但此时可在Cache 中设定一个标志地址及数据陈旧的信息,只有当Cache中的数据被再次更改时,才将原更新的数据写入主存相应的单元中,然后再接受再次更新的数据。这样保证了Cache和主存中的数据不致产生冲突。