内存管理

 页帧管理
层次化的页帧管理
  • 页帧
    •   用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
      •    _count:改帧的引用计数
        •     -1 :表示空闲
        •     >=0 :已用, page_count()返回(_count+1);
      •    _mapcount: 引用该帧的页表项数。
      •    其他:(该章节不涉及)
        •     ulong private
        •     address_space* mapping:该帧属于一个页缓冲或匿名区域时有用
        •     ulong index
        •     list_head lru: 表示最近所在链表
    •   函数:
      •    virt_to_page (addr):根据线性地址,得出帧描述符地址。
        •     实际上是先用线性映射转化为物理地址,再根据页帧号在mem_map数组中找到对应单元.
      •    pfn_to_page (pfn):根据页号(线性地址>>PAGE_SHIFT),得出页描述符地址。

  • 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放在数组中, 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
              •        里面只有一个内嵌环链表(链起每个块的第一个帧),和空闲块个数
        •     等待队列:
          •      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

  • buddy系统 (传入的是指数):
    •   分配块:__rm_queue:
      •    如果指定的大小的块存在,分配。
      •    否则,从小到大,找合适大的空闲块;根据帧索引按位逐级分裂,不用的兄弟挂入相应链表。返回最终的块
    •   释放块:__free_pages_bulk
      •    不断与兄弟合并成大块,最后插入链表。
        •     根据对应的帧描述符判断是不是兄弟。

  • 带单页帧缓冲的分配
    •   分配:buffered_rmqueue
      •    如果order是0:判断冷热缓冲,拿出帧。如果缓冲中的空闲帧数超出了下限,从buddy系统中分配一串出来
      •    如果order大于0:直接从buddy系统中分配
    •   释放:
      •    free_hot_page, free_cold_page,判断冷热缓冲,归还帧。如果缓冲中的空闲帧数超出了上限,释放一串到从buddy系统中
      •    order大于0的情况,最终调用__free_pages_bulk释放

  • 有保留帧的分配:
    •   目标:
      •    要有保留帧,供急需之用
      •    如果空闲帧太少,而且当前进程可以被阻塞,要触发页面(帧)回收算法。
      •    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的参数)如下:
严格程度gfp_highcan_try_harder阀值最严00pages_high稍严00pages_low实际实际情况(gfp_mask & __GFP_HIGH)实际情况(当前进程是实时进程且不在中断上下文中,或gfp_mask表示不等待)pages_min
      •    空闲帧分布:
        •     [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没有置位, 且
              •        如果请求的页面
          •      如果回收不成功:
            •       如果允许, kill一个进程回收它的页帧,从头再来
              •        允许:
                •         重试,__GPF_NORETRY没有置位
                •         文件系统操作(__GFP_FS)。kill一个进程需要文件系统操作。
              •        检查必要性并kill一个进程:
                •         用最严条件检查,看看是否别的kcp正在kill进程回收页,防止不必要的kill)
                •         通过kill一个进程释放页。

  • 给分配的帧建立映射
    •   线性地址布局 (没考虑地址排列)
      •    fix-mapping区域(FIXME: 建立映射时的虚拟地址都是常量)
        •     其他
        •     临时映射的页空间(13个页 * CPU数目)
        •     启动时用到的16个页
      •    永久性映射的页
        •     基地址是PKMAP_BASE, 页个数是LAST_PKMAP
          •      LAST_PKMAP(32位系统中):有PAE时为512, 否则是1024。一页页表能容纳的PTE数)
      •    其他(暂不考虑)

    •   地址布局 (从左向右)
其余内核空间永久性映射空间固定映射(fix-mapped)空间启动时映射的fix-mapped空间永久的fixed-mapped空间其他临时映射空间其他
    •   建立永久性映射
      •    特点:
        •     建立映射后,可以发生进程切换;其他进程可以看到这个映射。
        •     当需要建立映射,但没有空闲页表项时,进程会阻塞。所以不能用于中断上下文中
      •    数据结构:
        •     互斥锁: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映射。
          •      建立映射时,禁止抢占;解除映射时,允许抢占。这样期间来了中断,不会引起调度。
        •     每次改变映射都成功,用于中断处理上下文中。
      •    建立映射: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: 仅用于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 (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个元素。相当与一个数组栈,分配对象=出栈,释放对象=入栈
    •   batchcount: 从本地cache到slab一次传送的对象数 (与array_cache中的相同)
    •   limit: 本地cache的最大空闲对象数 (与array_cache中的相同)
    •   free_limit:整个cache中空闲object的上限。每个CPU 有了batchcout + 一个slab内对象个数

  • 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的组织:
        •     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

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+索引数组
            •       存放off_slab描述符
              •        如果off_slab的内碎片能放开slab描述符,就放进去(slab_size要align圆整)不再off_slab.
              •        否则,找出合适的cache容器
            •       colour设置
              •        colour偏移 = MIN(硬件cache_line, 对齐长度)
              •        颜色数 =  (剩余空间 / 颜色偏移)
            •       三个slab空列队
          •      本地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
            •       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_chain链表
    •   kmem_cache_destroy():从cache_chain摘下cache描述符,并释放
    •   kmem_cache_shrink():销毁所有的slab, 并 kmem_cache_destroy
    •   通过/proc/slabinfo,可以得到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)。反向操作。

  • 分配、销毁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,list,struct,object,kill)