深入理解Linux虚拟内存管理(六)(上)

简介: 深入理解Linux虚拟内存管理(六)

一、slab 分配器

1、高速缓存控制

(1)创建高速缓存

(a)kmem_cache_create

  这个函数的调用图如图 8.3 所示。这个函数负责创建一个新的高速缓存,然后根据大小进行批量处理。这个批量处理大约包括如下操作:

  • 进行基本的有效性检查以防错误使用。
  • 如果设置有 CONFIG_SLAB_DEBUG 则进行调试检查。
  • cache_cache slab 高速缓存中分配一个 kmem_cache_t
  • 将对象大小对齐为字大小。
  • 计算在 slab 中有多少合适的对象。
  • slab 大小对齐为硬件高速缓存。
  • 计算着色偏移。
  • 初始化在高速缓存描述符中的其余字段。
  • 将新高速缓存加入到高速缓存链。

传送门 8.1.6 创建高速缓存

// mm/slab.c

/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @offset: The offset to use within the page.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 * @dtor: A destructor for the objects.
 *
 * Returns a ptr to the cache on success, NULL on failure.
 * Cannot be called within a int, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache
 * and the @dtor is run before the pages are handed back.
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_NO_REAP - Don't automatically reap this cache when we're under
 * memory pressure.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 */
// 这一块进行基本的有效性检查防止错误使用。
// 这个函数的参数如下:
// name是该高速缓存的可读名。
// size是一个对象的大小。
// offset是用于指定高速缓存中对象的边界,但一般设为0。
// flags是静态高速缓存标志位。
// ctor是在slab创建时为每个对象调用的构造函数。
// dtor是相应的销毁函数。销毁函数可以使一个对象回到初始态。 
kmem_cache_t *
kmem_cache_create (const char *name, size_t size, size_t offset,
  unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),
  void (*dtor)(void*, kmem_cache_t *, unsigned long))
{
  const char *func_nm = KERN_ERR "kmem_create: ";
  size_t left_over, align, slab_size;
  kmem_cache_t *cachep = NULL;

  /*
   * Sanity checks... these are all serious usage bugs.
   */
// 在创建高速缓存时有各种bug的使用方法。
  if ((!name) ||
  // 这里用于可读名大于最大的高速缓存名(CACHE_MAXELEN)的情况。 
    ((strlen(name) >= CACHE_NAMELEN - 1)) ||
  // 中断处理程序不能创建高速缓存,因为需要访问中断安全的自旋锁以及信号量。  
    in_interrupt() ||
  // 对象大小必须至少是一个字大小。slab分配器不适合于以单个字节为度量的对象。   
    (size < BYTES_PER_WORD) ||
  // 最大可能的slab利用32个页面可以创建 2^MAX_OBJ_ORDER 个页面数。   
    (size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) ||
  // 如果没有构造函数可用,也不能使用销毁函数。    
    (dtor && !ctor) ||
  // 偏移不能在slab前,也不能超出第1个页面的边界。    
    (offset < 0 || offset > size))
  // 调用BUG()退出。   
      BUG();

// 如果设置了 CONFIG_SLAB_CONFIG,则这一块进行调试检查。
#if DEBUG
  // SLAB_DEBUG_INITIAL需要构造函数对对象进行检查以保证它们处于初始状
  // 态,所以,必须退出一个构造函数。如果没有退出,则清除该标志。
  if ((flags & SLAB_DEBUG_INITIAL) && !ctor) {
    /* No constructor, but inital state check requested */
    printk("%sNo con, but init state check requested - %s\n", func_nm, name);
    flags &= ~SLAB_DEBUG_INITIAL;
  }
  // slab可能会被某种已知模式感染,以保证某个对象在分配之前没有被使用,但
  // 是一个构造函数可能会破坏这种模式,它会错误地报告一个bug。如果存在这样
  // 一个构造函数,则这里如果设置了 SLAB_POISON 就移除该标志位。
  if ((flags & SLAB_POISON) && ctor) {
    /* request for poisoning, but we can't do that with a constructor */
    printk("%sPoisoning requested, but con given - %s\n", func_nm, name);
    flags &= ~SLAB_POISON;
  }
#if FORCED_DEBUG
  // 仅有很少一部分对象是调试的红色区域。很大的红色区域对象将导致严重的碎片。
  if ((size < (PAGE_SIZE>>3)) && !(flags & SLAB_MUST_HWCACHE_ALIGN))
    /*
     * do not red zone large object, causes severe
     * fragmentation.
     */
    flags |= SLAB_RED_ZONE;
  // 如果没有构造函数,这里设置感染位。    
  if (!ctor)
    flags |= SLAB_POISON;
#endif
#endif

  /*
   * Always checks flags, a caller might be expecting debug
   * support which isn't available.
   */
  // 调用所有可以调用的kmem_cache_alloc()来设置CREATE_MASK以
  // 及所有允许设置的标志位。这里阻止调用者在没有这些标志位的时候使用调试标志位,如果
  // 使用则调用BUG()。 
  BUG_ON(flags & ~CREATE_MASK);

  /* Get cache's description obj. */
  // 利用kmem_cache_alloc()从cache_cache中分配一个高速缓存描述符对象。
  cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);
  // 如果内存不足.则转到oops.在那里处理OOM。
  if (!cachep)
    goto opps;
  // 用0填充对象以防止意外地使用未初始化的数据。   
  memset(cachep, 0, sizeof(kmem_cache_t));

  /* Check that size is in terms of words.  This is needed to avoid
   * unaligned accesses for some archs when redzoning is used, and makes
   * sure any on-slab bufctl's are also correctly aligned.
   */
// 将对象大小对齐于某个字大小边界。
  // 如果大小没有与某个字大小边界对齐,则……  
  if (size & (BYTES_PER_WORD-1)) {
  // 增加一个对象的字大小,然后屏蔽低位。这能有效地将对象大小向上取整到下
  // 一个字边界
    size += (BYTES_PER_WORD-1);
    size &= ~(BYTES_PER_WORD-1);
  // 为了调试打印一个提示信息。    
    printk("%sForcing size word alignment - %s\n", func_nm, name);
  }
  
