接着上篇写,继续介绍zone allocator。上一篇介绍了周边,现在来看看它的全貌 --- 函数__alloc_pages()。

Kernel源代码里是这样注释函数__alloc_pages()的。其重要地位可见一斑。

1451 /*
1452  * This is the 'heart' of the zoned buddy allocator.
1453  */

__alloc_pages()的工作模式很清晰:利用函数get_page_from_freelist()多次遍历zonelist中所有的zones。遍历时把关条件会逐渐放宽,其中还可能会启动内存回收等机制。

1454 struct page * fastcall
1455 __alloc_pages(gfp_t gfp_mask, unsigned int order,
1456         struct zonelist *zonelist)
1457 {
1458     const gfp_t wait = gfp_mask & __GFP_WAIT;
1459     struct zone **z;
1460     struct page *page;
1461     struct reclaim_state reclaim_state;
1462     struct task_struct *p = current;
1463     int do_retry;
1464     int alloc_flags;
1465     int did_some_progress;
1466
1467     might_sleep_if(wait);
1468
1469     if (should_fail_alloc_page(gfp_mask, order))
1470         return NULL;
1471
1472 restart:
1473     z = zonelist->zones;  /* the list of zones suitable for gfp_mask */
1474
1475     if (unlikely(*z == NULL)) {
1476         /*
1477          * Happens if we have an empty zonelist as a result of
1478          * GFP_THISNODE being used on a memoryless node
1479          */
1480         return NULL;
1481     }
1482
1483     page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,
1484                 zonelist, ALLOC_WMARK_LOW|ALLOC_CPUSET);
1485     if (page)
1486         goto got_pg;

第一次遍历,把关时要求相对较高:watermark选择了ALLOC_WMARK_LOW。这样可以尽量保护各个zone预留的空闲内存。

如果第一次遍历没能申请到内存,说明系统中空闲内存不多了。放宽一下把关条件,进行第二次遍历。

1499     for (z = zonelist->zones; *z; z++)
1500         wakeup_kswapd(*z, order);
15011512     alloc_flags = ALLOC_WMARK_MIN;
1513     if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait)
1514         alloc_flags |= ALLOC_HARDER;
1515     if (gfp_mask & __GFP_HIGH)
1516         alloc_flags |= ALLOC_HIGH;
1517     if (wait)
1518         alloc_flags |= ALLOC_CPUSET;
15191528     page = get_page_from_freelist(gfp_mask, order, zonelist, alloc_flags);
1529     if (page)
1530         goto got_pg;

在进行第二次遍历之前,Kernel做了两件事:

1) 异步启动内存回收机制。内存回收机制是个大的topic,这里我们只需知道,该机制会释放出一些内存页面到buddy system中。

2) 调整分配标志 alloc_flags,放宽把关条件:

  • 选择ALLOC_WMARK_MIN作为watermark。ALLOC_WMARK_MIN的watermark值(pages_min)比ALLOC_WMARK_LOW的watermark值(pages_low)数值小,选用更小的watermark可以更多的动用预留的空闲内存。

  • 如果当前进程是实时优先级(real-time)进程且不是在中断上下文,或是这次内存申请不能被中断(__GFP_WAIT没有置位),则设置标志ALLOC_HARDER。

  • 如果gfp_mask中__GFP_HIGH置位,则设置标志ALLOC_HIGH。

在上一篇博文里讲到,如果设置了ALLOC_HIGH 或 ALLOC_HARDER,zone_watermark_ok()中使用的阈值会进一步减少,这也就意味着把关条件放松,分配会更加aggressive。

这里有一点需要注意:GFP_ATOMIC的内存申请,会同时设置ALLOC_HARDER和ALLOC_HIGH。因为GFP_ATOMIC定义如下:

 60 #define GFP_ATOMIC  (__GFP_HIGH)

如果还是没能申请到内存,说明内存非常吃紧。此时,对于下面这种特殊情况,Kernel会特殊对待一下。

