页帧管理
层次化的页帧管理
帧分配和释放接口
区管理slab分配器
数据结构:cache (kmem_cache_t)
cache 接口:
层次化的页帧管理
- 页帧
- 用4K小页管理动态内存 (内存管理的最小存储单元)
- 找空闲页方便。
- 磁盘与内存传输方便。
- 页帧描述符 (page)
- flags:FIXME: 添加细节
- 高几位表示该页所属的node(UMA,只占1bit),所属的zone(2bit)
- node号在32-bit的NUMA中占6位, 64-bit中占10位(此时flags为64位)
- 低几位存的页的状态位: 值宏 PG_xyz, 取值宏PageXyz, 设值宏SetPageXyz.
- 该帧的本身属性:pg_highmem, pg_comound(4M帧)
- 该帧的使用情况:pg_locked pg_slab pg_checked pg_reserved pg_nosave pg_private pg_swapcache pg_nosave_free
- 对该帧内数据的访问情况: pg_referenced pg_uptodate pg_dirty pg_error
- 高几位表示该页所属的node(UMA,只占1bit),所属的zone(2bit)
- _count:改帧的引用计数
- -1 :表示空闲
- >=0 :已用, page_count()返回(_count+1);
- _mapcount: 引用该帧的页表项数。
- 其他:(该章节不涉及)
- ulong private
- address_space* mapping:该帧属于一个页缓冲或匿名区域时有用
- ulong index
- list_head lru: 表示最近所在链表
- flags:FIXME: 添加细节
- 函数:
- virt_to_page (addr):根据线性地址,得出帧描述符地址。
- 实际上是先用线性映射转化为物理地址,再根据页帧号在mem_map数组中找到对应单元.
- pfn_to_page (pfn):根据页号(线性地址>>PAGE_SHIFT),得出页描述符地址。
- virt_to_page (addr):根据线性地址,得出帧描述符地址。
- 用4K小页管理动态内存 (内存管理的最小存储单元)
- node - NUMA系统中,与CPU关联的内存单元。每个CPU有对应的node, 但node与cpu不一定是一一对应关系. 为了兼容性,非NUMA系统中,仅仅把单元数设为1
- 特点:
- 小心存放CPU经常访问的内核数据,以减少跨node访问,
- 相邻的物理区域,可能分到不同的node中.
- 同一node中的页不一定连续
- node描述符 (typedef struct pglist_data page_data_t). 所有node组成一个数组pgdat_list
- node自身的组织:
- node_id
- pgdat_next: 指向下一个page_data_t的指针
- zone相关的
- node_zones[MAX_NR_ZONES]: zone描述符数组
- nr_zones:zone数目
- node_zonelists: zone列表数组 (页分配器用, 可以用来依次寻找有空闲帧的zone)
- node_zonelists是三个数组,每个数组内的zone属性相同。分配帧时,根据pfg_mask的最低三位,取一个数组
每个数组zonelist: 大小是[ MAX_NUMNODES * MAX_NR_ZONES +
1]。分配页帧时,依次寻找zonelist内的各个zone (首先找到是本node的zone, 后面是其他node的zone。
越往后离本node“距离”越大,即访存时间可能越长)。zonelist前面一串非NULL的指针是有效的。
- page相关的
- node_mem_map: 页描述符指针的数组
- bdata: FIXME: kernel初始化时用的,FIXME: 不知道做什么的,是否与页相关
- node_start_pfg:第一个页的索引
- node_present_pages: 目前页数, 不含空洞。(因为一个node内的帧不一定是连续的)
- node_spanned_pages: 跨越的页数,含空洞。
- swap相关的
- kswapd_wait: 等待换页的等待列队
- kswapd:换页内核线程
- kswap_max_order: kswapd创建的空闲大小(对数大小)
- node自身的组织:
- 所有node放在数组中, pgdat_list指向这个数组。x86中用NUMA,所以数组只有一个元素,即contig_page_data
- 特点:
- zone - 根据硬件对内存使用的限制,把不同页归属为不同的地带(zone)
- 硬件限制:
- DMA: 旧的ISA总线的DMA只能访问低于16M的地址空间
- CPU地址线限制, 不能直接访问全部的内存空间.
- 每个node的帧, 按照物理内存地址分成三个ZONE:
- ZOME_DMA: 0 - 16M 内的帧
- ZONE_NORMAL: 16M - 896M 内的帧, 内核只能访问直接访问第4G线性空间 (线性映射到0 - 1G的物理空间)
- ZONE_HIGHMEM : 896M以上的帧. 64位的系统中,该处是空的
- zone描述符 (zone)
- 整体状况
- name: zone的名字("DMA","Norml","HighMem")
- lock 自旋锁
- zone_pgdat: 该zone所在node (struct pglist_data*)
- zone_meme_map: 该zone内的第一个帧描述符的地址
- zone_start_pfn: 第一个帧的索引值
- spanned_pages: 这个zone的整个帧数
- present_pages: 这个zone的目前帧数, 不含空洞
- 页面分配器和相关计数:
- 空闲帧及保留帧计数
- free_pages: 空闲页数
- pages_min: 该zone保留页数
- 总保留内存是min_free_kbytes = sqrt (16 * 内核线性映射的总内存KB) (单位是KB)
- 每一zone的pages_min 是min_free_kbytes按zone大小比例分得的帧数
- pages_low :(pages_low == 5 * pages_min / 4)
- pages_high :(pages_high == 6 * pages_min / 4)
- lowmen_reserve[3]: 一个数组. 保存着该zone, 为处理低内存情况, 必须分别保留DMA的、NORMAL的、HIGHMEM的帧数
- 分配器:
- pageset: per-CPU变量,帧缓冲. 为实现"单独页面cache"的数据结构. 内有两个per_cpu_pages,一热一冷。
- 冷热选择:
- 热帧:硬件cache可能缓存了帧。如果分配帧后就写数据,分配热帧,减少访存。
- 冷帧:硬件cache没有缓存该帧。如果分配帧用于设备的DMA传输,分配冷帧。
- per_cpu_pages内部结构
- count: 空闲帧数
- 高低水位线:high, low (count在high、low之间,超出则从buddy中分配或归还)
- batch: 每次从buddy分配帧数 (batch个单帧,充分利用零碎空间)
- list: 池中的帧组成的链表
- 冷热选择:
- struct free_area[11] free_area: 用来分配空闲帧组成的块 (buddy 系统)
- 介绍:
- 每个块所含帧个数必须为2的指数(0 - 10), 而且地址对齐。最大是210个帧,即4M空间 (一个大页)
- 兄弟块可以合并成大块。
- 分配、释放块大小也都是2的指数
- 碎片低;寻找合适大小的块方便;块合并、拆分方便
- 数据结构free_area
- 里面只有一个内嵌环链表(链起每个块的第一个帧),和空闲块个数
- 介绍:
- pageset: per-CPU变量,帧缓冲. 为实现"单独页面cache"的数据结构. 内有两个per_cpu_pages,一热一冷。
- 等待队列:
- wait_table: hash表, 里面是等待分配该zone的1个帧的进程
- wait_table_size: hash表的大小
- wait_table_bits:
color(Orchid):FIXME: 哈希表数组长度的指数(底是2)
- 空闲帧及保留帧计数
- 页面回收相关:
- lru_lock: active 和 inactive链表用的锁
- 活跃的
- active_list
- nr_active
- nr_scan_active
- 不活跃的
- inactive_list
- nr_inactive
- nr_scan_inactive
- 总体情况
- page_scanned
- all_unreclaimabe
- temp_priority
- prev_priority
- 整体状况
- page_zone(): 根据所给帧描符,得出所在zone
- zonetable: 大小是[ 大于MAX_NUMNODES的2的指数 * 大于MAX_NR_ZONES的2的指数 ], 用于根据page.flags(几个位指定node号,几个位指定zone号),快速定位所属zone
- 硬件限制:
帧分配和释放接口
- zone内的帧分配和回收的接口 (帧数都是2的指数)
- 得到帧描述符的线性地址 (好像不太对应,凡是分配函数没有下划线的,释放函数都有;反之也是)
- alloc_pages (gfp_mask, 帧数的对数)
- alloc_page (gfp_mask) : 一个帧
- get_zeroed_page
- 释放函数:__free_pages, __free_page
- 分配帧并建立映射,得到帧对应内存空间的线性地址
- __get_free_pages:
- __get_dma_page
- 释放函数:free_pages, free_page
- pfg_mask的位:__GFP_XXX
- 对帧本身的要求FIXME: 待验证:
- 允许、必须在的ZONE: DMA, HIGHMEM
- 请求大页帧(2M帧) COMP
- 冷的请求 COLD (默认是hot)
- 运行在低内存页传送I/O,以便释放帧: IO
- 允许依赖于文件系统的操作: FS
- 请求紧迫性
- 可以访问保留帧 HIGH
- 失败后如何处理
- 分配失败时不会产生警告 NOWARN
- 阻塞进程:WAIT
- 重试,直到成功 REPEAT
- 不重试 NOTRETRY
- 不允许slab cache变大 NO_GROW
- 成功后如何处理
- 填0 ZERO
- 对帧本身的要求FIXME: 待验证:
- 得到帧描述符的线性地址 (好像不太对应,凡是分配函数没有下划线的,释放函数都有;反之也是)
- buddy系统 (传入的是指数):
- 分配块:__rm_queue:
- 如果指定的大小的块存在,分配。
- 否则,从小到大,找合适大的空闲块;根据帧索引按位逐级分裂,不用的兄弟挂入相应链表。返回最终的块
- 释放块:__free_pages_bulk
- 不断与兄弟合并成大块,最后插入链表。
- 根据对应的帧描述符判断是不是兄弟。
- 不断与兄弟合并成大块,最后插入链表。
- 分配块:__rm_queue:
- 带单页帧缓冲的分配
- 分配:buffered_rmqueue
- 如果order是0:判断冷热缓冲,拿出帧。如果缓冲中的空闲帧数超出了下限,从buddy系统中分配一串出来
- 如果order大于0:直接从buddy系统中分配
- 释放:
- free_hot_page, free_cold_page,判断冷热缓冲,归还帧。如果缓冲中的空闲帧数超出了上限,释放一串到从buddy系统中
- order大于0的情况,最终调用__free_pages_bulk释放
- 分配:buffered_rmqueue
- 有保留帧的分配:
- 目标:
- 要有保留帧,供急需之用
- 如果空闲帧太少,而且当前进程可以被阻塞,要触发页面(帧)回收算法。
- DMA zone要保留适当的帧
- 起保留帧作用的函数:zone_watermark_ok. 检查分配后的空闲帧数目及空闲帧分布是否满足要求,若满足返回1
- 分配后的空闲帧数 >= 保留的(lowmen_reserve [zone 类型]) + 调整后的参数阀值
- 传入的参数一般有pages_low(归一化后是5/4), page_high(6/4), page_min(1);
- 参数阀值的调整:如果有gfp_high, 减半; 如果有can_try_harder,减1/4.
- 可见gfp_high、can_try_harder越小,阀值越大;条件越严格
- 最大阀值是5/4, 最小的调整是(3/4)。(5/4) * (3/4) = 15/16
- 所以说即便是最小的调整,也大大放宽了条件。分配时用到的条件(传给zone_watermark_ok的参数)如下:
- 分配后的空闲帧数 >= 保留的(lowmen_reserve [zone 类型]) + 调整后的参数阀值
- 目标:
- 空闲帧分布:
- [1, order]各链表中:至少有 (调整后的参数阀值) / 2k 个空闲帧 (加起来大约等于“调整后的参数阀值”)
- 空闲帧分布:
- 函数__alloc_pages(gfp_mask, order, zonelist)
- 多次尝试遍历zonelist(遇到NULL指针就停止)。 每一个zone: 用zone_watermark_ok检查条件,用buffered_rmqueue分配。直到遇上一个zone,条件允许并且分配成功。
- 整体流程
- 初始尝试
- 用稍严条件检查。如果失败,启动kswapd内核线程,开始回收算法
- 用实际条件检查。
- 可直接分配:不在中断上下文中,且当前进程的属性标志说明它正试图回收页面。
- 看是否可以不检查zone条件,直接分配; 如果直接分配都失败,打印警告信息,返回NULL.
- 不可直接分配,但可以wait(__GFP_WAIT):
- 尝试回收页面。
- 调用cond_resched暂时放弃CPU。FIXME: 因为尝试回收页面太耗时?
- 设置属性标识(表明自己正试图回收页面),尝试回收页面(try_to_free_pages),取消标志.
- 调用cond_resched放弃CPU
- 如果回收成功,用实际条件再次检查。
- 如果仍不满足,但允许重试;调用blk_congestion_wait等一会,回到上步重新尝试回收页面。
- __GPF_NORETRY没有置位, 且
- 如果请求的页面
- 如果仍不满足,但允许重试;调用blk_congestion_wait等一会,回到上步重新尝试回收页面。
- 如果回收不成功:
- 如果允许, kill一个进程回收它的页帧,从头再来
- 允许:
- 重试,__GPF_NORETRY没有置位
- 文件系统操作(__GFP_FS)。kill一个进程需要文件系统操作。
- 检查必要性并kill一个进程:
- 用最严条件检查,看看是否别的kcp正在kill进程回收页,防止不必要的kill)
- 通过kill一个进程释放页。
- 允许:
- 如果允许, kill一个进程回收它的页帧,从头再来
- 尝试回收页面。
- 初始尝试
- 给分配的帧建立映射
- 线性地址布局 (没考虑地址排列)
- fix-mapping区域(FIXME: 建立映射时的虚拟地址都是常量)
- 其他
- 临时映射的页空间(13个页 * CPU数目)
- 启动时用到的16个页
- 永久性映射的页
- 基地址是PKMAP_BASE, 页个数是LAST_PKMAP
- LAST_PKMAP(32位系统中):有PAE时为512, 否则是1024。一页页表能容纳的PTE数)
- 基地址是PKMAP_BASE, 页个数是LAST_PKMAP
- 其他(暂不考虑)
- fix-mapping区域(FIXME: 建立映射时的虚拟地址都是常量)
- 线性地址布局 (没考虑地址排列)
- 地址布局 (从左向右)
- 建立永久性映射
- 特点:
- 建立映射后,可以发生进程切换;其他进程可以看到这个映射。
- 当需要建立映射,但没有空闲页表项时,进程会阻塞。所以不能用于中断上下文中
- 数据结构:
- 互斥锁:kmap_lock
- 建立永久映射的页表:pkmap_page_table
- 存页表项被映射的次数的数组(表示映射的有效性):pkmap_count
- 0: 没有map到页帧, 可用
- 1:没有map到页帧, 但在TLB表中存在过时的映射
- n(大于1):map到了一个页帧,而且被map了(n-1)次
- 保存永久映射的帧:哈希表 page_address_htable
- 冲突链中是struct page_address_map, 存{ 帧描述符指针,线性地址 }
- 建立映射: void* kmap (struct page*)
- 不是high_mem的帧,利用线性映射
- high_mem的帧,利用kmap_high在建立映射并保存在哈希表中(期间要锁互斥锁,阻塞时打开)
- 查找该帧的映射是否已经建立。
- 如果没有建立,建立映射(map_new_virtual):
- 在一个无限循环中,扫描空闲页表项,如果没有就阻塞。
- 如果找到一个pkmap_count是0的页表项,把映射对插入哈希表,设置页表项。计数设为1,返回虚拟地址 (在map_new_virtual, 计数被改为2)
- 每成功一次,下一次从下一个页表项开始扫。
- 每次扫到开头,就把所有计数为1的项flush_tlb, 并把计数改为0,并重启本次扫描。
- 被唤醒后检查一下是不是已被其他进程map了,然后再进行下一次扫描
- 在一个无限循环中,扫描空闲页表项,如果没有就阻塞。
- 对应虚址的计数加1
- 求帧的线性地址:page_address
- 不是high_mem的帧,利用线性映射
- high_mem的帧,在page_address_htable查找,返回哈希表中记录的线性地址。
- 解除映射: void unkmap(struct page*)
- 对应虚址的计数减1;如果变成1,唤醒等待进程
- 特点:
- 建立永久性映射
- 临时映射:
- 特点:
- kernel保证同一个页表项不会同时被两个KCP映射。
- 建立映射时,禁止抢占;解除映射时,允许抢占。这样期间来了中断,不会引起调度。
- 每次改变映射都成功,用于中断处理上下文中。
- kernel保证同一个页表项不会同时被两个KCP映射。
- 建立映射:void* kmap_atomic (struct page* page, enum km_type type)
- 禁止抢占
- 如果是high_mem帧,直接返回虚址。
- 如果不是high_mem帧,根据type和cpuid计算出在fix-mapped中的索引。得出虚址,设置页表。刷相应TLB
- 解除映射:void* kunmap_atomic (struct page* page, enum km_type type)
- 允许抢占,并检查调度
- type参数是与kmap_atomic相同的常量
- 特点:
- 临时映射:
- 所以__get_free_pages不能用于high_mem的帧。
- 由它的实现可知,它用alloc_pages分配帧,用page_address取得其线性地址。但此时映射还没有建立。
区管理slab分配器
- 层次结构
- cache: 同一类型对象的分配器
- 通用:仅被slab分配器用
- 专用:被内核其他部分用
- slab: cache下的结构,每一个slab有一串相邻的帧,
- 外部slab描述符:slab描述符保存在cache的内存帧外
- 内部slab描述符:slab描述符在第一个帧内。
- 对象大小
- object: slab内的对象。
- cache: 同一类型对象的分配器
- cache的种类
- 通用cache: 仅用于slab分配器
- 系统初始化时,kmem_cache_init()建立通用cache.
- 两类:
- 变量cache_cache:名字是"kmem_cache", 它的对象是cache描述符的cache,它负责保管所有的cache描述符。
- cache_size malloc_sizes[13]:按对象大小组织的cache指针对:
- 13个元素代表13种大小:25到217字节(从32字节到32帧)。
- 每个元素有两个cache描述符指针:
- 帧在DMA zone的cache,
- 帧在normal zone的cache
- (FIXME: high-mem的帧, 不用cache管理,why)
- 专用cache: 用于其他CPU模块。也是由kmem_create_create()创建的,
- 通用cache: 仅用于slab分配器
数据结构:cache (kmem_cache_t)
- cache本身的组织
- char* name:名字
- 属性:永久属性:flags; 动态属性:dflags
- (本章里一个常用的永久属性:CFLAGS_OFF_SLAB: slab描述符在外部)
- spinlock:互斥锁
- next:内嵌链表, 链起所有的cache
- 所有的cache都挂在链表cache_chain上,由读写信号量cache_chain_sem保护
- 本地cache相关的
- struct array_cache* array [CPU_NR]: 本地cache, 一个per-CPU数组。保存指针的指向区域
- struct array_array:
- avail:空闲对象的指针个数,看作是栈顶位置
- limit:栈总长度。
- batchcount:一次向slab中分配、释放的对象个数。
- 即:栈空时,加入元素个数;栈满时,取走元素个数
- touched: 最近是否被访问。
- 一个指针数组,limit个元素。相当与一个数组栈,分配对象=出栈,释放对象=入栈
- struct array_array:
- batchcount: 从本地cache到slab一次传送的对象数 (与array_cache中的相同)
- limit: 本地cache的最大空闲对象数 (与array_cache中的相同)
- free_limit:整个cache中空闲object的上限。每个CPU 有了batchcout + 一个slab内对象个数
- struct array_cache* array [CPU_NR]: 本地cache, 一个per-CPU数组。保存指针的指向区域
- slab相关的
- 内存帧:
- gfgorder:一个slab中的帧数对数
- gfpflags:帧属性
- 对象相关:
- void* ctor,void* dtor:构造、析构函数
- objsize:对象大小
- 为了提供性能,对象大小是经过硬件cache_line,和CPU字对齐的。对齐方式见kmem_cache_create函数的实现
- 单个slab相关:
- num:一个slab内能容纳的object数目
- slab_size:一个slab描述符占用空间的大小。包括slab描述符结构和一个索引数组
- slab描述符结构体(类型是slab)
- list:slab内嵌链表(list_head)
- colouroff:slab内第一个对象的偏移量
- s_mem:第一个对象的线性地址。(内存空间的地址 + colouroff = s_mem)
- inuse:正使用的对象的个数
- free:下一个空闲object的索引。如果是BUFCTL_END,表示没有空闲的
- 对象索引的数组:构成索引链表,每个单元存放下一个空闲对象的索引。
- 在位置0,放的是第一个空闲对象的位置。
- 如果放的是BUFCTL_END:表示该处是链表尾。
- slab描述符结构体(类型是slab)
- 多个slab的组织:
- kmem_cache_t* slabp_cache: 对象是slab描述符的cache容器,是存放slab描述符的地方
- 三个slab链表:slabs_partial(部分slab空闲),slabs_free(全部slab空闲),slabs_full(没有slab空闲)
- free_objects:cache内空闲object
- free_touched:slab页回收用
- next_reap:slab页回收用
- struct arrar_cache* shared 多CPU共享的一个array_cache.只有在SMP下,且对象大小小于页大小时才建立它。从slab中分配、释放时,都要经过它。相当于slab的一个缓冲器
- shared的limit是本地batchcount的8倍
- chared的batchcount 是 0xbaabf00d. FIXME: 不知道是做什么用的,可能是个魔数,表明此array_cache是shared.
- 减少硬件cache线冲突相关的:
- 原理:
- 如果每个slab中第一个对象的地址,距离帧开头都相同,那很容易会造成硬件cache冲突。
- 利用slab内的剩余空间,对对象进行偏移。使每个slab的偏移量不同,但都是某个值(单元偏移量)的倍数。
- 但是slab内的对象仍旧是紧邻放置。
- colour_off:单元偏移量。取值:MIN ( 硬件cache_line, 对象对齐大小 )
- colour:slab的颜色数,即偏移量的取值个数。偏移量的范围是 [0, colour_off*(colour-1)]。
- colour_next:新建slab的颜色。对应偏移量是:单元偏移量 * colour_next
- 原理:
- kmem_cache_t* slabp_cache: 对象是slab描述符的cache容器,是存放slab描述符的地方
- 内存帧:
cache 接口:
- 建立、销毁cache
- kmem_cache_create():从cache_cache分配cache对象,插入cache_chain. 主要的是对cache描述符内的参数的设置
- 参数:
- 名字,cache属性flags,
- 对象大小,对象地址对齐,
- 对象的构造、析构函数
- 主要流程:
- 判断错误:
- 无名cache,
- 中断处理中建立cache,
- 对象size超出限制。(合理范围:CPU字长
- 有析构却没有构造函数
- 计算对齐长度,对象大小圆整,调整off_slab
- 对齐长度
- 如果flags标出要硬件cache对齐, 对齐长度为(cache_line/2n),比对象大的最小值。(使一个cache线上容纳2n个对象)
- 否则字对齐
- 不小于slab系统的最小align(常量),不小于参数指定值。
- 并把size圆整到CPU的字大小和对齐长度
- 如果size大于1/8页, 使用off_slab。有利于大对象打包
- 对齐长度
- 从cache_cache中分配一个对象,并初始化相关数据
- 设置slab相关的数据
- 计算order
- 是帧可回收cache 而且 一个帧能放下一个对象; order=0.
- 计算能放几个对象,以及剩余空间 (如果不是off_slab, 剩余空间已除去slab描述符)
- 否则:order从0开始增长找合适的值,
- 最小值:slab空间至少能放下一个对象
- 合适的order (依次弱化):
- 够用的情况下,达到2帧(order=1)就可以了。TODO: 太大的slab,不太好。源码注释没看懂
- 不到2帧时,如果内碎片低于帧大小的1/8,也合格了。
- 最大值:
- 自身的极限(最大cache的slab大小:32帧)
- 它影响的slab描述符的极限值:如果slab描述符在外部,容纳对象数不能超过最大对象索引个数(slab+索引数组
- 是帧可回收cache 而且 一个帧能放下一个对象; order=0.
- 存放off_slab描述符
- 如果off_slab的内碎片能放开slab描述符,就放进去(slab_size要align圆整)不再off_slab.
- 否则,找出合适的cache容器
- colour设置
- colour偏移 = MIN(硬件cache_line, 对齐长度)
- 颜色数 = (剩余空间 / 颜色偏移)
- 三个slab空列队
- 计算order
- 本地cache
- 通用cache还没都建立时(现在创建的正是通用cache),只建立本CPU的本地cache, 而且本地cache都只能容纳一个对象。 FIXME: why只负责本地CPU?
- 如果此时除了cache_cache还没有另外的cache (g_cpucache_up == NONE),直接用编译时存在的变量initarray_generic{0, 1, 1, 0}
- 如果此时部分通用cache已存在了 (g_cpucache_up == PARTIAL),在通用cache中分配(最小对象的cache也够了),即用kmalloc函数。
- 通用cache都建立好时(现在建立的是专用cache),
- 所有CPU的本地cache都建立(通过kmalloc)
- limit 根据对象大小而定, 对象越大limit越小
- batchcount是limit的一半
- smp下 如果objsize
- 所有CPU的本地cache都建立(通过kmalloc)
- cachep中:
- batchcount: 与array_cache的相同
- limit: 与array_cache的相同
- free_limit: (1+num_online_cpus())*cachep->batchcount + cachep->num;
- 每个CPU 有了batchcout + 一个slab内对象个数
- 设置页面回收相关:TODO: lists.next_reap = jiffies + REAPTIMEOUT_LIST3 + ((unsigned long)cachep)%REAPTIMEOUT_LIST3
- 通用cache还没都建立时(现在创建的正是通用cache),只建立本CPU的本地cache, 而且本地cache都只能容纳一个对象。 FIXME: why只负责本地CPU?
- 设置slab相关的数据
- 插入 cache_chain链表
- 判断错误:
- 参数:
- kmem_cache_destroy():从cache_chain摘下cache描述符,并释放
- kmem_cache_shrink():销毁所有的slab, 并 kmem_cache_destroy
- 通过/proc/slabinfo,可以得到cache的信息
- kmem_cache_create():从cache_cache分配cache对象,插入cache_chain. 主要的是对cache描述符内的参数的设置
- 分配、释放页帧(得到的是页帧空间的虚拟地址). 页数由cache->gfporder决定
- void* kmem_getpages(kmem_cache_t* cache, flags):
- 用(flags|cache->gfpflags)从buddy系统分配页帧并页帧属性(PG_slab),
- 如果cache有“页帧可回收”(SLAB_RECLAIM_ACCOUNT)属性,增加回收计数(全局变量 slab_reclaim_pages),
- 通过page_address返回虚拟地址. 所以该函数不能用high-mem的帧
- void kmem_freepages(kmem_cache_t* cache, void* addr)。反向操作。
- void* kmem_getpages(kmem_cache_t* cache, flags):
- 分配、销毁slab
- cache_grow():有新对象分配请求,而且cache内所有slab都慢时调用
- 刷新colour_next, 计算该slab的colour_off
- 分配帧,slab描述符
- 分配内存空间(一段连续帧)
- 分配slab描述符,并设置内部变量(alloc_slabmgmt), 并挂到slabs_free链表上
- 并设置每个页帧描述符:lru->next = cache指针, lru->prev = slab指针.
- 初始化slab内的所有对象 (每个对象调用一次构造函数)
- 增加计数:cache->lists.free_objects.
- destroy_slab(): 当cache有太多空闲对象时,或被一个timer函数调用。该timer函数周期的检查有没有空闲的slab, 并释放之
- 对每个对象调用析构函数
- 如果有SLAB_DESORY_BY_RCU. 调用call_rcu。用回调函数来释放帧,释放slab描述符(如果是off_slab的话)。
- 否则,直接释放帧和slab描述符
- cache_grow():有新对象分配请求,而且cache内所有slab都慢时调用