#if DEBUG
// 如果调试可用,则必须稍微改变一下对齐方式。
  if (flags & SLAB_RED_ZONE) {
    /*
     * There is no point trying to honour cache alignment
     * when redzoning.
     */
  // 如果slab将红色分区的话,就不需要尝试将硬件高速缓存中的东西对齐了。对象的
  // 红色分区是从高速缓存边界处移动一个字的偏移。    
    flags &= ~SLAB_HWCACHE_ALIGN;
  // 对象的大小增加两个BYTES_PER_WORD,以在对象的两端标记红色区域。   
    size += 2*BYTES_PER_WORD; /* words for redzone */
  }
#endif
  // 初始化对齐方式为一个字边界。如果调用者请求的是一个CPU高速缓存对齐方式,
  // 则在这里改变。
  align = BYTES_PER_WORD;
  // 如果有请求,则这里将对象对齐到LI CPU高速缓存。
  if (flags & SLAB_HWCACHE_ALIGN)
    align = L1_CACHE_BYTES;

  /* Determine if the slab management is 'on' or 'off' slab. */
  // 如果对象比较大,在这里存储slab描述符off-slab,这将更好地允许打包对象到
  // slab 中 
  if (size >= (PAGE_SIZE>>3))
    /*
     * Size is large, assume best to place the slab management obj
     * off-slab (should allow better packing of objs).
     */
    flags |= CFLGS_OFF_SLAB;

  // 如果请求硬件高速缓存对齐,则对象的大小将与硬件高速缓存对齐
  if (flags & SLAB_HWCACHE_ALIGN) {
    /* Need to adjust size so that objs are cache aligned. */
    /* Small obj size, can get at least two per cache line. */
    /* FIXME: only power of 2 supported, was better */
  // 如果对象符合对齐,则试着将对象打包到高速缓存中的一行。对于那些带有大
  // L1高速缓存字节的系统(如Alpha和奔腾4),这很重要。align将被调整为对齐硬件高速缓存
  // 边界的最小值。对大的L1高速缓存行,两个或者更多的小对象将可以填入到一行中。例如,
  // 从32位高速缓存的两个对象将在奔腾4中填入一个高速缓存行。    
    while (size < align/2)
      align /= 2;
  // 将高速缓存大小向上取整为硬件高速缓存边界。      
    size = (size+align-1)&(~(align-1));
  }

  /* Cal size (in pages) of slabs, and the num of objs per slab.
   * This could be made much more intelligent.  For now, try to avoid
   * using high page-orders for slabs.  When the gfp() funcs are more
   * friendly towards high-order requests, this should be changed.
   */
// 计算slab中将填入多少个对象,并在需要时调整slab大小。  
  do {
    unsigned int break_flag = 0;
cal_wastage:
  // kmem_cache_estimate。计算以gfp次序填入slab的对象数,并
  // 计算剩余的字节数。
    kmem_cache_estimate(cachep->gfporder, size, flags,
            &left_over, &cachep->num);
  // 在使用了 offslab slab描述符后,如果填入slab的对象数超过了 slab存放的对
  // 象数,则设置break_flag。            
    if (break_flag)
      break;
  // 页面使用的次数不能超过MAX_GFP_ORDER(5)。     
    if (cachep->gfporder >= MAX_GFP_ORDER)
      break;
  // 如果对象不能填入,则跳转到next,在那里将增加高速缓存使用的gfporder。     
    if (!cachep->num)
      goto next;
  // 如果在高速缓存中没有slab描述符,但对象数超过了bufctl的off-slab所确定的数
  // 目,测……      
    if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) {
      /* Oops, this num of objs will cause problems. */
  // 降低使用页面的次数。     
      cachep->gfporder--;
  // 设置break_flag标志位,这样将退出循环。
      break_flag++;
  // 计算新的消耗数。     
      goto cal_wastage;
    }

    /*
     * Large num of objs is good, but v. large slabs are currently
     * bad for the gfp()s.
     */
  // 除非填充了0号对象,slab_break_gfp_order将是不超过slab的次数。这里检查以保证不会超过次数。   
    if (cachep->gfporder >= slab_break_gfp_order)
      break;

// 对外部碎片的粗略检查。如果高速缓存的消耗量小于八分之一,则可以接受。
    if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))
      break;  /* Acceptable internal fragmentation. */
next:
  // 如果碎片过多,则这里增加gfp次,并重新计算存储的对象数目以及消耗量。
    cachep->gfporder++;
  } while (1);

  // 在调整后如果对象还是不能填入高速缓存,则不能创建该对象。
  if (!cachep->num) {
    printk("kmem_cache_create: couldn't create cache %s.\n", name);
  // 释放高速缓存描述符并设指针指向NULL。   
    kmem_cache_free(&cache_cache, cachep);
  // 跳转到oops,在那里仅返回NULL指针。    
    cachep = NULL;
    goto opps;
  }
// 这一块将slab大小对齐于硬件高速缓存。
  // slab_size是slab描述符的总大小,不是slab自身的大小。它是一个固定的slab_t结
  // 构,大小为对象数乘以bufctl的结果。 
  slab_size = L1_CACHE_ALIGN(cachep->num*sizeof(kmem_bufctl_t)+sizeof(slab_t));

  /*
   * If the slab has been placed off-slab, and we have enough space then
   * move it on-slab. This is at the expense of any extra colouring.
   */
  // 如果有足够的空间留给slab描述符,则这里指定为放置off-slab描述符,这里
  // 移除标志位并更新left_over字节量。这样做会影响到高速缓存着色,但是对off-slab描述符 
  // 而言,这不是问题。
  if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
    flags &= ~CFLGS_OFF_SLAB;
    left_over -= slab_size;
  }

  /* Offset must be a multiple of the alignment. */
// 计算着色偏移。
  // offset是请求调用者页面中的偏移。这里保证所请求偏移在使用中的高速缓存
  // 的正确对齐方式中。  
  offset += (align-1);
  offset &= ~(align-1);
  // 如果由于某个原因偏移为0,则这里设置它与CPU高速缓存对齐。
  if (!offset)
    offset = L1_CACHE_BYTES;
  // 这个偏移用于在不同的高速缓存行中存入对象。对创建的每个slab,将给予不同的
  // 颜色偏移。    
  cachep->colour_off = offset;
  // 可以使用的不同偏移数。
  cachep->colour = left_over/offset;