1534 rebalance:
1535     if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))
1536             && !in_interrupt()) {
1537         if (!(gfp_mask & __GFP_NOMEMALLOC)) {
1538 nofail_alloc:
1539             /* go through the zonelist yet again, ignoring mins */
1540             page = get_page_from_freelist(gfp_mask, order,
1541                 zonelist, ALLOC_NO_WATERMARKS);
1542             if (page)
1543                 goto got_pg;
1544             if (gfp_mask & __GFP_NOFAIL) {
1545                 congestion_wait(WRITE, HZ/50);
1546                 goto nofail_alloc;
1547             }
1548         }
1549         goto nopage;
1550     }

如果当前环境不是中断上下文,并且当前进程设置了PF_MEMALLOC或TIF_MEMDIE,则会进行特殊对待。

  • 如果是__GFP_NOMEMALLOC的请求,表示禁止使用为紧急情况预留的内存,这种情况下Kernel再无它法,只能返回NULL。

  • 否则,Kernel会拿出家底,放手一搏,进行第三次遍历。这次遍历,Kernel使用了ALLOC_NO_WATERMARKS,这就意味着跳过把关函数zone_watermark_ok(),完全忽略watermark和lowmem_reserve的限制。这也是唯一能够动用全部的预留内存的地方。如果第三次遍历还是失败,苍天啊:

    • 如果不是__GFP_NOFAIL的请求,则只能返回NULL。

    • 如果是__GFP_NOFAIL的请求,想死又不让死,只能死抗着了。Kernel会进入一个死循环,不过每次循环之前会先等块设备层的写拥塞结束。

话说PF_MEMALLOC和TIF_MEMDIE到底表示啥东东呢?简单地讲,它们的出现一般表示当前的上下文是在回收内存。回收内存本身也是需要内存的,你先给我一点点空闲内存,我将回报给你更多的空闲内存。给我一滴水,我将还你一片海。所以它才有资格动用全部的预留内存。

如果不是上面这种特殊情况,Kernel还有一些手段可用,不过这些手段需要当前进程能够进入睡眠状态。

1552     /* Atomic allocations - we can't balance anything */
1553     if (!wait)
1554         goto nopage;
1555
1556     cond_resched();

如果__GFP_WAIT没有置位,说明这次请求不允许被中断,那Kernel的那些手段就不能用了。此时只能返回NULL。

在拿出这些手段之前,Kernel先看看有没有其他人需要CPU。毕竟咱不能太自私,占着CPU太久。

1558     /* We now go into synchronous reclaim */
1559     cpuset_memory_pressure_bump();
1560     p->flags |= PF_MEMALLOC;
1561     reclaim_state.reclaimed_slab = 0;
1562     p->reclaim_state = &reclaim_state;
1563
1564     did_some_progress = try_to_free_pages(zonelist->zones, order, gfp_mask);
1565
1566     p->reclaim_state = NULL;
1567     p->flags &= ~PF_MEMALLOC;
1568
1569     cond_resched();

手段一:利用函数try_to_free_pages() 进行同步的内存回收。这个函数很耗时,而且可能会睡眠。

注意在调用函数try_to_free_pages()之前,Kernel设置了PF_MEMALLOC。一是表示接下来要进行内存回收操作了;二是防止函数try_to_free_pages()被递归调用,因为PF_MEMALLOC的设置会让Kernel特殊对待。

1571     if (order != 0)
1572         drain_all_local_pages();

手段二:如果申请的是多个内存页,则把未雨绸缪准备的per-cpu page frame cache中的内存页面还给buddy system。哎,不要怪Kernel抠门啊,实在是资源太紧张。

如果手段一成功释放了一些内存页面,则再来一次遍历(第三次)。

1574     if (likely(did_some_progress)) {
1575         page = get_page_from_freelist(gfp_mask, order,
1576                         zonelist, alloc_flags);
1577         if (page)
1578             goto got_pg;

这次遍历使用了与第二次遍历相同的条件。

如果还是没能申请到内存,Kernel就要做个决定了,是放弃?是坚持?

1616     do_retry = 0;
1617     if (!(gfp_mask & __GFP_NORETRY)) {
1618         if ((order <= PAGE_ALLOC_COSTLY_ORDER) ||
1619                         (gfp_mask & __GFP_REPEAT))
1620             do_retry = 1;
1621         if (gfp_mask & __GFP_NOFAIL)
1622             do_retry = 1;
1623     }
1624     if (do_retry) {
1625         congestion_wait(WRITE, HZ/50);
1626         goto rebalance;
1627     }

1)设置了__GFP_NORETRY,放弃,返回NULL。

