Kernel那些事儿之内存管理(6) --- 衣带渐宽终不悔(下)
接着上篇写,继续介绍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) --- 衣带渐宽终不悔(下)相关推荐
- Kernel那些事儿之内存管理(5) --- 衣带渐宽终不悔(上)
Kernel中负责分配一个连续内存页块的子系统一般被称为zoned page frame allocator.前面讲了函数 buffered_rmqueue() 是如何从指定zone的buddy sy ...
- Kernel那些事儿之内存管理(8) --- Slab(中)
上篇讲了Slab中的数据结构,这篇该讲Slab中的操作了. 既然是内存管理,那操作无非就两点:allocate 和 free. 1. 申请一个object 在Slab中,申请一个object是通过函数 ...
- Kernel那些事儿之内存管理(7) --- Slab(上)
前面讲的buddy system算法,分配内存的最小单位是一个页面(例如 4K).这对于大的内存申请比较适用.可是实际生活中,Kernel经常需要分配小的内存空间,比如几十个字节,这个时候怎么办呢? ...
- Kernel那些事儿之内存管理(2) --- 百闻不如一见
上次介绍了物理内存管理中三位主要人物中的node 和zone.这两位是当官的,一个是县长,一个是里长,不敢不先介绍啊.接下来出场的就是我们的老百姓了 --- page frame. Page fram ...
- Spark 内存管理详解(下):内存管理
本文转自:Spark内存管理详解(下)--内存管理 本文最初由IBM developerWorks中国网站发表,其链接为Apache Spark内存管理详解 在这里,正文内容分为上下两篇来阐述,这是下 ...
- linux kernel内存管理之/proc/meminfo下参数介绍
一.前言 /proc/meminfo是了解Linux系统内存状态的主要接口,里面统计了当前系统各类内存的使用状况,需要注意的是:这是从内核的角度来统计.我们常用的free,vmstat等指令都是通过/ ...
- 启动期间的内存管理之初始化过程概述----Linux内存管理(九)
转载地址:https://blog.csdn.net/gatieme/article/details/52403148 日期 内核版本 架构 作者 GitHub CSDN 2016-09-01 Lin ...
- linux chown 将root改变所有者为admin,Linux用户管理 权限管理 内存管理 网络管理命令 (第四天)...
默认添加的用户会自动加入和用户名一样的组中 su 切换用户 查看当前登陆的用户: whoami id` 查看当前用户属于哪个组:groups groupadd 组名 添加组 groupdel 组名 删 ...
- Cocos2d-x内存管理研究二
http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04 上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动 ...
- Java内存管理:深入Java内存区域
Java内存管理:深入Java内存区域 本文引用自:深入理解Java虚拟机的第2章内容 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述 ...
最新文章
- DeepLearning——CNN
- HDOJ 1202 The calculation of GPA
- 数猪第一名!推荐给你的朋友们!猪只盘点挑战赛Baseline分享
- 论文浅尝 | 虚拟知识图谱:软件系统和应用案例综述
- 解析网上的XML文件
- bzoj1132:[POI2008]Tro
- 对Linux的cp命令的思考
- SQL数据库基础(六)
- 循环buffer的实现_Go语言源码阅读之bytes.Buffer
- CF991B Getting an A
- DNS原理及解析过程
- Linux复制文件到某路径并重命名
- matlab 稀疏矩阵求 特征值
- 数据分析之RFM——用户模型分析(附案例数据和代码)
- [Mysql] STR_TO_DATE函数
- spj查询零件、工程、供应商表
- 「TCG 规范解读」初识基础设施工作组
- 3y开发都不的不写单元测试,然后被被批了
- Excel应用技巧:单元格文字的拆分与合并
- Swift游戏实战-跑酷熊猫 07 平台的移动
热门文章
- 未来科学大奖 计算机,未来科学大奖
- android俄罗斯方块报告,Android 俄罗斯方块
- 走近棒球运动·中华职业棒球大联盟·MLB棒球创造营
- cad抠图 lisp_[原创]几个超级有用的裁剪用autocad--lisp程序
- 谷歌浏览器插件自动点击程序
- ROS实验笔记之——JCV-450无人机初入门
- 记录下一个带内购的iOS app的上架App Store历程
- 干货!量子技术入门、进阶、行业专家观点、最新资讯!1000篇好文帮你揭开量子技术神秘面纱!
- android 4.4 zygote 开机速度,一种安卓系统快速开机的方法及装置的制造方法
- Codeforces 128 A Statues【预处理+Bfs】