// 这一块初始化高速缓存的其他字段。
  /* init remaining fields */
  // 对只有一个页面的slab高速缓存,设置CFLAGS_OPTIMIZE标志位,这实际
  // 上没有什么效果,因为并没有用到该标志位。
  if (!cachep->gfporder && !(flags & CFLGS_OFF_SLAB))
    flags |= CFLGS_OPTIMIZE;

  // 设置高速缓存静态标志位。
  cachep->flags = flags;
  // 将gfpflags清0。这是个无效操作,因为可以使用memset()在分配了高速缓存描述
  // 符后清除这些标志位。
  cachep->gfpflags = 0;
  // 如果slab用于DMA,则这里设置GFP_DMA标志位,这样伙伴分配器将使用ZONE_DMA。
  if (flags & SLAB_CACHE_DMA)
    cachep->gfpflags |= GFP_DMA;
  // 为访问高速缓存初始化自旋锁。   
  spin_lock_init(&cachep->spinlock);
  // 复制对象大小,如果需要,则按硬件高速缓存大小对齐。
  cachep->objsize = size;
  // 初始化slab链表。
  INIT_LIST_HEAD(&cachep->slabs_full);
  INIT_LIST_HEAD(&cachep->slabs_partial);
  INIT_LIST_HEAD(&cachep->slabs_free);
  // 如果描述符是off-slab的,这里分配和放置一个slab管理器以在slabp_cache使用。
  if (flags & CFLGS_OFF_SLAB)
    cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);
  // 设置指向构造函数和销毁函数的指针。    
  cachep->ctor = ctor;
  cachep->dtor = dtor;
  /* Copy name over so we don't have problems with unloaded modules */
  // 复制可读名。
  strcpy(cachep->name, name);

#ifdef CONFIG_SMP
  // 如果per-CPU可用,这里为该高速缓存创建一个集(见8.5节)。
  if (g_cpucache_up)
    enable_cpucache(cachep);
#endif
// 这一块将新的高速缓存加入到高速缓存链表中。
  /* Need the semaphore to access the chain. */
  // 获取对高速缓存链表同步访问的信号量。
  down(&cache_chain_sem);
  {
    struct list_head *p;
// 检查在高速缓存链表中的每个高速缓存,并保证没有其他的高速缓存具有相同
// 的名字。如果有相同的名字,则意味着创建了具有相同类型的两个高速缓存,这是一个严重
// 的 bug。
    list_for_each(p, &cache_chain) {
  // 从链表中获取高速缓存。    
      kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);

      /* The name field is constant - no lock needed. */
  // 比较名字,如果相同则调用BUG()。值的注意的是并不删除新的高速缓存,这
  // 个错误是开发时由于不规范编程而造成的,是一个常见的问题。     
      if (!strcmp(pc->name, name))
        BUG();
    }
  }

  /* There is no reason to lock our new cache before we
   * link it in - no one knows about it yet...
   */
  // 将高速缓存链到链表中。   
  list_add(&cachep->next, &cache_chain);
  // 释放高速缓存链表信号量。
  up(&cache_chain_sem);
opps:
  // 返回新的高速缓存指针。
  return cachep;
}

    使用 kmem_cache_alloc 分配一个 kmem_cache_t,然后根据 size 计算 cachep->gfporder,最后把这个 kmem_cache_t 加入到全局链表中。

① ⇒ kmem_cache_alloc

    kmem_cache_alloc 函数

② ⇒ kmem_cache_estimate

    kmem_cache_estimate 函数

③ ⇒ kmem_cache_free

    kmem_cache_free 函数

④ ⇒ kmem_find_general_cachep

    kmem_find_general_cachep 函数

(2)计算 slab 上的对象数量

(a)kmem_cache_estimate

传送门 8.2.1 存储 slab 描述符

    在创建高速缓存时,可以存放多少个对象以及需要耗费多少空间是确定的。下面的函数计算有多少个对象可以存储,并且考虑到 slabbufctls 必须以 on-slab 方式存放。

// mm/slab.c
/* Cal the num objs, wastage, and bytes left over for a given slab size. */
// 这个函数的参数如下:
// gfporder为每个slab分配2^gfporder个页面。
// size是每个对象的大小。
// flags是高速缓存标志位。
// left_over是slab中剩余的字节数,由调用者返回。
// num是填入slab的对象数,由调用者返回。
static void kmem_cache_estimate (unsigned long gfporder, size_t size,
     int flags, size_t *left_over, unsigned int *num)
{
  int i;
  // wastage是一个递减函数。它从可能的最大消耗量开始。
  size_t wastage = PAGE_SIZE<<gfporder;
  // extra是需要存储kmem_bufctl_t的字节数。
  size_t extra = 0;
  // base是在slab开始处的可用内存地址
  size_t base = 0;

  // 如果slab描述符保存在高速缓存中,基地址开始于slab_t结构的末端,存放bufctl所
  // 需的字节数是kmem_bufctl_t的大小。
  if (!(flags & CFLGS_OFF_SLAB)) {
    base = sizeof(slab_t);
    extra = sizeof(kmem_bufctl_t);
  }
  // i变成slab可以拥有的对象数。
  i = 0;
  // 将高速缓存可以容纳的对象数加起来。i * size 是对象自身的大小。
  // L1_CACHE_ALIGN(base + i * extra)有一点灵活性。这是一个计算需要存储在slab中每个
  // 对象所需kmem_bufctl_t的内存量函数。由于它在slab的起点,所以它与L1高速缓存对齐,
  // 这样slab中的第1个对象将与硬件高速缓存对齐。i* extra将计算为容纳该对象kmem_bufctl_t
  // 所需的空间。由于消耗量以slab的大小计算,所以在这里用于负载。
  while (i*size + L1_CACHE_ALIGN(base+i*extra) <= wastage)
    i++;
  // 由于前面循环计数直到slab溢出,所以对象的数量记为 i-1。    
  if (i > 0)
    i--;
  // SLAB_LIMIT是一个slab可存储对象的最大绝对值。它定义为OxffffFFFE, 
  // 因为这是kmem_bufctl_t可以容纳的最大无符号整数中最大的数值。
  if (i > SLAB_LIMIT)
    i = SLAB_LIMIT;
  // num现在是slab可以容纳的对象数。
  *num = i;
  // 减去消耗对象所占据的空间。
  wastage -= i*size;
  // 减去由kmem_bufctl_t所占据的空间。
  wastage -= L1_CACHE_ALIGN(base+i*extra);
  // 现在消耗量计算为slab中剩下的空间。
  *left_over = wastage;
}

    计算 gfporder 页面可以容纳多少个 slab 对象。

