linux内存管理系统后期的内核对zonelist的简化

简介:
struct pglist_data {
    struct zone node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[GFP_ZONETYPES];
...
}
GFP_ZONETYPES是一个宏,在2.6.8的时候它如下定义:
#define GFP_ZONEMASK    0x07
/* #define GFP_ZONETYPES       (GFP_ZONEMASK + 1) */       
#define GFP_ZONETYPES  ((GFP_ZONEMASK + 1) / 2 + 1)
也就是说每一个numa的node拥有算了一下5条zone链表,就这还算比较少了。
可是在高一点版本的内核中,这个本来已经很低的zonelists数量变成了2(支持numa)或者1(不支持numa),也许很多人会说,通过GFP标志已经无法控制在哪个zone中以及之下分配内存了,然而看一下get_page_from_freelist这个函数多了很多参数,其中一个high_zoneidx所起到的作用就是原来那么多zonelist的作用,由此可见,高版本的内核丝毫没有降低功能,倒是少维护很多链表。以下首先说一下早期的内核中的多条zonelist链表的起因以及为何那么设计。
     在早期的内核中,每一个node拥有好几条zonelist,每一条代表一个起始zone开始的以及其下的所有的zone,但是这只是实际上的做法,而理论上这个zonelist的数量会更多,这些zonelist代表了一个优先级序列,说明了内存分配在zone中尝试的顺序,在实现中,linux是用位掩码来实现的。如果我们有三个zone,那么就应该用3位掩码中的每一个位来表示不同的zone掩码,设如下:
0x01表示dma
0x02表示normal
0x04表示highmem
这样3个位就有8*2...个顺序,分别是(o为空):
1.o;
2.dma;
3.normal->o;
4.normal->dma;
5.highmem->o;
6.highmem->o->dma;
7.highmem->normal->o;
8.highmem->normal->dma;
9-16.前面8个方向反过来。
17-xx.两两反向,保持一个正向...
可是为何内核仅仅保留了3个左右的顺序呢?首先是三个原则在起作用,第一个就是一致的顺序管理起来更高效而且更不容易冲突,类似单行道的作用,虽然灵活性不够!第二个原则就是内核的内存管理是一个管理机构,但凡内核的管理机构,采取的原则都是平等至上的!第三个原则就是连续性管理原则,按照一定的顺序依次编排,这个顺序一般都是从易到难,从受影响最小的地方开始,不到万不得已不会惊动其它。有了这三个原则的话,首先我们看一下为何没有反向顺序的zonelist,如果有的话就增加了管理负担,因为自动内存置换程序(kswapd)和手动置换程序(try_to_free...)就都要在两个方向进行扫描和管理,由于存在多条路径扫描和分配,这就很不容易了解到各个zone的内存使用情况,从而不知道要将扫描主力放在哪个zone。那么为何不存在跳跃的zonelist,比如跳过normal而仅仅在highmem和dma中分配,这是因为之所以存在一个zonelist而不是一个zone是因为在该zone中分配失败之后有一个后备的可分配zone,由于分配使用的zonelist使用的顺序必然是从容易分配的zone到难分配的zone排列的,那么dma中分配内存的代价会比normal中分配的代价更大。在linux操作系统的内存管理中,主要就是管理内核内存(包括驱动的内存)和进程内存,在大内存的机器上,大量的物理内存无法一一映射进内核地址空间的前HIGH兆,因此实际上对于大多数内核内存管理来讲,这些大量的内存对之意义不大,因此它们更多的被用于进程内存,进程内存是可以随意映射进自己的地址空间的,不要求映射的方式--比如线性映射,也不要求连续性。因此进程内存优先从highmem这个zone中分配,如果不行的话,则最好先在normal中看看,然后再往dma走。对于内核内存管理而言,在系统初始化的时候,一些核心的数据和代码已经映射完毕了,需要内存的大户都是驱动或者模块或者就是诸如进程管理和网络协议栈的动态部分,比如新申请了一个task_t结构体,或者新分配了一个sk_buff结构体等等,这些内存一般从normal区开始分配,这是因为使用normal区的内存可以线性映射进系统内核,操作起来更高效。由于没有谁拥有特权,所以大家都遵守一样的原则,从它所可以触及的最高zone(gfp_flags决定)依次尝试到最低的zone,如果不行则手工调用try_to_free...从该zonelist的开始zone开始释放一些内存,这样做真的是很简单很高效!内核之所以不通知用户进程和驱动就直接在某个zone为之分配了内存是因为zone是操作系统内存管理模块最低层的概念,进程或者驱动甚至都不应该知道有zone的存在,因此内核才可以采取遵循以上三个原则的策略将物理内存分为若干个zone,然后按照从高到低的顺序依次尝试分配内存,直到成功。
     后期的内核将上述机制的实现改变了,每一个pglist_data在没有numa的情况下只有一条zonelist,按照上述的三个原则,其实没有必要搞那么多链表的,由于不存在跳跃,不存在逆向,因此只需要一个链表和一个上限值即可,每次分配内存的时候从这个上限值代表的zone往下开始尝试即可,这样就可以省去一些空间和管理费用,将事先设置好N条zonelist转为直到实际上分配内存的时候再确定上限:
struct page *__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
            struct zonelist *zonelist, ...)
{ //参数中的zonelist其实就是那唯一的一条zonelist(没有numa的情况下)
    enum zone_type high_zoneidx = gfp_zone(gfp_mask);
...
}

如此一来,high_zoneidx这个变量就可以作为一个限制值,在get_page_from_freelist的时候设置一个阀值,虽然__alloc_pages_nodemask函数增加了一些分量,但是时间一点也不损耗多少,取消了几个链表,运行时增加了几个指令周期,这也许(很可能)是值得的。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271159


目录
打赏
0
0
0
0
344
分享
相关文章
|
7天前
|
Linux系统之whereis命令的基本使用
Linux系统之whereis命令的基本使用
50 23
Linux系统之whereis命令的基本使用
|
1月前
|
linux 手动释放内存
在 Linux 系统中,内存管理通常自动处理,但业务繁忙时缓存占用过多可能导致内存不足,影响性能。此时可在业务闲时手动释放内存。
110 17
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
79 48
|
2月前
|
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
231 78
Linux系统查看操作系统版本信息、CPU信息、模块信息
在Linux系统中,常用命令可帮助用户查看操作系统版本、CPU信息和模块信息
109 23
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
118 20
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
97 15
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
121 13
|
2月前
|
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
66 0