2)没有设置__GFP_NORETRY,并且要分配的内存页块小于等于8页,或是设置了__GFP_REPEAT或__GFP_NOFAIL, 坚持!

选择坚持的方法就是,先等一下块设备层的写拥塞结束,然后从第二次遍历结束的地方重新开始。

如果手段一没能释放出任何页面,Kernel遇到big trouble了。这时会拿出手段三:大义灭亲,选择一个进程,杀掉其人,霸占其内存资源。

在杀人之前,先看两个标志:__GFP_FS和__GFP_NORETRY。如果__GFP_FS没有置位(不允许执行依赖于文件系统的操作),或是__GFP_NORETRY置位了(不允许重试),Kernel会立即放下屠刀,然后去思考前面“放弃还是坚持”的问题。

否则,Kernel就真要杀人了!

1579     } else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
1580         if (!try_set_zone_oom(zonelist)) {
1581             schedule_timeout_uninterruptible(1);
1582             goto restart;
1583         }
1584
1585         /*
1586          * Go through the zonelist yet one more time, keep
1587          * very high watermark here, this is only to catch
1588          * a parallel oom killing, we must fail if we're still
1589          * under heavy pressure.
1590          */
1591         page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,
1592                 zonelist, ALLOC_WMARK_HIGH|ALLOC_CPUSET);
1593         if (page) {
1594             clear_zonelist_oom(zonelist);
1595             goto got_pg;
1596         }
1597
1598         /* The OOM killer will not help higher order allocs so fail */
1599         if (order > PAGE_ALLOC_COSTLY_ORDER) {
1600             clear_zonelist_oom(zonelist);
1601             goto nopage;
1602         }
1603
1604         out_of_memory(zonelist, gfp_mask, order);
1605         clear_zonelist_oom(zonelist);
1606         goto restart;
1607     }

在真的动刀杀人之前,Kernel再进行一次遍历(第三次)。不过这次遍历,Kernel选择了很严格的把关条件: ALLOC_WMARK_HIGH。所以这次遍历失败的可能性很大。

有人会说,这不是假仁慈吗?内存已经这么紧张了,你还定这么高的把关条件,注定要失败啊。要杀就杀,直接来吧。。。

其实这是Kernel的真仁慈。如果一个进程已经被其他人杀掉了,那么这次遍历就会成功,这样就能让一个无辜的生命幸免遇难。

Kernel还有另一个仁慈的表现。如果发现申请的内存页面大于8页,则直接返回NULL。因为这时即使杀掉一个进程,也不大可能会满足要求,何必要多牺牲一个生命呢。

好了,到了这个时候,命运是逃不过的了。Kernel召唤出杀手OOM,调用函数out_of_memory(),来杀掉一个进程,释放出其内存资源。然后回到第一次遍历之前,重新开始。

以上,就是函数__alloc_pages()的全貌。一次次的遍历,屡败屡战,衣带渐宽终不悔,为伊消得人憔悴。

转载于:https://blog.51cto.com/richardguo/1670665