(3)收缩高速缓存

   kmem_cache_shrink() 的调用图如图 8.5 所示。提供两套收缩函数。kmem_cache_shrink() 从 slabs_free 中移除所有 slab ,并返回释放的页面数作为结果。__kmem_cache_shrink() 从 slabs_free 中释放所有的 slab ,然后验证 slabs_partial 和 slabs_free 是否为空。这在销毁高速缓存时比较重要,在那时它不关心释放的页面数,而只关心高速缓存是否为空。

(a)kmem_cache_shrink

传送门 8.1.8 收缩高速缓存

    这个函数进行基本的调试检查,然后获取高速缓存描述符,接着释放 slab。在这时,它也用于调用 drain_cpu_caches() 来释放在 per-CPU 高速缓存中的对象。很奇怪的是,由于对象可以在 per-CPU 高速缓存中分配,slab 可能不能被释放,这里却移除,并不使用。

// mm/slab.c
/**
 * kmem_cache_shrink - Shrink a cache.
 * @cachep: The cache to shrink.
 *
 * Releases as many slabs as possible for a cache.
 * Returns number of pages released.
 */
// 参数是被收缩的高速缓存。
// 进行如下检查:
//    高速缓存不为NULL。
//    调用者不是一个异常。
//    高速缓存在高速缓存链表中,且不是一个坏指针。
int kmem_cache_shrink(kmem_cache_t *cachep)
{
  int ret;

  if (!cachep || in_interrupt() || !is_chained_kmem_cache(cachep))
    BUG();
  // 获取高速缓存描述符锁并关中断。
  spin_lock_irq(&cachep->spinlock);
  // 收缩高速缓存区。
  ret = __kmem_cache_shrink_locked(cachep); 
  // 释放高速缓存锁并开中断。
  spin_unlock_irq(&cachep->spinlock);
  // 返回释放的页面数,但是并没有考虑消耗 CPU 时释放的对象。 
  return ret << cachep->gfporder;
}
(b)__kmem_cache_shrink

    这个函数与 kmem_cache_shrink() 相似,除了它在高速缓存为空时返回。这在销毁高速缓存时比较重要,在那时,释放的内存量并不很重要,重要的是安全地删除高速缓存,且不泄漏内存。

// mm/slab.c
static int __kmem_cache_shrink(kmem_cache_t *cachep)
{
  int ret;
  // 从per-CPU对象高速缓存中移除所有对象。
  drain_cpu_caches(cachep);
  // 获取高速缓存描述符锁并关中断
  spin_lock_irq(&cachep->spinlock);
  // 释放slabs_free链表中的所有slab。
  __kmem_cache_shrink_locked(cachep);
  // 检查slabs_partial和slabs_full链表是否为空
  ret = !list_empty(&cachep->slabs_full) ||
    !list_empty(&cachep->slabs_partial);
  // 释放高速缓存描述符锁并重新打开中断  
  spin_unlock_irq(&cachep->spinlock);
  // 如果释放了高速缓存中所有slab,则返回。
  return ret;
}


(c)__kmem_cache_shrink_locked

    这里完成释放 slab 的实际工作。它将不断地销毁 slab 直到设置了增长标志位,表明高速缓存正在使用或者直到 slabs_free 中没有更多的 slab

// mm/slab.c

/*
 * Called with the &cachep->spinlock held, returns number of slabs released
 */
static int __kmem_cache_shrink_locked(kmem_cache_t *cachep)
{
  slab_t *slabp;
  int ret = 0;

  /* If the cache is growing, stop shrinking. */
  // 当高速缓存不再增长时,这里释放slab。
  // 每创建一个 slab_t ,growing 增长一次,所以这里遍历所有的 slab_t ?
  while (!cachep->growing) {
    struct list_head *p;
// 获取slabs_free链表中最后一个slab。
    p = cachep->slabs_free.prev;
    if (p == &cachep->slabs_free)
      break;

    slabp = list_entry(cachep->slabs_free.prev, slab_t, list);
#if DEBUG
  // 如果调试可用,则这里保证它目前不在使用中。如果不可用,则它首先就不会
  // 在slabs_free链表中。
    if (slabp->inuse)
      BUG();
#endif
  // 从链表中移除slab。
    list_del(&slabp->list);
  // 重新打开中断。调用这个函数时关闭了中断,所以需要尽快地释放中断。
    spin_unlock_irq(&cachep->spinlock);
  // 利用 kmem_slab_destroy() 删除 slab。    
    kmem_slab_destroy(cachep, slabp);
  // 记录释放的slab数。    
    ret++;
  // 获取高速缓存描述符锁并关中断。    
    spin_lock_irq(&cachep->spinlock);
  }
  return ret;
}

    主要释放 kmem_cache_t->slabs_free 中的 slab_t

① ⇒ kmem_slab_destroy

    kmem_slab_destroy 函数

(4)销毁高速缓存

    当卸载某个模块时,如果创建了高速缓存,则它负责销毁高速缓存。由于在载入一个模块时已经保证没有两个相同名字的高速缓存。内核代码经常不销毁它自己的高速缓存,因为它们在整个系统生命周期中都一直存在。销毁一个高速缓存的步骤如下:

  • 从高速缓存链表中删除该高速缓存。
  • 收缩高速缓存,删除所有的 slab(见 8.1.8 小节)。
  • 释放所有的 per-CPU 高速缓存( kfree() )。
  • cache_cache 中删除高速缓存描述符(见 8.3.3 小节)。
