linux内核中的内存分配睡眠问题

嵌入式技术

1372人已加入

描述


在linux内核当中,分配内存是常有的事情,许多的内核数据结构都需要动态建立,这就需要分配内存,如果当下没有可用内存的话,内存分配函数是返回 NULL,还是睡眠等待呢?这其实是两种策略,答案也是非常简单,当当前的执行环境不允许睡眠的时候就不能睡眠,比如说中断,当前可以睡眠的时候就可以睡 眠等待,比如进程的系统调用或缺页异常处理中,基于以上不同的策略,内核专门为内存分配函数提供了flag参数,它们都是以GFP_打头的参数,可以参考 内核代码。最终都要进入__alloc_pages:
  • struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order, struct zonelist *zonelist)
  • {
  •          const int wait = gfp_mask & __GFP_WAIT;
  •          unsigned long min;
  •          struct zone **zones, *z;
  •          struct page *page;
  •          struct reclaim_state reclaim_state;
  •          struct task_struct *p = current;
  •          int i;
  •          int alloc_type;
  •          int do_retry;
  •          int can_try_harder;  //这个can_try_harder很重要,见下面初始化
  •          might_sleep_if(wait);
  •          can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;  
  •          zones = zonelist->zones;  /* the list of zones suitable for gfp_mask */
  •          if (unlikely(zones[0] == NULL)) {
  •                  return NULL;
  •          }
  •          alloc_type = zone_idx(zones[0]);
  •          for (i = 0; (z = zones[i]) != NULL; i++) {
  •                  min = z->pages_low + (1<protection[alloc_type];
  •                  if (z->free_pages < min)
  •                          continue;
  •                  page = buffered_rmqueue(z, order, gfp_mask);
  •                  if (page)
  •                          goto got_pg;
  •          }
  •          for (i = 0; (z = zones[i]) != NULL; i++)
  •                  wakeup_kswapd(z);  //这个wakeup并不能引起进程切换,稍后解释
  •          for (i = 0; (z = zones[i]) != NULL; i++) {  //常规分配,逐渐加大强度
  •                  min = z->pages_min;
  •                  if (gfp_mask & __GFP_HIGH)
  •                          min /= 2;
  •                  if (can_try_harder)  //can_try_harder影响着内存分配是否在本zone进行
  •                          min -= min / 4;
  •                  min += (1<protection[alloc_type];
  •                  if (z->free_pages < min)  //满足一定条件才进入下面的buffered_rmqueue实际分配,这对于保证空闲页面在一定范围内是很重要的。
  •                          continue;
  •                  page = buffered_rmqueue(z, order, gfp_mask);
  •                  if (page)
  •                          goto got_pg;
  •          }
  •          if ((p->flags & (PF_MEMALLOC | PF_MEMDIE)) && !in_interrupt()) {//特权分配,没有强度限制
  •                  for (i = 0; (z = zones[i]) != NULL; i++) {
  •                          page = buffered_rmqueue(z, order, gfp_mask);
  •                          if (page)
  •                                  goto got_pg;
  •                  }
  •                  goto nopage;
  •          }
  •          if (!wait)   //如果不能睡眠等待,比如在中断中,则直接退出此次分配
  •                  goto nopage;
  •  rebalance:           //平衡内存
  •          p->flags |= PF_MEMALLOC;
  •          reclaim_state.reclaimed_slab = 0;
  •          p->reclaim_state = &reclaim_state;
  •          try_to_free_pages(zones, gfp_mask, order); //这个函数中有显式的睡眠
  •          p->reclaim_state = NULL;
  •          p->flags &= ~PF_MEMALLOC;
  •          for (i = 0; (z = zones[i]) != NULL; i++) {
  •                  min = z->pages_min;
  •                  if (gfp_mask & __GFP_HIGH)
  •                          min /= 2;
  •                  if (can_try_harder)
  •                          min -= min / 4;
  •                  min += (1<protection[alloc_type];
  •                  if (z->free_pages < min)
  •                          continue;
  •                  page = buffered_rmqueue(z, order, gfp_mask);
  •                  if (page)
  •                          goto got_pg;
  •          }
  •          do_retry = 0;
  •          if (!(gfp_mask & __GFP_NORETRY)) {
  •                  if ((order <= 3) || (gfp_mask & __GFP_REPEAT))
  •                          do_retry = 1;
  •                  if (gfp_mask & __GFP_NOFAIL)
  •                          do_retry = 1;
  •          }
  •          if (do_retry) {
  •                  blk_congestion_wait(WRITE, HZ/50);
  •                  goto rebalance;
  •          }
  •  nopage:
  •          if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {
  •                  printk(KERN_WARNING "%s: page allocation failure."
  •                          " order:%d, mode:0x%x\n",
  •                          p->comm, order, gfp_mask);
  •                  dump_stack();
  •          }
  •          return NULL;
  •  got_pg:
  •          zone_statistics(zonelist, z);
  •          kernel_map_pages(page, 1 << order, 1);
  •          return page;
  • }
  • 上述函数中有wakeup_kswapd调 用,不管能否睡眠都回调用它,如果你认为它会导致进程切换会导致内存分配进程的睡眠,那么你就大错特错了,wakeup操作只是设置了 TF_NEED_RESCHED标志,虽然有了调度请求,可以调度点的验证却无法通过,在中断或原子上下文,进程的preempt标志为非0,而只有它为 0的时候才会通过调度点的验证实际发生进程切换,实际上在中断中会有很多wakeup的发生,很多进程都是在中断中被wakup的,真正会发生睡眠的是在 try_to_free_pages函数中,该函数中可能要调用blk_congestion_wait,而blk_congestion_wait则会 毫不犹豫地进入睡眠,因此页面分配标志中如果没有_GFP_WAIT标志,根本就无法进入try_to_free_pages,从而也不会睡眠,反之,一 旦设置了该标志便会有可能进入睡眠。我们来看看vmalloc函数会不会睡眠:
  • void *vmalloc(unsigned long size)
  • {
  •         return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);
  • }
  • #define GFP_KERNEL      (__GFP_WAIT | __GFP_IO | __GFP_FS)
  • 结果很显然,我就不说了,而kmalloc和get_free_pages是可以自己设置标志把握策略的,因此在中断中不要调用vmalloc来分配内存。

    打开APP阅读更多精彩内容
    声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
    • 相关推荐

    全部0条评论

    快来发表一下你的评论吧 !

    ×
    20
    完善资料,
    赚取积分