Kernel那些事儿之内存管理(6) --- 衣带渐宽终不悔(下)相关推荐

  1. Kernel那些事儿之内存管理(5) --- 衣带渐宽终不悔(上)

    Kernel中负责分配一个连续内存页块的子系统一般被称为zoned page frame allocator.前面讲了函数 buffered_rmqueue() 是如何从指定zone的buddy sy ...

  2. Kernel那些事儿之内存管理(8) --- Slab(中)

    上篇讲了Slab中的数据结构,这篇该讲Slab中的操作了. 既然是内存管理,那操作无非就两点:allocate 和 free. 1. 申请一个object 在Slab中,申请一个object是通过函数 ...

  3. Kernel那些事儿之内存管理(7) --- Slab(上)

    前面讲的buddy system算法,分配内存的最小单位是一个页面(例如 4K).这对于大的内存申请比较适用.可是实际生活中,Kernel经常需要分配小的内存空间,比如几十个字节,这个时候怎么办呢? ...

  4. Kernel那些事儿之内存管理(2) --- 百闻不如一见

    上次介绍了物理内存管理中三位主要人物中的node 和zone.这两位是当官的,一个是县长,一个是里长,不敢不先介绍啊.接下来出场的就是我们的老百姓了 --- page frame. Page fram ...

  5. Spark 内存管理详解(下):内存管理

    本文转自:Spark内存管理详解(下)--内存管理 本文最初由IBM developerWorks中国网站发表,其链接为Apache Spark内存管理详解 在这里,正文内容分为上下两篇来阐述,这是下 ...

  6. linux kernel内存管理之/proc/meminfo下参数介绍

    一.前言 /proc/meminfo是了解Linux系统内存状态的主要接口,里面统计了当前系统各类内存的使用状况,需要注意的是:这是从内核的角度来统计.我们常用的free,vmstat等指令都是通过/ ...

  7. 启动期间的内存管理之初始化过程概述----Linux内存管理(九)

    转载地址:https://blog.csdn.net/gatieme/article/details/52403148 日期 内核版本 架构 作者 GitHub CSDN 2016-09-01 Lin ...

  8. linux chown 将root改变所有者为admin,Linux用户管理 权限管理 内存管理 网络管理命令 (第四天)...

    默认添加的用户会自动加入和用户名一样的组中 su 切换用户 查看当前登陆的用户: whoami id` 查看当前用户属于哪个组:groups groupadd 组名 添加组 groupdel 组名 删 ...

  9. Cocos2d-x内存管理研究二

    http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04   上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动 ...

  10. Java内存管理:深入Java内存区域

    Java内存管理:深入Java内存区域 本文引用自:深入理解Java虚拟机的第2章内容 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述 ...

最新文章

  1. DeepLearning——CNN
  2. HDOJ 1202 The calculation of GPA
  3. 数猪第一名!推荐给你的朋友们!猪只盘点挑战赛Baseline分享
  4. 论文浅尝 | 虚拟知识图谱:软件系统和应用案例综述
  5. 解析网上的XML文件
  6. bzoj1132:[POI2008]Tro
  7. 对Linux的cp命令的思考
  8. SQL数据库基础(六)
  9. 循环buffer的实现_Go语言源码阅读之bytes.Buffer
  10. CF991B Getting an A
  11. DNS原理及解析过程
  12. Linux复制文件到某路径并重命名
  13. matlab 稀疏矩阵求 特征值
  14. 数据分析之RFM——用户模型分析(附案例数据和代码)
  15. [Mysql] STR_TO_DATE函数
  16. spj查询零件、工程、供应商表
  17. 「TCG 规范解读」初识基础设施工作组
  18. 3y开发都不的不写单元测试,然后被被批了
  19. Excel应用技巧:单元格文字的拆分与合并
  20. Swift游戏实战-跑酷熊猫 07 平台的移动

热门文章

  1. 未来科学大奖 计算机,未来科学大奖
  2. android俄罗斯方块报告,Android 俄罗斯方块
  3. 走近棒球运动·中华职业棒球大联盟·MLB棒球创造营
  4. cad抠图 lisp_[原创]几个超级有用的裁剪用autocad--lisp程序
  5. 谷歌浏览器插件自动点击程序
  6. ROS实验笔记之——JCV-450无人机初入门
  7. 记录下一个带内购的iOS app的上架App Store历程
  8. 干货!量子技术入门、进阶、行业专家观点、最新资讯!1000篇好文帮你揭开量子技术神秘面纱!
  9. android 4.4 zygote 开机速度,一种安卓系统快速开机的方法及装置的制造方法
  10. Codeforces 128 A Statues【预处理+Bfs】