(a)kmem_cache_destroy

    这个函数的调用图如图 8.7 所示。

// mm/slab.c

/**
 * kmem_cache_destroy - delete a cache
 * @cachep: the cache to destroy
 *
 * Remove a kmem_cache_t object from the slab cache.
 * Returns 0 on success.
 *
 * It is expected this function will be called by a module when it is
 * unloaded.  This will remove the cache completely, and avoid a duplicate
 * cache being allocated each time a module is loaded and unloaded, if the
 * module doesn't have persistent in-kernel storage across loads and unloads.
 *
 * The cache must be empty before calling this function.
 *
 * The caller must guarantee that noone will allocate memory from the cache
 * during the kmem_cache_destroy().
 */
int kmem_cache_destroy (kmem_cache_t * cachep)
{
  // 有效性检查。保证cachep不为空,没有中断正尝试使用这个函数,以及高速
  // 缓存没有标记为增长,表明它正在使用中。
  if (!cachep || in_interrupt() || cachep->growing)
    BUG();

  /* Find the cache in the chain of caches. */
  // 获取访问高速缓存链表的信号量。
  down(&cache_chain_sem);
  /* the chain is never empty, cache_cache is never destroyed */
  // 从高速缓存链表中获取一个链表项。
  if (clock_searchp == cachep)
    clock_searchp = list_entry(cachep->next.next,
            kmem_cache_t, next);
  // 从高速缓存链表中删除该高速缓存。           
  list_del(&cachep->next);
  // 释放高速缓存链表信号量。
  up(&cache_chain_sem);
  // 利用__kmem_cache_shrink()收缩高速缓存,释放所有的slab 。
  if (__kmem_cache_shrink(cachep)) {
  // 如果在高速缓存中还存在slab,则这个收缩函数返回真。如果它们是无法销
  // 毁的高速缓存,则这里将其加入到高速缓存链表中,并报告错误。
    printk(KERN_ERR "kmem_cache_destroy: Can't free all objects %p\n",
           cachep);
    down(&cache_chain_sem);
    list_add(&cachep->next,&cache_chain);
    up(&cache_chain_sem);
    return 1;
  }
  
#ifdef CONFIG_SMP
  {
    int i;
  // 如果SMP可用,则利用 kfree() 删除per-CPU数据结构。   
    for (i = 0; i < NR_CPUS; i++)
      kfree(cachep->cpudata[i]);
  }
#endif
  // 利用kmem_cache_free()从cache_cache中删除高速缓存描述符。 
  kmem_cache_free(&cache_cache, cachep);

  return 0;
}
① ⇒ __kmem_cache_shrink

    __kmem_cache_shrink 函数

② ⇒ kfree

    kfree 函数

③ ⇒ kmem_cache_free

    kmem_cache_free 函数

(5)回收高速缓存

(a)kmem_cache_reap

    这个函数的调用如图 8.4 所示。由于这个函数比较大,所以将其分成几个独立的小节。第 1 部分是一个简单的函数开始部分,第 2 部分选择要回收的高速缓冲,第 3 部分是释放 slab,基本的任务在 8.1.7 小节中描述。

传送门 8.1.7 回收高速缓存

// mm/slab.c
/**
 * kmem_cache_reap - Reclaim memory from caches.
 * @gfp_mask: the type of memory required.
 *
 * Called from do_try_to_free_pages() and __alloc_pages()
 */
// 惟一的一个参数是GFP标志位。惟一做的检查是对__GFP_WAIT标志位进行检
// 查。作为惟一的调用者,kswapd可以睡眠。这个参数实际上没有用。 
int kmem_cache_reap (int gfp_mask)
{
  slab_t *slabp;
  kmem_cache_t *searchp;
  kmem_cache_t *best_cachep;
  unsigned int best_pages;
  unsigned int best_len;
  unsigned int scan;
  int ret = 0;
  
  // 调用者可以睡眠吗?如果可以,则在这里获取信号量。
  if (gfp_mask & __GFP_WAIT)
    down(&cache_chain_sem);
  else
  // 如果不可以睡眠,则这里试着获取信号量。如果没有信号量可用,则返回。
    if (down_trylock(&cache_chain_sem))
      return 0;

  // REAP_SCANLEN(10)是要检查的高速缓存数目。
  scan = REAP_SCANLEN;
  best_len = 0;
  best_pages = 0;
  best_cachep = NULL;
  // 设置searchp为上次回收时检查过的最后一个高速缓存。
  searchp = clock_searchp;
// 这一块考察REAP_SCANLEN数量个高速缓存,并选择一个来释放。 
  do {
    unsigned int pages;
    struct list_head* p;
    unsigned int full_free;

    /* It's safe to test this without holding the cache-lock. */
    if (searchp->flags & SLAB_NO_REAP)
      goto next;
  // 获取一个对高速缓存描述符的中断安全锁。      
    spin_lock_irq(&searchp->spinlock);
  // 如果高速缓存在增长,这里跳过它。   
    if (searchp->growing)
      goto next_unlock;
  // 如果高速缓存最近增长过,则这里跳过它并清除标志位。      
    if (searchp->dflags & DFLGS_GROWN) {
      searchp->dflags &= ~DFLGS_GROWN;
      goto next_unlock;
    }
#ifdef CONFIG_SMP
  // 释放per-CPU对象到全局池中。
    {
      cpucache_t *cc = cc_data(searchp);
      if (cc && cc->avail) {
        __free_block(searchp, cc_entry(cc), cc->avail);
        cc->avail = 0;
      }
    }
#endif

    full_free = 0;
    p = searchp->slabs_free.next;
  // 计算在slab_free链表中的slab数量。    
    while (p != &searchp->slabs_free) {
      slabp = list_entry(p, slab_t, list);
#if DEBUG
      if (slabp->inuse)
        BUG();
#endif
      full_free++;
      p = p->next;
    }

    /*
     * Try to avoid slabs with constructors and/or
     * more than one page per slab (as it can be difficult
     * to get high orders from gfp()).
     */
  // 计算所有slab持有的页面数量。    
    pages = full_free * (1<<searchp->gfporder);
  // 如果对象有构造函数,则这里将其页面计数减为1/5,这样就不太可能选择它来回收。    
    if (searchp->ctor)
      pages = (pages*4+1)/5;
  // 如果slab由多于一页组成,这里减少页面计数到1/5。这是因为难以获取高次的页面。      
    if (searchp->gfporder)
      pages = (pages*4+1)/5;
  // 如果这是目前找到的最好的回收候选高速缓存,这里检查它是否适合回收。      
    if (pages > best_pages) {
  // 记录新的最大量。   
      best_cachep = searchp;
  // 记录best_len ,这样很容易知道空闲链表中 slab 一半的slab数量。     
      best_len = full_free;
      best_pages = pages;
  // 如果这个高速缓存适合回收,则 ...     
      if (pages >= REAP_PERFECT) {
  // 更新 clock_searchp。      
        clock_searchp = list_entry(searchp->next.next,
              kmem_cache_t,next);
  // 转到perfect在那里释放一半的slab。             
        goto perfect;
      }
    }
next_unlock:
    spin_unlock_irq(&searchp->spinlock);
next:
    searchp = list_entry(searchp->next.next,kmem_cache_t,next);
  // 在达到REAP_SCANLEN之前,且在还没有循环遍历整个高速缓存链表之前一直扫描
  } while (--scan && searchp != clock_searchp);
  
// 这一块从选择的高速缓存中释放一半的slab.
  // 更新clock_searchp 以准备下一次高速缓存回收。
  clock_searchp = searchp;
  // 如果没有找到一个高速缓存,则转到 out 以释放高速缓存链表并退出。
  if (!best_cachep)
    /* couldn't find anything to reap */
    goto out;
  
  // 获取高速缓存链表自旋锁,并关闭中断。cachep描述符必须持有一个中断安全的
  // 锁,因为一些高速缓存可能在中断上下文中使用。slab分配器无法区分中断安全
  // 和中断不安全的高速缓存。
  spin_lock_irq(&best_cachep->spinlock);
perfect:
  /* free only 50% of the free slabs */
  // 调整best_len为要释放的slab数量。
  best_len = (best_len + 1)/2;
// 释放 best_len 个 slab。  
  for (scan = 0; scan < best_len; scan++) {
    struct list_head *p;
  // 如果高速缓存正在增长,则在这里退出。
    if (best_cachep->growing)
      break;
  // 从链表中获得一个slab。    
    p = best_cachep->slabs_free.prev;
  // 如果在链表中没有一个slab,则在这里退出。     
    if (p == &best_cachep->slabs_free)
      break;
  // 获取slab指针。    
    slabp = list_entry(p,slab_t,list);
#if DEBUG
  // 如果调试可用,则这里保证没有活动的对象在slab中。
    if (slabp->inuse)
      BUG();
#endif
  // 从 slabs_free 链表中移除 slab。
    list_del(&slabp->list);
  // 如果可用则更新统计计数。   
    STATS_INC_REAPED(best_cachep);

    /* Safe to drop the lock. The slab is no longer linked to the
     * cache.
     */
  // 释放高速缓存并开中断。     
    spin_unlock_irq(&best_cachep->spinlock);
  // 释放 slab(见 8.2.8 小节)。   
    kmem_slab_destroy(best_cachep, slabp);
  // 重新获取高速缓存描述符的自旋锁并关中断。   
    spin_lock_irq(&best_cachep->spinlock);
  }
  // 释放高速缓存描述符并开中断。
  spin_unlock_irq(&best_cachep->spinlock);
  // ret是已经释放的页面数。
  ret = scan * (1 << best_cachep->gfporder);
out:
  // 释放高速缓存信号量并返回释放的页面数。
  up(&cache_chain_sem);
  return ret;
}

    考察 clock_searchp 开始 REAP_SCANLEN(10)kmem_cache_tslabs_freeslat_t 的数量。选择最多的进行释放。

2、slabs

(1)存储 slab 描述符

传送门 8.2 slabs

(a)kmem_cache_slabmgmt

    这个函数或者为不在高速缓存中的 slab 描述符分配空间,或者在 slab 开始的地方为描述符和 bufctls 保留足够的空间。

// mm/slab.c

/* Get the memory for a slab management obj. */
// 这个函数的参数如下:
//  cachep是slab要分配的高速缓存。
//  objp是当调用该函数时指向slab的开始处。
//  colour_off是这个slab的颜色偏移。
//  local_flags是高速缓存的标志位。
static inline slab_t * kmem_cache_slabmgmt (kmem_cache_t *cachep,
      void *objp, int colour_off, int local_flags)
{
  slab_t *slabp;

// 如果slab描述符并不存放在高速缓存中,则... 
  if (OFF_SLAB(cachep)) {
    /* Slab management obj is off-slab. */
  // 从指定大小的高速缓存中分配内存。在创建高速缓存时,slabp_cache设置为分配
  // 内存指定大小的高速缓存。   
    slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);
  // 如果分配失败,这里返回。   
    if (!slabp)
      return NULL;
  } else {
    /* FIXME: change to
      slabp = objp
     * if you enable OPTIMIZE
     */
// 在slab的起点保留空间。  
  // slab的地址将是slab的起点(objp)加上颜色偏移。  
    slabp = objp+colour_off;
  // colour_off 计算为放置第一个对象的偏移位置。该地址对齐于L1高速缓存,
  // cachep—>num * sizeof(kmem_bufctl_t) 是容纳slab中各对象的bufctls所需的空间大小,
  // sizeof(slab_t)是slab描述符的大小。这里在slab起点已经有效地保留了空间。   
    colour_off += L1_CACHE_ALIGN(cachep->num *
        sizeof(kmem_bufctl_t) + sizeof(slab_t));
  }
  // 在slab中使用的对象数为0。
  slabp->inuse = 0;
  // 更新 colouroff 以放置新对象。
  slabp->colouroff = colour_off;
  // 第1个对象的地址计算为slab的开始地址加上偏移。
  slabp->s_mem = objp+colour_off;

  return slabp;
}


kmem_cache_slabmgmt 函数主要用来分配一个 slab_t 对象(slab 管理对象):

  • 如果 slab_tslab 没放在一起(OFF_SLAB(cachep)),则调用 kmem_cache_alloc 分配一个 slab_t
  • 否则,从 objp 内存处分配一个 slab_t ,同时调整 colour_off 偏移。

    最后更新 slabpinusecolouroffs_mem 字段域。

① ⇒ kmem_cache_alloc

    kmem_cache_alloc 函数

(b)kmem_find_general_cachep

    如果 slab 描述符不在 slab 中保存,则这个函数在创建高速缓存时被调用,它将找到合适大小的高速缓存供使用,并将其存放在 slabp_cache 的高速缓存描述符中。

// mm/slab.c
// size是slab描述符的大小,gfpflags总是为0,因为DMA内存不需要slab描述符。
kmem_cache_t * kmem_find_general_cachep (size_t size, int gfpflags)
{
  cache_sizes_t *csizep = cache_sizes;

  /* This function could be moved to the header file, and
   * made inline so consumers can quickly determine what
   * cache pointer they require.
   */
  // 从最小的大小开始,这里不断增加大小,直至找到一个足够大的缓冲区来存
  // 放slab描述符的高速缓存。  
  for ( ; csizep->cs_size; csizep++) {
    if (size > csizep->cs_size)
      continue;
    break;
  }
  // 返回一个普通的或DMA大小的高速缓存,这取决于传入的gfpflags标志位。
  // 实际上,仅传回 cs_cachep。
  return (gfpflags & GFP_DMA) ? csizep->cs_dmacachep : csizep->cs_cachep;
}

(2)创建 slab

(a)kmem_cache_grow

    这个函数的调用图如图 8.11 所示。这个函数的基本步骤如下:

  • 进行基本的有效性检查以防止错误使用。
  • 计算该 slab 中对象的颜色偏移。
  • slab 分配内存,并获取 slab 描述符。
  • slab 使用的页面链接到 slab 和高速缓存描述符。
  • 初始化 slab 中的对象。
  • slab 加入高速缓存。

传送门 8.2.2 创建 slab

// mm/slab.c
/*
 * Grow (by 1) the number of slabs within a cache.  This is called by
 * kmem_cache_alloc() when there are no active objs left in a cache.
 */
// 这是一些基本的声明。这个函数的参数如下:
//  cachep是要分配新slab的高速缓存
//  flags创建slab的标志位。 
static int kmem_cache_grow (kmem_cache_t * cachep, int flags)
{
  slab_t  *slabp;
  struct page *page;
  void    *objp;
  size_t     offset;
  unsigned int   i, local_flags;
  unsigned long  ctor_flags;
  unsigned long  save_flags;

  /* Be lazy and only check for valid flags here,
   * keeping it out of the critical path in kmem_cache_alloc().
   */  
// 这里进行基本的有效性检查以防止错误使用。在这里进行检查,而不是利用 kmem_cache_alloc()
// 来保护速度优先的路径。这里不需要在每次分配对象时都检查这些标志位。 
  // 保证仅使用允许的标志位来进行分配。
  if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))
    BUG();
  // 如果设置了这些标志位,则不增长高速缓存,实际上,从来不设置这些标志位。    
  if (flags & SLAB_NO_GROW)
    return 0;

  /*
   * The test for missing atomic flag is performed here, rather than
   * the more obvious place, simply to reduce the critical path length
   * in kmem_cache_alloc(). If a caller is seriously mis-behaving they
   * will eventually be caught here (where it matters).
   */
  // 如果在中断上下文中调用,保证设置了ATOMIC标志位。这样我们在调用
  // kmem_getpages() 时不会睡眠。  
  if (in_interrupt() && (flags & SLAB_LEVEL_MASK) != SLAB_ATOMIC)
    BUG();
  // 这个标志位告知构造函数初始化对象。
  ctor_flags = SLAB_CTOR_CONSTRUCTOR;
  // local_flags 仅与页面分配器相关。
  local_flags = (flags & SLAB_LEVEL_MASK);
  // 如果设置了 SLAB_ATOMIC 标志位,则构造函数在创建一次新分配时就需要知道它。
  if (local_flags == SLAB_ATOMIC)
    /*
     * Not allowed to sleep.  Need to tell a constructor about
     * this - it might need to know...
     */
    ctor_flags |= SLAB_CTOR_ATOMIC;

// 计算该slab中对象的颜色偏移。
  /* About to mess with non-constant members - lock. */
  // 获取访问高速缓存描述符的中断安全锁。
  spin_lock_irqsave(&cachep->spinlock, save_flags);

  /* Get colour for the slab, and cal the next value. */
  // 获取该slab中的对象偏移。
  offset = cachep->colour_next;
  // 移到下一个颜色偏移。
  cachep->colour_next++;
  // 如果达到colour,就没有更多的偏移,这里将 colour_next 重置为0。
  if (cachep->colour_next >= cachep->colour)
    cachep->colour_next = 0;
  // colour_off是每个偏移的大小。所以 offset * colour_off 表明对象偏移的字节数。    
  offset *= cachep->colour_off;
  // 将高速缓存标记为增长,这样 kmem_cache_reap() 将忽略该高速缓存。
  cachep->dflags |= DFLGS_GROWN;
  // 增加增长该高速缓存的调用者计数。
  cachep->growing++;
  // 释放自旋锁并重新打开中断。
  spin_unlock_irqrestore(&cachep->spinlock, save_flags);

  /* A series of memory allocations for a new slab.
   * Neither the cache-chain semaphore, or cache-lock, are
   * held, but the incrementing c_growing prevents this
   * cache from being reaped or shrunk.
   * Note: The cache could be selected in for reaping in
   * kmem_cache_reap(), but when the final test is made the
   * growing value will be seen.
   */

// 为slab分配内存并获取一个slab描述符。
  /* Get mem for the objs. */
  // 利用 kmem_getpages() 从页面分配器中为slab分配页面。
  if (!(objp = kmem_getpages(cachep, flags)))
    goto failed;

  /* Get slab management. */
  // 利用 kmem_cache_slabmgmt() 获取一个 slab 描述符。
  if (!(slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags)))
    goto opps1;

// 将slab使用的页面链接到slab和高速缓存描述符。
  /* Nasty!!!!!! I hope this is OK. */
  // i是slab使用的页面数。每个页面都必须链接到slab和高速缓存描述符。
  i = 1 << cachep->gfporder;
  // objp是一个指向slab起点的指针。宏virt_to_page()将赋予struct page该地址值。
  page = virt_to_page(objp);
// 将每个页面链接到slab字段以及高速缓存描述符。 
  do {
  // SET_PAGE_CACHE()使用page->link_next字段将页面链接到高速缓存描述符。  
    SET_PAGE_CACHE(page, cachep);
  // SET_PAGE_SLAB()使用page->link_prev字段将页面链接到高速缓存描述符。   
    SET_PAGE_SLAB(page, slabp);
  // 设置PG_slab页面字段。整个PG_flags在表2.1中列出。    
    PageSetSlab(page);
  // 移到slab中下一个页面进行链接。    
    page++;
  } while (--i);

  // 初始化对象
  kmem_cache_init_objs(cachep, slabp, ctor_flags);
// 将slab加入到高速缓存中。
  // 以一种中断安全的方式获取高速缓存描述符自旋锁。
  spin_lock_irqsave(&cachep->spinlock, save_flags);
  // 将增长计数减1。
  cachep->growing--;

  /* Make slab active. */
  // 将slab加入到slabs_free链表的末尾。
  list_add_tail(&slabp->list, &cachep->slabs_free);
  // 如果设置了 STATS,这里增加 cachepf->grown 字段 STATS_INC_GROWN()
  STATS_INC_GROWN(cachep);
  // 设置失败为0,这个字段在其他地方没有用到。
  cachep->failures = 0;
  // 以一种中断安全的方式释放高速缓存描述符自旋锁。
  spin_unlock_irqrestore(&cachep->spinlock, save_flags);
  // 返回成功。
  return 1;
  
// 这一块进行错误处理: 
  // 如果为slab分配了页面,则转到oopsl。必须在这里释放这些页面。
opps1:
  kmem_freepages(cachep, objp);
failed:
  // 为访问高速缓存描述符获取自旋锁。
  spin_lock_irqsave(&cachep->spinlock, save_flags);
  // 将增长计数减1。
  cachep->growing--;
  // 释放自旋锁。
  spin_unlock_irqrestore(&cachep->spinlock, save_flags);
  // 返回假。
  return 0;
}
① ⇒ kmem_getpages

    kmem_getpages 函数

② ⇒ kmem_cache_slabmgmt

    kmem_cache_slabmgmt 函数

③ ⇒ kmem_cache_init_objs

    kmem_cache_init_objs 函数

④ ⇒ kmem_freepages

    kmem_freepages 函数

(3)销毁 slab

(a)kmem_slab_destroy

    这个函数的调用图如图 8.13 所示。为了便于阅读,调试部分已经从这个函数中略去,它们几乎与分配对象时的调试部分相同。如何标记以及检查感染模式见 H.3.1.1。

传送门 8.2.8 销毁 slab

// mm/slab.c
/* Destroy all the objs in a slab, and release the mem back to the system.
 * Before calling the slab must have been unlinked from the cache.
 * The cache-lock is not held/needed.
 */
static void kmem_slab_destroy (kmem_cache_t *cachep, slab_t *slabp)
{
// 如果有销毁函数可用,这里将对slab中每个对象调用它。
  if (cachep->dtor
#if DEBUG
    || cachep->flags & (SLAB_POISON | SLAB_RED_ZONE)
#endif
  ) {
    int i;
// 遍历slab中每个对象。   
    for (i = 0; i < cachep->num; i++) {
  // 计算要销毁对象的地址。    
      void* objp = slabp->s_mem+cachep->objsize*i;
#if DEBUG
      if (cachep->flags & SLAB_RED_ZONE) {
        if (*((unsigned long*)(objp)) != RED_MAGIC1)
          BUG();
        if (*((unsigned long*)(objp + cachep->objsize
            -BYTES_PER_WORD)) != RED_MAGIC1)
          BUG();
        objp += BYTES_PER_WORD;
      }
#endif
  // 调用销毁函数。
      if (cachep->dtor)
        (cachep->dtor)(objp, cachep, 0);
#if DEBUG
      if (cachep->flags & SLAB_RED_ZONE) {
        objp -= BYTES_PER_WORD;
      } 
      if ((cachep->flags & SLAB_POISON)  &&
        kmem_check_poison_obj(cachep, objp))
        BUG();
#endif
    }
  }
  // 释放slab中使用的页面。
  kmem_freepages(cachep, slabp->s_mem-slabp->colouroff);
  // 如果slab描述符不在slab中,则这里使用它所用到的内存。
  if (OFF_SLAB(cachep))
    kmem_cache_free(cachep->slabp_cache, slabp);
}
① ⇒ kmem_freepages

    kmem_freepages 函数

② ⇒ kmem_cache_free

    kmem_cache_free 函数

深入理解Linux虚拟内存管理(六)(中):https://developer.aliyun.com/article/1597819

目录
相关文章
|
3月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
195 6
|
13天前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
|
13天前
|
缓存 Linux
如何检查 Linux 内存使用量是否耗尽?
何检查 Linux 内存使用量是否耗尽?
|
22天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
5天前
|
存储 算法 安全
深入理解Linux内核的内存管理机制
本文旨在深入探讨Linux操作系统内核的内存管理机制,包括其设计理念、实现方式以及优化策略。通过详细分析Linux内核如何处理物理内存和虚拟内存,揭示了其在高效利用系统资源方面的卓越性能。文章还讨论了内存管理中的关键概念如分页、交换空间和内存映射等,并解释了这些机制如何协同工作以提供稳定可靠的内存服务。此外,本文也探讨了最新的Linux版本中引入的一些内存管理改进,以及它们对系统性能的影响。
|
28天前
|
存储 缓存 监控
|
2月前
|
存储 缓存 监控
Linux中内存和性能问题
【10月更文挑战第5天】
40 4
|
2月前
|
算法 Linux
Linux中内存问题
【10月更文挑战第6天】
58 2
|
25天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
31 0
|
2月前
|
存储 缓存 固态存储