Chinaunix

标题: linux内存管理讨论——欢迎大家围观! [打印本页]

作者: 瀚海书香    时间: 2012-03-28 16:06
标题: linux内存管理讨论——欢迎大家围观!
内存管理是一个系统最核心的部分,内存管理实现的好坏,直接影响的了系统的优劣。

本期讨论话题: linux内存管理的实现

大家可以分享自己对linux内存管理的理解和建议
也可以分享linux内存管理的代码分析
也可以分享与linux内存相关的代码

活动时间:2012.3.28-2012.4.15

活动有奖:我们为大家准备了ChinaUnix社区独家定制电脑背包(共四个)


作者: 瀚海书香    时间: 2012-03-28 16:07
回复 1# 瀚海书香
我先抛个砖了,希望能引玉
首先关于虚拟地址、物理地址、逻辑地址的概念,可以参考独孤九贱的这个帖子 http://bbs.chinaunix.net/thread-2083672-1-1.html,写的非常好。
这里先贴一下当时看内存页表的一点笔记。
(1)每个进程只有一个页表,0-3G映射的是用户空间的内存,也就是应用程序可以直接操作的,3-4G映射的是内核空间,这部分页表所有进程共享。页表保存在内核态,由内核帮助进程来维护。
(2)内核给进程分配物理内存的时候,通过按需分配的方法。也就说进程申请一个页表,这个时候进程获得的只 是一个地址空间,而这个地址空间并没有真正的物理页面对应,只有当进程试图访问这个页面的时候,才会给进程 分配真实的物理页面。这个过程通过缺页异常来实现。
(3)当发生缺页异常的时候,内核根据缺页地址(cr2寄存器中)查看页表项的内容,如果为空,表明还没有映射 ,内核根据虚拟地址判断出它所在的vm_area_struct,这个结构体描述了进程地址空间中相同属性的一块区域,比 如映射到一个文件。
(4)对于已经交换到交换分区的步伐,这个时候页表项非空,而是保存了它在交换分区中的地址信息,根据这个 信息可以从交换分区中取出相应的物理页面。

   
作者: 无风之谷    时间: 2012-03-28 16:10
强烈支持书香版主的活动~!
作者: dooros    时间: 2012-03-28 16:22
不错的活动,支持!
作者: 无风之谷    时间: 2012-03-28 16:31
回复 4# dooros


    dooros兄,一定要多多分享嘛
作者: waker    时间: 2012-03-28 16:34
围观求包
作者: lujian19861986    时间: 2012-03-28 16:41
支持书香版主的活动~!
作者: 瀚海书香    时间: 2012-03-28 16:46
回复 4# dooros
多谢支持,期待dooros 分析你对内存管理的理解

   
作者: 瀚海书香    时间: 2012-03-28 16:47
回复 7# lujian19861986
多谢支持!

   
作者: 木流稻溪    时间: 2012-03-28 17:12
一直对这一块不是很清楚,希望牛人能多多发言,版主好人啊。
作者: dooros    时间: 2012-03-28 17:15
当年研究操作系统原理的时候研究了下,现在好多年了……
作者: 瀚海书香    时间: 2012-03-28 17:53
回复 1# 瀚海书香
这里说一个关于内存的实际的应用,用户态进程的虚拟地址如何转换成物理地址?

区分一个进程,我们都知道最简单就是进程的pid。我们就从(pid,virtualaddress)来看看如何将一个进程的虚拟地址转换为物理地址phyaddress。
首先根据pid我们可以得到这个进程的task_struct,进而通过task_struct得到mm,通过mm得到pgd。
好了,现在我们有pgd和virtualaddress.
通过pgd和virtualaddress我们可以得到页表pte.
有了pte和virtualaddress,我们就可以计算物理地址了
phyaddress=(pte_val(pte)&PAGE_MASK)|(virtualladdress&~PAGE_MASK)
物理地址既然出来了,访问这个地址的值就比较简单了,只需要将物理地址转换为内核线性地址就行。
*phyaddress=*((char *)phyaddress+PAGE_OFFSET)

   
作者: jeanhz    时间: 2012-03-28 18:21
如果把内存管理的范围扩大点,如何访问和管理设备的地址空间呢, 这个领域有没有深入学习的同学来分享知识? 比如说,我现在开发了一块基于PowerPC  的平台, 其物理地址已经被硬件固化。 如何在Linux 系统里,使用什么机制来访问这些物理地址,
Unit computer memory configuration
Resource                             Address range                      Size        Port width             Chip Select
Main memory                      0x0000_0000 – 0x1FFF_FFFF        512 MB           64             M1_MCS0
Application FLASH (NAND)        0x4000_0000 – 0x4000_1FFF        8KB           8             HP_LCS2#
PCI Express Memory Window        0x5000_0000 – 0x5FFF_FFFF        256 MB           n/a             n/a
SRIO Window                      0x6000_0000 – 0x9FFF_FFFF        1 GB           n/a             n/a
Memory mapped registers        0xC000_0000 – 0xC000_000F        16 B           16             HP_LCS3#
CCSRBAR                                    0xE000_0000 – 0xE00F_FFFF        1MB               
Redundant boot FLASH        0xF000_0000 – 0xF7FF_FFFF        128 MB           16             Flash1_CS#
Boot FLASH                      0xF800_0000 – 0xFFFF_FFFF        128 MB           16             Flash0_CS#

作者: __general__    时间: 2012-03-28 18:34
先顶下!
作者: amarant    时间: 2012-03-28 19:43
内存管理一节我也非常感兴趣。瀚海书香版主说的都是x86架构上的内存管理,各种的架构对内存的管理很大的不一样。像mips的内存管理就和X86有很大的不一样。内存管理这一章不深入理解下架构上的实现,总会有一些说不清道不明的感觉。
作者: dooros    时间: 2012-03-28 21:15
本帖最后由 dooros 于 2012-03-28 21:16 编辑

当年看汤子瀛等主编的《计算机操作系统(第二版)》中笔记下的一些疑问,关于内存的,原理性的,不涉及代码实现,上次问了,这次继续。

一、Page 116 4.3.2 地址变换机构
2.具有快表的地址变换机构
快表用以存放当前访问的那些页表项。
问题:在单处理机系统中,是不是每当进程阻塞、挂起、结束都要清除一次快表,即:快表只保留当前进程的页表项?
当前访问的理解?比如现在进程使用50个页,则快表中只保存50个页的信息,一段时间后,进程使用20个页,则快表立即清除其余30个页的信息,只保留20个页的信息。这样理解对否?
如果在多处理机系统中,进程并发执行,则快表同时保存多个进程的页表项(页号与物理块号的对应信息),如果快表中多个进程的页号相同,岂不乱套?是不是多处理机系统中有多个快表?
二、Page 114 4.3.1 页面与页表
问题:在分页存储管理中是所有的进程的页面大小都相等,还是不同进程有不同的页面大小?
分页存储管理中是不是把所有内存都划分为大小相等的物理块?页表是不是也存储在这样的物理块中?还是页表独立分配空间?
三、Page 119 4.3.3 两级和多级页表
2 多级页表
倒数第二段最后一句:
这样的结果显然是不能令人接受的,因此必须采用多级页表,将外层页表再进行分页,也是将各分页离散地装入到不相邻的物理块中,再利用第二级的外层页表来映射它们之间的关系。
问题:这里是不是只能使用将部分页表调入内存这种方法?
四、Page 159 5.3.4缓冲池
第一段的最后一句话:为了提高缓冲区的利用率,目前广泛流行公用缓冲池;在池中设置了多个可供若干个进程共享的缓冲区。
问题:是不是整个缓冲池有一个空缓冲队列emq,而对应每个进程均需要一个输入队列inq和一个输出队列outq?如果整个缓冲区仅有一个输入队列inq 和一个输出队列outq,那么,在进程共享使用时,岂不是无法确定摘取的是否是自己需要的缓冲区?另外,队列的数据结构也不允许随机摘取。
作者: renxiao2003    时间: 2012-03-28 21:18
这个要参加啊。
作者: g__gle    时间: 2012-03-28 21:24
我讲一讲自己在学习过程中遇到的不解之处,及各个概念的高度总结。好吧,其实是前几天发现包破了。。。。

1.内核是否有独立的地址空间。
X86上的Linux内核没有独立的地址空间。作为对比,DEC-11上的Unix V6内核有独立的地址空间,一旦进入内核态,页表自动切换。这样操纵用户态时就可跳出三界外。现在几乎没有操作系统这样做。根据某部FreeBSD著作所讲,内核态常做的一件事就是从、向用户空间搬、传数据。内核与用户态进程不在一个地址空间,则需要额外的指令在两个地址空间里传数据。

但这确实是一种设计,有其历史渊源。



2.内核态内存的映射。
X86上Linux内核之内核态虚拟地址,大部分固定地与低端物理内存一一映射。这样做的理由,一来是想更容易地访问物理内存,二来方便使用动态申请到的内存。DEC-11上的Unix V6内核就算有自己独立的地址空间,其大部分映射也是与物理内存一一对应的---在内核眼中,大部分虚拟地址与物理地址都是一样。

Linux上则仅仅有个差值。



3.896M与高端内存
X86上Linux内核与进程共用地址空间,另外,内核所有的地址空间被设计成与内存固定映射。结果是:内核拥有的地址空间很小(因为用户态占了大部分);能灵活使用的虚拟地址不多(因为大部分要与物理内存做一一对应的关联)。于是,内核能使用物理内存的大小被其狭小的虚拟地址空间所限制了。即便有再多的物理内存,内核已没有多余的虚拟地址与之映射,从而无法访问。

所以,896M用于与物理内存建立一一对应关系,剩余的虚拟地址空间,分时映射没能被896M覆盖到的物理内存,这部分物理内存就是高端内存。内核自己申请动态内存时,通常只在这896M以内申请,免得为了使用还得建立映射关系。其内部大量cache都在这一区域内,所以这块内存比较宝贵。用户虚拟地址空间可以随意设置,给用户态分配内存时,优先从高端内存取(如果有的话)。


4.映射与使用
内核映射了低端896M物理内存,大部分动态内存都从这里获得。但这并不是说这些内存就属于内核了,用户态不能用了。动态内存的使用要由mm子系统来协调,基本原理就是:分出去的内存不能再被分出去;收回来的内存可以被再分出去。动态内存的归属,最终由mm子系统来调度,申请到了再使用,这样才能保证秩序。分给用户态了,先建立映射,用户程序就可安心用了,即使内核可以直接访问这块内存,只要没有BUG,它都是很安全的。分给内核态了,直接使用即可--映射早已建立。

如果896M内的物理内存分给用户态,内核自然不能使用。而且与之关联的虚拟地址段也不能另作他用。可惜了。


5.虚拟地址空间
虚拟地址空间也是资源。于用户态而言,典型的例子是在扩展地址空间之时:需要找到一块没用的地址空间。必需得记录哪些地址空间被用了,哪些没有被用。分配虚拟地址空间与分配物理内存其实是一样的道理,只不过物理内存是全局的,需要用更精妙的算法来分配。Linux虚拟地址空间似使用first fit----沿途看看有没有合适的地址段,第一个找到的就是。其实Unix V6就是这么分配物理内存的。

有数据结构描述用户态地址空间段,内核怎么管理自己的虚拟地址空间呢?是什么决定其动态申请内存时获得内存的位置?由于大部分虚拟地址空间都与物理内存一一对应,两者完全可以合二为一:管理虚拟内存段就是管理物理内存。申请的虚拟地址的情况取决于申请到物理内存的情况。

高端分时复用的那一块虚拟地址空间,面临的需求和解决方案,与用户态一样。


6.Buddy System
管理物理内存最基本的要求:判断一块内存单位是否被使。只记录这一个信息的话,一个bit足矣。于是可以用一个bitmap描述当前物理内存的使用情况。实际上,一块内存单位的属性远不止是否被使用,其复杂程序需要用一个结构体来描述。下一步是考虑分配内存的策略。Buddy System就是策略的一种。其原理,至少从表面上看起来,并不复杂。它主要为了解决外部碎片:系统中有足够量的内存满足需要,但是这些内存散乱在各地,无法作为整体来使用。


7. Slab
现代操作系统通常以页作为物理内存单位,这是分配与回收的最小单位。分配一页给一页你,分配几byte还是得给一页你。如果分出去的内存大部分没有被使用,那就太可惜了。一个频繁分配小内存的代码段,可能会先分配几页,然后自己内部管理这些内存,通过某种方法一点点满足小内存分配的需要。slab就是提取出来,专门做这种事的代码。它并不在乎底层用的是何种内存分配方式,它只在乎地层提供的分配与释放的接口,然后向上层提供分配小块内存的接口。之前向底层要的物理内快完了,它会负责再要一点;之前从底层拿的内存多了用不完,它会择时还回去。它默默地做着这些脏活,留给上层一个功能简单明了的接口。


8.mmap与虚拟地址空间
以前看APUE,以为mmap是个trick十足的系统调用。后来才明白,如果不理解内核的cache系统,mmap的作用很难理解。在内核里,带mmap字样的函数更让人觉得trick:它们用来分配用户态的虚拟地址空间。这似乎偏离了mmap的初始意图。

初始的时候,虚拟地址空间的样子由可执行文件,比如ELF,来决定。如果地址空间与文件映射,就叫有名映射(好吧,好像没有这种叫法);如果纯粹扩展一段地址空间,比如从用户态上去理解malloc的语义,就叫匿名映射,虽然看不出来整个过程哪里跟映射有关了。于是,用户态的地址空间就这么一点一点被有名映射,无名映射筑起来了。



作者: Godbach    时间: 2012-03-28 21:42
回复 1# 瀚海书香
好活动,强烈支持。

   
作者: 塑料袋    时间: 2012-03-28 23:39
本帖最后由 塑料袋 于 2012-03-28 23:48 编辑

占位,计划谈page compaction及migration的提出目的,实现手段。

目的:
1) NUMA结构,进程迁移,则进程的内存页面若也迁移至目标CPU,则对这个进程来说,访存最快。
2) 内存热插拔
3) huge page
4) buddy造成的的内存碎片化:buddy有求必应,一律从满足要求大小的,最小的连续内存片段分配,但是无视分配的性质。考虑场景如下,1234共4个页:
   a) slab临时性的分走[1],随时可能shrink cache收回,此时剩余[2],[34]
     b) 某driver长期性的分走[2],这页不可能收回,此时剩余[34]
     c) slab临时性的分走[3],随时可能shrink cache收回,此时剩余[4]
     d) 某driver长期性的分走[4],这页不可能收回,此时无剩余
   e) slab释放了[1],此时剩余[1]
     f) slab释放了[3],此时剩余[1],[3]
由此导致碎片化。但若将内存按照分配的性质分类,[12]归于movable,临时性的分配从这里分;[34]归于unmovable,长期性占用从这里分,则不会产生这种情况。



将内存按照分配的性质来来划分成几部分,需要一个划分的依据。以前的版本中分了三部分:MOVABLE,RECLAIMABLE ,UNMOVABLE 。movable最可能被随时释放,unmovable最不可能被释放。buddy中的每个order,都被分成这3部分。
这三部分中,那一部分到底应涵盖多少内存,不应设置成死的,而应根据分配的情况来动态调整。即:
unmovable不够,则先自reclaimable借,后自movable借。
movable不够,则先自reclaimable借,后自unmovable借。
原则为尽量少改动内存的易释放性,从最接近自己的那类中来借。

当本order管理的内存中借也借不着时,那只好在斥逐于更高的order,整个拿来一块,将这一块分成小块,除了分配出去的那小块外,其他的尽量不更改易释放性。


MOVABLE中的内存是重头戏,除了只分配给那些临时性的请求外,对这些内存,还可以进行compaction及migration。
compaction发生于内存不足时,基本上是个和page reclaim并列的逻辑。自头至尾释放zone内的页,这些页都在lru中,且页与页之间可能有缝隙,即碎片化;自尾至头分配zone内的页,这些页都属于movable,且分配的原则为见缝插针,中间不产生缝隙。将被释放页的内容复制到新分配的页,同时依据被释放页的反向映射,修改所有引用被释放页的pte。

占位待续
作者: 瀚海书香    时间: 2012-03-29 07:42
回复 18# g__gle
高手都习惯潜水啊,这次终于出来了

   
作者: 瀚海书香    时间: 2012-03-29 07:45
回复 15# amarant
内存管理一节我也非常感兴趣。瀚海书香版主说的都是x86架构上的内存管理,各种的架构对内存的管理很大的不一样。像mips的内存管理就和X86有很大的不一样。内存管理这一章不深入理解下架构上的实现,总会有一些说不清道不明的感觉。

就我本人来说,真正了解过的只有x86的内存管理,看过一点arm的。其他的由于工作原因,很少接触。
但是这个帖子的目的不局限于某一种硬件平台,期望amarant兄分析一些宝贵资源啊

   
作者: 瀚海书香    时间: 2012-03-29 07:47
回复 20# 塑料袋
以塑料袋兄在硬件方面的知识,可以把mem/tlb cache这块顺便给大家说说

   
作者: 瀚海书香    时间: 2012-03-29 08:11
回复 16# dooros
由于不同的操作系统实现不同,而且我手头也没有这本书,这能根据你的描述以x86 linux为例,简单回答几个问题:

一、Page 116 4.3.2 地址变换机构
2.具有快表的地址变换机构
快表用以存放当前访问的那些页表项。
问题:在单处理机系统中,是不是每当进程阻塞、挂起、结束都要清除一次快表,即:快表只保留当前进程的页表项?
当前访问的理解?比如现在进程使用50个页,则快表中只保存50个页的信息,一段时间后,进程使用20个页,则快表立即清除其余30个页的信息,只保留20个页的信息。这样理解对否?
如果在多处理机系统中,进程并发执行,则快表同时保存多个进程的页表项(页号与物理块号的对应信息),如果快表中多个进程的页号相同,岂不乱套?是不是多处理机系统中有多个快表?
所谓的快表,在linux下就是TLB。
当进程的页表发生变化的时候,会修改TLB的内容;当CPU上发生进程切换的时候,会将本地的TLB清空(不考虑惰性TLB的情况)。
在SMP下,每个CPU都有一个本地TLB。但是仍然会出现多个CPU使用同一个页表集的情况,这样每当某一个CPU的TLB发生变化,都会通过IPI通知其他CPU修改各自的TLB项。

二、Page 114 4.3.1 页面与页表
问题:在分页存储管理中是所有的进程的页面大小都相等,还是不同进程有不同的页面大小?
分页存储管理中是不是把所有内存都划分为大小相等的物理块?页表是不是也存储在这样的物理块中?还是页表独立分配空间?
所有进程的页大小是相同的。
分页存储将所有的物理内存划分为大小相等的物理块。页表也是存储在这些物理块中。

三、Page 119 4.3.3 两级和多级页表
2 多级页表
倒数第二段最后一句:
这样的结果显然是不能令人接受的,因此必须采用多级页表,将外层页表再进行分页,也是将各分页离散地装入到不相邻的物理块中,再利用第二级的外层页表来映射它们之间的关系。
问题:这里是不是只能使用将部分页表调入内存这种方法?
不是的。多级页表的使用会减少因为页表管理所占用的物理地址。
四、Page 159 5.3.4缓冲池
第一段的最后一句话:为了提高缓冲区的利用率,目前广泛流行公用缓冲池;在池中设置了多个可供若干个进程共享的缓冲区。
问题:是不是整个缓冲池有一个空缓冲队列emq,而对应每个进程均需要一个输入队列inq和一个输出队列outq?如果整个缓冲区仅有一个输入队列inq 和一个输出队列outq,那么,在进程共享使用时,岂不是无法确定摘取的是否是自己需要的缓冲区?另外,队列的数据结构也不允许随机摘取。
这个不知道描述的什么?难道是linux下的slab
   
作者: lujian19861986    时间: 2012-03-29 09:40
可以看看此书,书名: 深入理解Linux虚拟内存管理
LINUX内存管理代码模块主要分为4个部分:
1.        Out of memory 代码在mm/oom_kill.c 貌似用于杀进程的时候对内存的操作
2.        虚拟内存的分配 代码在mm/vmalloc.c
3.        物理内存页面分配 代码在mm/page_alloc.cVMA(virtual memory addresses)的 创建和进程内的内存区域的管理
4.        这些模块,贯穿与其他kernel代码之中,形成更复杂的系统模块,如页面替换策略,buffer的输入输出等

作者: 瀚海书香    时间: 2012-03-29 10:55
回复 25# lujian19861986
欢迎分享读后感

   
作者: lutheran    时间: 2012-03-29 11:17
强力围观

作者: 塑料袋    时间: 2012-03-29 11:49
ARM在内存管理上,和x86的区别主要是页表构造不同。

kernel里的注释已经说的比较清楚了,解释arch/arm/include/asm/pgtable.h里的注释如下:

从硬件上说,ARM期望使用二级页表结构:
1) 第一级有4096个目录项
2) 第二级的每个表中,有256个目录项
每个目录项均为32bit。

第二级中,每个目录项指向了4096大小的page。这样,系统共 :pgd的4096项 * pte的256项 * 每页的4096大小 = 4G地址空间。

ARM的问题是,第二级每个目录项的32bit中,绝大部分已经为硬件所使用,并且没有accessed与dirty位。
accessed位为内存回收的依据,确定每个页在LRU链中位置;
dirty位作为内存回写的依据,脏页面应回写至文件或交换区。

因此,linux在构造页表时,制造了一个假象:
1) 硬件PGD还是4096项,每项4字节;但是linux按照PGD共2048项,每项8字节来计算,计算得来的每项中实际都含有两个pgd项。
2) 硬件PTE还是256项,每项4字节;但是linux每次分配pte表时,都分配4K大小的页,页的前2048字节折合512个pte项,即折合两个硬件的pte表;页的后2048字节留作它用,折合为虚拟的512个pte项,即折合两个虚拟的pte表,与前半页对应。

这样,2)中前半页折合出来的两个硬件pte表,正好填入1)中的2个pgd项中。

而且:
2)中前半页折合出来的两个硬件pte表中,每项都包含一些页的属性bit,如是否present,是否可写....这些属性均为ARM硬件支持的属性,命名为PTE_xxx
2)中后半页折合出来的两个虚拟pte表中,每项都包含一些页的属性bit,如是否accessed,是否dirty....这些属性均为ARM硬件不能支持的属性,命名为L_PTE_xxx

这样,就能模拟出来诸如accessed,dirty.....这样硬件还不支持的属性。

比如说模拟dirty:我们可以另页属性为不可写,一个写操作将引发页保护异常,进入到页异常的处理程序handle_pte_fault()后,linux将把虚拟pte项中置上L_PTE_dirty标志,同时将硬件pte项修改为可写;kswapd在操作LRU的时候,将清除掉虚拟pte项中的L_PTE_dirty位,同时也清除掉硬件pte项中的可写属性

比如说模拟access:我们可以清掉硬件pte中的present位,另映射不存在,一个读写操作将引发缺页异常,进入到页异常的处理程序handle_pte_fault()后,linux将把虚拟pte项中置上L_PTE_accessed标志,同时将置位硬件pte项的present位;kswapd在操作LRU的时候,将清除掉虚拟pte项中的L_PTE_access位,同时也清除掉硬件pte项中的present属性

注释中最后还提到,这么搞的话,应注意刷TLB,以另对硬件pte项的修改可见。
作者: scoocs    时间: 2012-03-29 13:39
回复 1# 瀚海书香


    LINUX采用的是分页制,而对于CPU提供的分段制,可以理解系统进行了直接的穿透

作者: jixuyang    时间: 2012-03-29 13:41
现在市面上的很多书,很多都是从内核角度来描述内存管理,包括哪些元素呀,各个元素的关联呀,虚拟地址映射呀等。
但是,内核就是为上层应用程序服务的,我们能否从应用程序的角度来描述内存管理呢?

进程启动的时候,需要把进程image加载到内存,这会涉及到内存管理。
进程从磁盘中读取文件的时候,需要把文件内容读取到内存,这也会涉及到内存管理。
进程加载动态链接库的时候,需要把动态库的代码和数据段加载到进程虚拟地址空间,这也会涉及到内存管理。

纯粹的讨论内存管理,个人感觉不能融会贯通地掌握内存管理的精华。
作者: 无风之谷    时间: 2012-03-29 13:56
呵呵 看到了很多潜水的冒泡了:wink:
作者: 塑料袋    时间: 2012-03-29 14:08
瀚海书香 发表于 2012-03-29 07:47
回复 20# 塑料袋
以塑料袋兄在硬件方面的知识,可以把mem/tlb cache这块顺便给大家说说


这块东西太多。

而且我只看过一些关于mips和opensparc的文档,这俩的实现都相对直接。

像x86,和典型的RISC大不相同,我觉得几乎是不能以常理揣测,容易实现且高效的办法,很少采用;复杂但高效的实现,却基本一个不落。
作者: dengbao2001    时间: 2012-03-29 14:14
包包不错,继续围观,继续支持!
作者: keilchu    时间: 2012-03-29 15:54
这种讨论太笼统,没有明确的议题,让人不知道从何说起
作者: 瀚海书香    时间: 2012-03-29 16:42
本帖最后由 瀚海书香 于 2012-03-29 17:23 编辑

回复 34# keilchu
这种讨论太笼统,没有明确的议题,让人不知道从何说起

只要是于内存相关的都可以拿出来讨论

BTW:潜水潜的好深了,居然就发了5个帖子

   
作者: asuka2001    时间: 2012-03-29 17:04
好吧,斑竹这颗深水炸弹炸出多少潜艇啊!!!威力十足恩恩!
作者: kernel_liwp    时间: 2012-03-29 18:51
浅析高端内存,不对的请指点

http://blog.csdn.net/woshixingaaa/article/details/6823194
http://blog.csdn.net/woshixingaaa/article/details/6826485
作者: sun5411    时间: 2012-03-29 21:01
[ 本帖最后由 sun5411 于 2012-04-06 11:05 编辑 ]

博客文章地址:
[url]http://blog.chinaunix.net/space.php?uid=15007890&do=blog&id=3153898[/url]

[align=center]我的内存管理理解[/align]

        80386中的寄存器
•        4个32位通用寄存器:EAX、EBX、ECX、EDX
•        4个32位地址寄存器:ESP、EBP、EDI、ESI
•        32位指令指针寄存器:EIP
•        32位标志寄存器:EFLAGS
•        6个16位段寄存器:CS(代码段)、DS(数据段)、ES(附加段)、SS(堆栈段)、FS、GS
•        4个32位控制寄存器:CR0、CR1、CR2、CR3
•        4个段表基地址寄存器:GDTR(48)、IDTR(48)、LDTR(16)、TR(16)
•        排错和测试寄存器


        实模式与保护模式
•        实模式:寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大段64KB。可以使用32位指令。32位的x86 CPU做高速的8086.
•        保护模式:寻址采用32位段和偏移量,最大寻址空间4GB,最大分段4GB(pentium pre及以后为64GB)。在保护模式下CPU可以进入虚拟8086方式,这是在保护模式下的实模式运行环境。
第一:实模式下程序运行回顾。
          程序运行的实质是什么? 其实很简单,就是指令的执行,显然cpu是指令的一致性的硬件保障。
         那么cpu如何知道指令在什么地方呢?对了,80x86系列是使用CS寄存器配合IP寄存器来通知CPU指令在内存中的位置。程序可能需要调用系统的服务子程序,80x86系列使用中断机制来实现系统服务。
        总得来说,这些就是实模式下一个程序运行的主要内容(其他如跳转、返回、端口操作等相对来说比较次要。)

第二:保护模式----从道行徐运行说起
         无论是实模式还是 保护模式,根本问题还是程序如何在其中运行。因此我们在学习保护模式的时候应该时刻围绕这个问题来思考。
        和实模式下一样,保护模式下程序运行的实质仍是“cpu执行指令,操作相关数据”,因此是模式下的各种代码段、数据段、堆栈段、中断服务程序仍然存在,且功能和作用不变。
      那么和保护模式下最大的变化是什么呢? 地址转换方式

第三: 地址转换方式的比较
         先看一下实模式下地址转换方式,假设我们在ES中存入0x1000,DI中存入0xFFFF,那么ES:DI=0x1000=0x1000*0x10 + 0xFFFF,这就是众所周知的“左移4位加偏移量”。
那么如果在保护模式下呢?  假设上面的数据不变ES=0x1000,DI=0xFFFF,现在ES:DI等于什么呢?
         公式如下:(注:0x1000=1000000000000b=10 0000 0000 0 00)
        ES:DI=全局描述符表中第0x1000项描述符给出的段基地址+0xFFFF
         现在比较一下,好像是不一样,再仔细看,又好像没什么区别!
         为什么说没什么区别,因为既然ES中内容不是真正的段地址,凭什么实模式下称ES为“段寄存器”,而到了保护模式就说是“选择子”?
          其实它们都是一种映射关系,只是映射规则不同而已: 在实模式下这个地址转换方式是“左移4位”,在保护模式下是“查全局/局部描述符表”。前者是系统定义的映射方式,后者是用户自定义的转换方式,而它影响的都是“shadow register”。
保护模式地址转换:

        从函数观点来看,前者是表达式,后者是列举式函数:
实模式: F(essegment)= {segment|segment = es*0x10}
保护模式: F(essegment)= {segment|(es,segment) ∈GDT/LDT}
其中GDT、LDT分别表示全局描述符表和局部描述符表。
描述符表有三种,分别为GDT、LDT和IDT(中断描述符表)
1.        全局描述符表GDT:
全局描述符表在系统中只能有一个,且可以被每个任务所共享,任何描述符都可以放在GDT中,但中断门和陷阱门放在GDT中是不会起作用的。能被多个任务共享的内存区就是通过GDT完成的。
2.        局部描述符表:
局部描述符表在系统中可以有多个,通常情况下是与任务的数量保持对等,但任务可以没有局部描述符表,任务间不相干的部分也是通过LDT实现的,这里涉及到地址映射的问题,和GDT一样,中断们和陷阱门不会起作用。
3.        中断描述符表IDT:
和GDT一样,中断描述符表在系统最多只能有一个,中断描述符表内可以存放256个描述符,分别对应256个中断,因为每个描述符占用8个字节,所以IDT的长度可达2k,中断描述符中可以有任务门、中断门、陷阱门三个描述符,其他描述符在中断描述符表中无意义。
4.        段选择子
在保护模式下,段寄存器的内容已不是段值,而成其为选择子。该选择子只是描述符在上面这三个表中的位置,所以说选择子即是索引值。
当我们把选择子装入寄存器时不仅使用该寄存器值,同时CPU将该选择子所对应的GDT或LDT中的描述符装入了不可见的部分。这样只要我们不进行代码切换(不重装入新的选择子)CPU就不会对不可见部分存储的描述符进行更新,可以直接进行访问,加快了访问速度。一旦寄存器被重新赋值,不可见部分也将被重新赋值。
为什么LDT要放在GDT中:
    LDT中的描述符和GDT中的描述符除了选择子的bit3一个为0一个为1用于区分该描述符是在GDT中还是在LDT中外,描述符本身的结构完全一样。开始我考虑既然是这样,为什么要将LDT放在GDT中而不是像GDT那样找一个GDTR寄存器呢?
   后来终于明白了原因——很简单,GDT表只有一个,是固定的;而LDT表每个任务就可以有一个,因此有多个,并且由于任务的个数在不断变化其数量也在不断变化。如果只有一个LDTR寄存器显然不能满足多个LDT的要求。因此INTEL的做法是把它放在放在GDT中。
GDTR全局描述符寄存器:48位,高32位存放GDT基址,低16为存放GDT限长。
LDTR局部描述符寄存器:16位,高13为存放LDT在GET中的索引值。
IA-32处理器仍然使用xxxx:yyyyyyyy(段选择器:偏移量)逻辑方式表示一个线性地址,那么是怎么得到段的基址呢?在上面说明中我们知道,要得到段的基址首先通过段选择器xxxx中TI位指定的段描述符所在位置:
当TI=0时表示段描述符在GDT中,如下图所示:① 先从GDTR寄存器中获得GDT基址。② 然后再GDT中以段选择器高13位位置索引值得到段描述符。③ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。

当TI=1时表示段描述符在LDT中,如下图所示:① 还是先从GDTR寄存器中获得GDT基址。② 从LDTR寄存器中获取LDT所在段的位置索引(LDTR高13位)。③ 以这个位置索引在GDT中得到LDT段描述符从而得到LDT段基址。④ 用段选择器高13位位置索引值从LDT段中得到段描述符。⑤ 段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。


第四:保护模式的基本组成
        保护模式最近本的组成部分是围绕着“地址转换方式”的变化增设了相应的机构。
1.        数据段
前面说过,实模式下的各种代码段、数据段、堆栈段、中断服务程序仍然存在,我将它们统称为“数据段”,本文从此乡下提到数据段都是使用这个定义。
2.        保护模式下引入描述符来描述各种数据段,所有的描述符均为8个字节(0-7),由第5个字节说明描述符的类型,类型不同,描述符的结构也有所不同。
    若干个描述符集中在一起组成描述符表,而描述符表也是一种数据段,也使用描述 符进行描述。
    从现在起,“地址转换”由描述表来完成,从这个意义上说,描述符表也是一张地址转换的函数表。
3.        选择子
选折子是一个2字节的数,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)还是LDT(局部描述符表)进行,最高13位给出了所需的描述符在描述符表中的地址。(注:13位正好足够地址8k项)
有了以上三个概念之后可以进一步工作了,现在程序的运行与实模式下完全一样!!!各段寄存器仍然给出一个“段值”,只是这个“假段值”到真正的段地址不再是“左移4位”,而是利用描述符来完成。但出现一个新的问题是:
      系统如何知道GDT/LDT在内存中的位置呢?
      为了解决这个问题,显然需要引入新的寄存器用于指示GDT/LDT在内存中的位置。在80x86系列中引入了两个新寄存器GDR和LDR,其中GDR用于表示GDT在内存中的段地址和段限(就是表的大小),因此GDR是一个48位的寄存器,其中32位表示段地址,16位表示段限(最大64K,每个描述符8字节,故最多有64k/8=8k个描述符)。LDR用于在内存中的位置,但是因为LDT本身也是一种数据段,它必须有一个描述符,且该描述符必须放在GDT中,因此LDR使用了DS、ES、CS等相同的机制,其中只存放一个“选择子”,通过查GDT表获得LDT的真正内存地址。
    对了,还有中断要考虑,在80x86系列中为中断服务提供中断/陷阱描述符,这些描述符构成中断描述符(IDT),并引入一个48位的全地址寄存器存放IDT的内存地址,理论上IDT表同样可以有8K项,可是因为80x86只支持256个中断,因此IDT实际上最大只能有256项(2k大小)。
第五:新要求---任务篇
        前面介绍了保护模式的基本问题,也是核心问题,解决了上面的问题,程序就可以在保护模式下运行了。
        但众所周知80286以后在保护模式下实现了对多任务的硬件支持。我的第一反应是:为什么不在实模式下支持多任务,是不能还是不愿?
        思考之后,我的答案是:实模式下能实现多任务。因为多任务的关键是有了描述符,可以给出关于数据段的额外描述,如权限等,进而在这些附加信息的基础上进行相应的控制,而实模式下缺乏描述符,但假设我们规定各段的前2个字节或若干字节用于描述段的附加属性,我觉得和使用描述符这样的机制没有本质区别,如果再附加其他机制…
        基于上述,我更倾向于任务任务是独立于保护模式之外的功能。下面我们来分析一下任务,任务的实质是什么呢?很简单,就是程序。所谓任务切换其实就是程序的切换!!
        现在问题明朗了。实模式下程序一个接一个的运行,因此程序运行的环境不必保存;保护模式下可能一个程序在运行过程中被暂停,转而执行下一个程序,我们要做什么?很容易想到保存程序运行的环境就行了(想想游戏程序中的保存进度功能),不如各寄存器值等。
        显然这些环境数据构成了一类新的数据段(即TSS)。沿用前面的思路,这些类段数据设置描述符(TSS描述符),将该类描述符放在GDT中(不能放在LDT中,因为80x86不允许),最后再加一个TR寄存器用于查表。TR是一个起“选择子”作用的寄存器,16位。
        好了,任务切换的基本工作就是将原任务“环境”存入TSS数据段,更新TR寄存器,系统将自动查GDT表获得并载入新任务的“环境”,然后转到新任务执行。

第六:附加要求---分页篇
        为什么叫附加要求,因为现在任务还不能很好地工作。前面说过,任务实质上是程序,不同的程序是由不同的用户写的,所有这些程序完全可能 使用相同的地址空间,而任务的切换过程一般不会包括内存数据的刷新, 不是不可能,而是如果那样做太浪费了。因此必须引入分页机制才可能有效 地完成对多任务的支持。
        分页引入的主要目标就是解决不同任务相互之间发生地址冲突的问题。 分页的实质就是实现程序内地址到物理地址的映射,这也是一个“地址转换” 机制,同样可以使用前面的方案(即类似GDT的做法):首先建立页表这样一种数据段,在80x86中使用二级页表方案,增设一个CR3寄存器用于存放 一级页表(又称为页目录)在内存中的地址,CR3共32位,其低12位总是为 零,高20位指示页目录的内存地址,因此页目录总是按页对齐的。CR3作为 任务“环境”的一部分在任务切换时被存入TSS数据段中。 当然还得有相应的缺页中断机制及其相关寄存器CR2(页故障线性地址寄存器)。

第七:总结
         保护模式下增加了什么?
1、寄存器 GDR LDR IDR TR CR3
2、数据段 描述符表(GDT LDT) 任务数据段(TSS) 页表(页目录 二级页表)
3、机制权限检测(利用选择子/描述符/页表项的属性位)
              线性地址到物理地址的映射

第八: 保护模式常用的名词解释
前面内容中出现过的不再解释。
1、RPL 选择子当中的权限位确定的权限
2、CPL 特指CS中的选择子当中的权限位确定的权限
3、EPL EPL=Max(RPL,CPL),即RPL和CPL中数值较大的,或说权限等级较小的
4、DPL 描述符中的权限位确定的权限
5、PL 泛指以上4种特权级
6、任务特权 =CPL
7、I/O特权 由EFLAGS寄存器的位13、14确定的权限
8、一致代码段 一种特殊的代码段,它在CPL>=DPL时允许访问
                正常的代码段在CPL=DPL RPL<=DPL时才允许访问

第九:x86的控制寄存器
CR0,CR1,CR2,CR3

状态和控制寄存器组除了EFLAGS、EIP ,还有四个32位的控制寄存器,它们是CR0,CR1,CR2和CR3。

这几个寄存器中保存全局性和任务无关的机器状态。

CR0 中包含了6个预定义标志,0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE位置1,则保护模式启动,如果PE=0,则在实模式下运行。1位是监控协处理位MP(Moniter coprocessor),它与第3位一起决定:当TS=1时操作码WAIT是否产生一个“协处理器不能使用”的出错信号。第3位是任务转换位(Task Switch),当一个任务转换完成之后,自动将它置1。随着TS=1,就不能使用协处理器。CR0的第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器。第4位是微处理器的扩展类型位 ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是387浮点协处理器。CR0的第31位是分页允许位(Paging Enable),它表示芯片上的分页部件是否允许工作。

CR1是未定义的控制寄存器,供将来的处理器使用。

CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。

CR3是页目录基址寄存器,保存页目录表的物理地址,页目录表总是放在以4K字节为单位的存储器边界上,因此,它的地址的低12位总为0,不起作用,即使写上内容,也不会被理会。

     这几个寄存器是与分页机制密切相关的,因此,在进程管理及虚拟内存管理中会涉及到这几个寄存器,读者要记住CR0、CR2及CR3这三个寄存器的内容.

[url]http://blog.chinaunix.net/space.php?uid=15007890&do=blog&id=3153898[/url]

作者: pitonas    时间: 2012-03-30 05:45
围观!
作者: 瀚海书香    时间: 2012-03-30 07:59
本帖最后由 瀚海书香 于 2012-03-30 16:06 编辑

回复 38# sun5411
写的非常不错

BTW:帮你编辑了一下帖子,把表情去掉了

   
作者: 瀚海书香    时间: 2012-03-30 16:08
回复 30# jixuyang
纯粹的讨论内存管理,个人感觉不能融会贯通地掌握内存管理的精华。

的确是这样的,我们需要从一个点来了解内存管理;但是在这之前需要对内存管理有个整体的把握。
   
作者: join__lin    时间: 2012-03-30 17:27
来看看,最近上班事情很少,业余时间都是在研究代码...
作者: 瀚海书香    时间: 2012-03-31 11:14
通过虚拟地址获取物理地址的代码

  1. #include <linux/module.h>
  2. #include <asm/pgtable.h>
  3. #include <linux/version.h>
  4. #include <asm/page.h>
  5. #include <linux/gfp.h>
  6. #include <linux/page-flags.h>
  7. #include <linux/sched.h>//find_task_by_vpid
  8. #include <linux/mm.h>//find_vma
  9. MODULE_LICENSE("GPL");
  10. MODULE_DESCRIPTION("CONVERT USER VIRTUAL ADDRESS TO PHYADDRESS");
  11. static int pid;
  12. static unsigned long va;
  13. module_param(pid,int,0644);
  14. module_param(va,ulong,0644);

  15. static int find_pgd_init(void)
  16. {
  17.         unsigned long pa=0;
  18.         struct task_struct *pcb_tmp=NULL;
  19.         pgd_t *pgd_tmp=NULL;
  20.         pud_t *pud_tmp=NULL;
  21.         pmd_t *pmd_tmp=NULL;
  22.         pte_t *pte_tmp=NULL;
  23.         if(!(pcb_tmp=find_task_by_pid(pid)))
  24.         {
  25.                 printk(KERN_ALERT "Can't find the task %d.\n",pid);
  26.                 return 0;
  27.         }
  28.         printk(KERN_ALERT "pgd=0x%p\n",pcb_tmp->mm->pgd);
  29.         if(!find_vma(pcb_tmp->mm,va))
  30.         {
  31.                 printk(KERN_ALERT "virt_addr 0x%lx not available.\n",va);
  32.                 return 0;
  33.         }
  34.         pgd_tmp=pgd_offset(pcb_tmp->mm,va);
  35.         printk(KERN_ALERT "pgd_tmp=0x%p\n",pgd_tmp);
  36.         printk(KERN_ALERT "pgd_val(*pgd_tmp)=0x%lx\n",pgd_val(*pgd_tmp));
  37.         if(pgd_none(*pgd_tmp))
  38.         {
  39.                 printk(KERN_ALERT "Not mapped in pgd.\n");
  40.                 return 0;
  41.         }
  42.         pud_tmp=pud_offset(pgd_tmp,va);
  43.         pmd_tmp=pmd_offset(pud_tmp,va);
  44.         /*FIXME:
  45.           Do I need to check Large Page ? PSE bit.
  46.                 if(pmd_large(*pmd_tmp) == 1){
  47.                         pa = (pmd_val(*pmd_tmp) & PMD_MASK) | (va & ~PMD_MASK);
  48.                 }
  49.           */
  50.         pte_tmp=pte_offset_kernel(pmd_tmp,va);
  51.         if(pte_none(*pte_tmp))
  52.         {
  53.                 printk(KERN_ALERT "Not mapped in pte.\n");
  54.                 return 0;
  55.         }
  56.         if(!pte_present(*pte_tmp))
  57.         {
  58.                 printk(KERN_ALERT "pte not in RAM,maybe swaped.\n");
  59.                 return 0;
  60.         }
  61.         pa=(pte_val(*pte_tmp)&PAGE_MASK)|(va&~PAGE_MASK);
  62.         printk(KERN_ALERT "virt_addr 0x%lx in RAM is 0x%lx.\n",va,pa);
  63.         printk(KERN_ALERT "content in 0x%lx is 0x%lx.\n",pa,*(unsigned long*)((char *)pa+PAGE_OFFSET));
  64.         return 0;
  65. }
  66. static void find_pgd_exit(void)
  67. {
  68.         printk(KERN_ALERT "Goodbey.\n");
  69. }
  70. module_init(find_pgd_init);
  71. module_exit(find_pgd_exit);
复制代码

作者: 瀚海书香    时间: 2012-03-31 11:17
回复 43# 瀚海书香
这个代码是没有考虑PSE的情况。有PSE的环境,可以根据代码中的FIXME部分,进行修改。

   
作者: ww2000e    时间: 2012-03-31 15:28
本帖最后由 ww2000e 于 2012-03-31 15:39 编辑
瀚海书香 发表于 2012-03-31 11:14
通过虚拟地址获取物理地址的代码



我在3.2.11内核上编译  给find_task_by_pid改成find_task_by_vpid对不对?  

不过  提示这个阿

WARNING: "find_task_by_vpid" [/vm_to_pm/main.ko] undefined!


找到方法了 ,那函数没导出 ,改成这个貌似行了
pcb_tmp = pid_task(find_vpid(pid), PIDTYPE_PID)
作者: moniskiller    时间: 2012-03-31 18:16
大家可以看看我写的这份关于内存的文档,可以跟我 交流。 保证能让你对内核内存物理内存管理分配 ,虚拟内存管理分配恍然大悟

ARM_Linux_Course_17_Kernel_Memory_Management.PDF

496.02 KB, 下载次数: 236


作者: linuxfellow    时间: 2012-03-31 21:50
回复 46# moniskiller

好东西多谢! 当然会有一些问题要问。 但你不会动员我去你们的培训班把?
   
作者: moniskiller    时间: 2012-03-31 21:55
技术交流第一。
作者: GFree_Wind    时间: 2012-04-01 12:15
回复 46# moniskiller

虽然有一点点广告嫌疑,但是东西确实不错,必须支持!



   
作者: 怪怪虎    时间: 2012-04-01 15:06
想知道这么个事情:
虚拟地址转到物理地址,是通过mmu. mmu是一块芯片, 在这个地址转换的过程中,软件和硬件协调着工作,
想知道软件在什么时候就全部交给了硬件,他们之间有明显的分解线吗?

+++
个人理解,软件把一些值付给mmu的寄存器后,mmu就会自动把相应的物理地址转化出来。不知道对不对。 望高人给指点。
作者: ak47mig    时间: 2012-04-01 16:53
大家都是从内核级别,设计级别讲的,比较高深。
我讲点实际应用中:
1.32 vs 64.
如果内存超过4G,基本上还是用64位系统+64位程序了,
就算用了pae,或者64系统跑32位程序,程序占用的内存数也是有限的
2.要保证一定的内存空闲度。
linux默认会把内存用在cache和buffer上,如果有较高的io,最好保证系统有30%甚至是40%的内存空闲
3.不要依赖swap
swap虽好,但是如果有一点占用,对系统的性能也会影响很大
4.oom经常会干掉sshd
oom在实际应用中不是想想的那么好用,该杀的不杀,不该杀的杀掉了....当然这个貌似可以配置优先级,不过具体没设置过.
作者: hpsfpcca    时间: 2012-04-01 17:07
这个奖品不错
作者: tempname2    时间: 2012-04-01 17:18
回复 51# ak47mig


脏页是可控的。前些时面了个试,看了一下这方面的代码,可能有点冷门,我正好简单概括一下。

基本上,用户通过proc目录设定两个参数来决定系统对脏页的容忍程度。每个一个CPU弄脏一定数量的disk cache里的页时,就要检查一下当前脏页情况如何。检查的频率要设置成,在最极端的情况下也可以保证,两次检查脏页情况期间,被弄脏的页数量不超过系统总内存的3%。最极端的情况是,两次检查期间,所有CPU并发地弄脏页。

每一次检查并不一定会引发页回写的操作。判断是否要采取行为取决于用户设置的容忍度,但并非简单将这个值与系统脏页值做对比。

下班了,下次有机会再说吧。

作者: moniskiller    时间: 2012-04-02 09:47

抛开cache,每条指令执行前都要从CPU 外部SDRAM取指令(Prefetch),如果是LDR/STR/LDM/STM 等指令,这些指令执行的时候涉及到外部存储器数据的装载和保存,所有这些都需要把相关的地址送到系统总线, 再有总线控制器解码然后送到相关的外设控制器。不同的外设控制器对总线来的地址或者数据就按照自己的协议和规范来操作

MMU打开的时候:指令预取 和 Load/Store指令执行的时候,地址都会送到MMU. MMU 和ARM核一样是硬件的,当然MMU事先必须配置,包括转换类型,页表,转换表基地址。 把这些信息告诉MMU.MMU就可以把虚拟地址转换成物理地址,最后送到总线上,对于ARM来讲,控制外设,一定要把外设的物理地址送到总线才可以。
作者: titer1    时间: 2012-04-02 21:19


首先感谢wdy 热心给图。当时就收藏了,但是没有细嚼。
借这个机会,谈谈我的读图感受吧:
各位大大随便抛砖吧。

内存管理分为 用户空间和核心地址空间。
明显区别是:核心地址空间访问页,是直接访问。

首先看用户空间:
pgd是创建一个进程的过程中分配的。
图中的用户线性地址是典型的32位寻址。
用户程序只能看见虚地址空间。

再说核心空间:
核心空间分为3个zone,分界线分别为 16m和896m.
free_pages的管理是 由伙伴算法实施的。

至于内核高端内存的映射,我没有系统学习,正好各位大大可以补充。

newbaby 66


作者: titer1    时间: 2012-04-02 22:03
本帖最后由 titer1 于 2012-04-02 22:07 编辑

今天有事,把图贴上,以后补上我的浅见


vm子系统交互图示


作者: titer1    时间: 2012-04-02 22:09
对了,上面的图示 ,来自于 北航的翻译版本。补充一下。

现在贴出书中关于 内核空间的解释图示:



作者: titer1    时间: 2012-04-02 22:10
其实一句话,认真读Mel Gorman 的
Understanding The Linux Virtual Memory Manager 是最好的方法,
作者还有一个 类似情景分析的 Code Commentary On The Linux Virtual Memory Manager
作者: titer1    时间: 2012-04-03 07:22
早上手机饿登录一看,是不是大牛都放假了?大家顶起哈
作者: regfoc    时间: 2012-04-03 18:32
回复 54# moniskiller

好东西,不错不错
作者: moniskiller    时间: 2012-04-03 20:17
传份我写的灯片给大家 参考下,有任何问题可以+ q 752070534 交流

ARM_Linux_Course_17_Kernel_Memory_Management.PDF

496.02 KB, 下载次数: 122


作者: titer1    时间: 2012-04-04 08:33
问个问题, Linux驱动模块是不是运行在 核心空间?
作者: 瀚海书香    时间: 2012-04-04 11:23
回复 62# titer1
问个问题, Linux驱动模块是不是运行在 核心空间?

是的。
   
作者: titer1    时间: 2012-04-04 17:49
本帖最后由 titer1 于 2012-04-04 17:56 编辑

谢谢版主热心解答。
不过我在看 moniskiller 的pdf看见:



上图来源
ARM_Linux_Course_17_Kernel_Memory_Management.PDF


那么 同时请教下,瀚海书香 和 moniskiller,图中

    TASK_SIZE 到  PAGE_0FFSET -1 区域是 Kernel module space ,这里应该是个bug吧。请确认?
顺便补充我的想法,我觉得应该那个区间是动态共享库的位置

至于这个空间分布,我觉得arm .x86应该是一致的哈。

作者: 瀚海书香    时间: 2012-04-04 20:30
回复 64# titer1
TASK_SIZE 到  PAGE_0FFSET -1 区域是 Kernel module space ,这里应该是个bug吧。请确认?
顺便补充我的想法,我觉得应该那个区间是动态共享库的位置

对应arm架构来说就是这样的,这块空间按Document的描述来说,的确是用于kernel module的

在x86下,#define TASK_SIZE PAGE_OFFSET
在arm下,#defeine TASK_SIZE (UL(CONFIG_PAGE_OFFSET) - UL(0x1000000))
作者: titer1    时间: 2012-04-04 20:31
本帖最后由 titer1 于 2012-04-04 20:31 编辑

再谈自己 关于 进程 线程 与内存的一些关系:

1.不同进程虚存空间是独立的,唯一例外的就是: share memory 是可以共享的。
2.用户线程共享 进程的 用户空间,但是核心空间 堆栈是各自独立的
3. 核心线程只拥有内核堆栈,没有用户空间说法。
作者: ljzbq123    时间: 2012-04-04 21:09
本帖最后由 ljzbq123 于 2012-04-04 21:12 编辑

在arm系统中,modules是放置在3G-16M~3G之间,如下linux 2.6.35的定义。

Memory.h (kernel\arch\arm\include\asm)       
#define TASK_SIZE                (UL(CONFIG_PAGE_OFFSET) - UL(0x01000000))

#define MODULES_VADDR                (PAGE_OFFSET - 16*1024*1024)

另外本人研究linux内存管理也有一段时间了,对active_list和inactive_list还有些问题没有完全弄清楚,哪位大侠可以讲讲active_list和inactive_list具体是怎么产生的?目前对 active_list和inactive_list的一些理解如下:
1. 这2个list只针对可以回收的内存。
  1)文件读写操作过程中用于缓冲数据的页面
  2)用户地址空间中用于文件内存映射的页面
  3)匿名页面:进程用户模式下的堆栈或者是使用 mmap 匿名映射的内存区
  4)特殊的用于 slab 分配器的缓存,比如用于缓存文件目录结构 dentry 的 cache,以及用于缓存索引节点 inode 的 cache)
2. 只针对应用进程,内核内存不涉及到这两个list。
3. active_list和inactive_list和buffer、cache的关系。buffer+cache作为最大可使用“准空闲”内存,而inactive_list也具有这种可转化为空闲内存的能力,2者之间肯定有一定的联系,比如一个page即链接在inactive_list中,又链接在buffer+cache中,从下面的meminfo接口中,我大概可以得出如下的一种关系:
Inactive( 107076 kB)<  buffer+cache(23864 +126840 )<Inactive( 107076 kB)< +Active(203992 kB).

上面3点是本人的一些初步理解,希望哪位大侠能够把理解不够或错误的地方指出来,同时抛砖引玉,继续深入阐释active_list和inactive_list和buffer、cache的关系。



MemTotal:         446684 kB
MemFree:           30748 kB
Buffers:             23864 kB
Cached:           126840 kB
SwapCached:            0 kB
Active:            203992 kB
Inactive:         107076 kB
Active(anon):     160556 kB
Inactive(anon):      416 kB
Active(file):      43436 kB
Inactive(file):   106660 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 4 kB
Writeback:             0 kB
AnonPages:        160360 kB
Mapped:            40612 kB
Shmem:               624 kB
Slab:              13504 kB
SReclaimable:       3432 kB
SUnreclaim:        10072 kB
KernelStack:        3728 kB
PageTables:        11380 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      223340 kB
Committed_AS:    1755684 kB
VmallocTotal:     491520 kB
VmallocUsed:      107460 kB
VmallocChunk:     367620 kB
#
作者: vsyour    时间: 2012-04-04 21:20
JVM进行调优
  1. if [ "${together}" = "MNAOM" ];then   
  2.             nohup $JRE_HOME/bin/java -Duser.timezone=UTC -server -Xms256m -Xmx1024m -XX:MaxNewSize=256m -XX:PermSize=128m -XX:MaxPermSize=512m -cp $JAVACP ${SPGPROC_ID} $BOARD_FRAME $BOARD_SLOT>/dev/null 2>&1 &
  3.         elif [ "${together}" = "SPSPG" ]; then
  4.             nohup $JRE_HOME/bin/java -Duser.timezone=UTC -server -Xms256m -Xmx2048m -XX:MaxNewSize=256m -XX:PermSize=128m -XX:MaxPermSize=512m -cp $JAVACP ${SPGPROC_ID} $BOARD_FRAME $BOARD_SLOT>/dev/null 2>&1 &
  5.         else
  6.             echo "Only base board can excute."
  7.             rm $FLAG_STARTING_FILE >> $LOGFILE
  8.             exit 1;
  9.         fi
复制代码

作者: haitao    时间: 2012-04-04 22:44
一直在win下用ramdisk避免临时文件的实际写盘导致的慢和磨损(虽然:有缓存,很多人说ramdisk没必要,但是写一次盘还是。。。。)
不知道linux下,ramdisk是不是真的没必要了

看到过linux下曾有一种ramdisk,比如8G内存,它占了1G,但是如果它上面的文件超过1G了,还可以直接拿真正的硬盘扩充自己,以便应用继续写,而不会直接报错:空间不足
这个功能好像win下一直没实现过
作者: titer1    时间: 2012-04-05 14:27
ljzbq123 发表于 2012-04-04 21:09
在arm系统中,modules是放置在3G-16M~3G之间,如下linux 2.6.35的定义。

Memory.h (kernel\arch\arm\inc ...


关于active_list和inactive_list和buffer、cache的关系。有篇好的帖子:
看看这个吧,我也在学习:
Linux Cache 机制探究

http://www.penglixun.com/tech/system/linux_cache_discovery.html
作者: titer1    时间: 2012-04-05 14:28
瀚海书香 发表于 2012-04-04 20:30
回复 64# titer1

对应arm架构来说就是这样的,这块空间按Document的描述来说,的确是用于kernel module ...


谢谢版主指正,原来arm x86 内存上细节上差别如此!!
作者: titer1    时间: 2012-04-05 23:31
本帖最后由 titer1 于 2012-04-05 23:34 编辑

在阅读 Understanding the Linux® Virtual Memory Manager,第二章中关于swap有下图:




涉及swap,有三个参数,page_min,page_max,pages_low,
下文中简称(min,max,low)

当可用页数 (available pages)小于min,启动kswapd

当可用页数               小于low,依然可以分配,只不过只能是fgp_atomic类型。
问题:图中,从min到low,页消耗率明显降低。但是小于low后,为何页消耗率会又增加?
在书上的注释说:

pages_min When pages_min is reached, the allocator will do the kswapd work in a synchronous fashion, sometimes referred to as the direct-reclaim path. Solaris does not have a real equivalent, but the closest is the desfree or minfree, which determine how often the pageout scanner is woken up.
我看完注释,依然不解。

当可用页数 恢复到max,此时kswap休眠。
期间,从min到low,从low 到high,前个区间回收页面的速率低,我认为前段区间有内存分配器的帮忙,后者只有kswapd工作。不知各位看法。


作者: sun5411    时间: 2012-04-06 11:01
本帖最后由 sun5411 于 2012-04-06 11:05 编辑

回复 40# 瀚海书香
哈哈,谢谢!

   
作者: 大邪神    时间: 2012-04-06 11:29
我不讨论,只要包,行不?

作者: 瀚海书香    时间: 2012-04-06 15:35
回复 74# 大邪神
我不讨论,只要包,行不?

可以不讨论,但是得分享经验和对内存管理的理解
   
作者: amarant    时间: 2012-04-06 16:20
看内存管理可以从不同的角度去看。
从虚拟地址上去看,自然联想到的知识点就是3:1的内存模型。在就是用户空间地址的安排诸如红黑树之类知识点。内核空间地址知识点就有固定映射、直接映射之类的知识点。
从物理地址的管理上看,很容易就联想到了伙伴系统,页表设置等等
这些知识点都可以从《深入理解Linux内核》的第八第九章学习到。大部分介绍内核的书籍也会介绍相关的知识点。

作者: own_qihua    时间: 2012-04-06 16:46
想高人学习
作者: renxiao2003    时间: 2012-04-06 22:06
虽然说现在的内存价格越来越便宜了,但是在任何一个操作系统中,内存管理都是OS的一个主要任务,不仅仅是Linux。
因为受操作系统限制,比如32位的OS就只能支持3G左右的内存,所以就算你有N多的钱,买N多的内存,但受系统支持的限制,现在你也只能使用其中的3G内存。
所以,内存管理很重要。综合如下::
1. 内核初始化:

    * 内核建立好内核页目录页表数据库,假设物理内存大小为len,则建立了[3G--3G+len]::[0--len]这样的虚地址vaddr和物理地址paddr的线性对应关系;
    * 内核建立一个page数组,page数组和物理页面系列完全是线性对应,page用来管理该物理页面状态,每个物理页面的虚地址保存在page->virtual中;
    * 内核建立好一个free_list,将没有使用的物理页面对应的page放入其中,已经使用的就不用放入了;

2. 内核模块申请内存vaddr = get_free_pages(mask,order):

    * 内存管理模块从free_list找到一个page,将page->virtual作为返回值,该返回值就是对应物理页面的虚地址;
    * 将page从free_list中脱离;
    * 模块使用该虚拟地址操作对应的物理内存;

3. 内核模块使用vaddr,例如执行指令mov(eax, vaddr):

    * CPU获得vaddr这个虚地址,利用建立好的页目录页表数据库,找到其对应的物理内存地址;
    * 将eax的内容写入vaddr对应的物理内存地址内;

4. 内核模块释放内存free_pages(vaddr,order):

    * 依据vaddr找到对应的page;
    * 将该page加入到free_list中;

5. 用户进程申请内存vaddr = malloc(size):

    * 内存管理模块从用户进程内存空间(0--3G)中找到一块还没使用的空间vm_area_struct(start--end);
    * 随后将其插入到task->mm->mmap链表中;

6. 用户进程写入vaddr(0-3G),例如执行指令mov(eax, vaddr):

    * CPU获得vaddr这个虚地址,该虚地址应该已经由glibc库设置好了,一定在3G一下的某个区域,根据CR3寄存器指向的current->pgd查当前进程的页目录页表数据库,发现该vaddr对应的页目录表项为0,故产生异常;
    * 在异常处理中,发现该vaddr对应的vm_area_struct已经存在,为vaddr对应的页目录表项分配一个页表;
    * 随后从free_list找到一个page,将该page对应的物理页面物理首地址赋给vaddr对应的页表表项,很明显,此时的vaddr和paddr不是线性对应关系了;
    * 将page从free_list中脱离;
    * 异常处理返回;
    * CPU重新执行刚刚发生异常的指令mov(eax, vaddr);
    * CPU获得vaddr这个虚地址,根据CR3寄存器指向的current->pgd,利用建立好的页目录页表数据库,找到其对应的物理内存地址;
    * 将eax的内容写入vaddr对应的物理内存地址内;   

7. 用户进程释放内存vaddr,free(vaddr):

    * 找到该vaddr所在的vm_area_struct;
    * 找到vm_area_struct:start--end对应的所有页目录页表项,清空对应的所有页表项;
    * 释放这些页表项指向物理页面所对应的page,并将这些page加入到free_list队列中;
    * 有必要还会清空一些页目录表项,并释放这些页目录表项指向的页表;
    * 从task->mm->mmap链中删除该vm_area_struct并释放掉;

作者: 瀚海书香    时间: 2012-04-07 08:07
回复 78# renxiao2003
多谢分享!

   
作者: 瀚海书香    时间: 2012-04-07 08:08
回复 78# renxiao2003
比如32位的OS就只能支持3G左右的内存,所以就算你有N多的钱,买N多的内存,但受系统支持的限制,现在你也只能使用其中的3G内存

linux系统虽然支持的总内存数会大于4G,但应用程序可用的用户态内存的确是3G(1:3模型)。64位应该是趋势了

   
作者: 瀚海书香    时间: 2012-04-09 12:28
回复 66# titer1
2.用户线程共享 进程的 用户空间,但是核心空间 堆栈是各自独立的

由于linux本身不支持用户态线程,所以这个不同的线程库有不同的实现方式。

   
作者: axiaowuua    时间: 2012-04-11 14:53
:wink:过来参观,顺便学习学习。
作者: titer1    时间: 2012-04-12 14:22
g__gle 发表于 2012-03-28 21:24

3.896M与高端内存
X86上Linux内核与进程共用地址空间,另外,内核所有的地址空间被设计成与内存固定映射。
结果是:内核拥有的地址空间很小(因为用户态占了大部分);
能灵活使用的虚拟地址不多(因为大部分要与物理内存做一一对应的关联)。
于是,内核能使用物理内存的大小被其狭小的虚拟地址空间所限制了。
即便有再多的物理内存,内核已没有多余的虚拟地址与之映射,从而无法访问。


所以,896M用于与物理内存建立一一对应关系,
剩余的虚拟地址空间,分时映射没能被896M覆盖到的物理内存,这部分物理内存就是高端内存。       
...


        我的问题:
        1.内核所有的地址空间被设计成与内存固定映射。这句话中的“所有”表示内核空间不存在
        用户空间那种换页,第二一旦调用内核分配函数就会有相应的页表项得到分配(与之对应的
        ,用户空间调用malloc..如果不立即访问该地址,页表项为空)。是否?
       
        2.64位机器上,如果内存条不止4g,那么传统的3:1模型,那个空间会增大啊?
        我的理解是:用户空间始终是3g,物理内存的变大可以减少换页的几率。 是否?
       
作者: titer1    时间: 2012-04-12 14:32
g__gle 发表于 2012-03-28 21:24

以前看APUE,以为mmap是个trick十足的系统调用。
后来才明白,如果不理解内核的cache系统,mmap的作用很难理解。
...


这里mmap 与cache 是如何交互的,希望能够细致讲解。:wink:
作者: 瀚海书香    时间: 2012-04-12 14:42
回复 88# titer1
这个或许有用http://linux.sheup.com/linux/38/linux21081.htm
1、 page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。
2、文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。
3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。
4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。 注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。
5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。 注:一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。

   
作者: titer1    时间: 2012-04-12 14:55
瀚海书香 发表于 2012-04-12 14:42
回复 88# titer1
这个或许有用http://linux.sheup.com/linux/38/linux21081.htm
1、 page cache及swap c ...


这么快就回复了,谢版主!
作者: doit_zzg    时间: 2012-04-12 15:55
    前段时间发现服务器会出现短暂访问异常缓慢的情况,top发现是kswapd 100%引起的。服务器上面跑的memcached、mongodb,这些都是内存大户。借着这个机会,正好分析了swap以及内存管理相关的原理,跟大家交流一下心得。

1.linux内存管理涉及的概念有:page、zone、node、页目录、页表。
分页单元把所有的RAM分成固定长度的页框,每个页框包含一个页。
页目录项和页表都具有present字段,表明page是否在主存中,如果present标志被清0,那么分页单元就把该线性地址存放cr2中,产生缺页异常。
页框的状态信息保存在page页描述符中,所有的页描述符存放在mem_map数组中。page的_count是页引用计数器,_flags字段描述页框状态PG_xxxx。
NUMA中,根据内存单元的访问时间划分为node,在一个单独的节点,任意给定CPU访问页面所需的时间都是相同。用pg_data_t来描述。
每个节点的物理内存又可分为几个管理区zone。zone分为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM,
管理区描述符里面,比较重要的字段有free_pages pages_min pages_low pages_high free_area

2.管理区zone
pages_min 管理区中保留页的数目
pages_low 回收页框使用的下界、同时也被管理区分配器作为阈值使用
pages_high 回收页框使用的上界、同时也被管理区分配器作为阈值使用

3.管理区分配器
管理区分配器接受动态内存分配与释放的请求,在请求分配的情况下,搜索一个能满足请求的一组连续页框内存的管理区,在每个管理区内,页框由buddy system来处理。
请求页框:
alloc_pages --> alloc_page --> __get_free_pages --> __get_free_page
释放页框:
free_pages free_page

__rmqueue() 函数用来在管理区找到一个空闲块
__free_pages_bulk()函数按照buddy system的策略释放页框

__alloc_pages()函数,会对内存管理区扫描,配合z->pages_min 、z->pages_low、z->pages_high标志,如果没有剩下多少空闲内存,会唤醒kswapd内核线程来异步地开始回收页框。

4.获得动态内存方式:
__get_free_pages或alloc_pages 从页框分配器中获取页框;
kmem_cache_alloc 或kmalloc 使用slab分配器为专用或通用对象分配块;
vmalloc 获得一块非连续的内存区。
作者: doit_zzg    时间: 2012-04-12 16:12
5.kswapd内核线程,主要的执行流程:

kswapd被唤醒时,主要执行逻辑是:
调用kswapd_can_sleep,变量所有的管理区,检查zone_t的need_balance 字段,如果有need_balance被设置,则它不能睡眠。
如果不能睡眠,则会被移出kswapd_wait等待队列;
调用kswapd_balance 遍历所有的管理区,如果need_balance字段被设置,它将使用try_to_free_pages_zone来释放管理区内的页面,直到达到pages_high阈值;
由于tq_disk的任务队列的运行,所以也没队列都将清除;
将kswapd重新加入kswapd_wait队列并返回继续执行。
作者: 瀚海书香    时间: 2012-04-12 16:16
回复 91# doit_zzg
多谢分享!

   
作者: doit_zzg    时间: 2012-04-12 17:01
在学习内存管理的过程中,感觉带着问题去找答案可能思路会清晰些,我列了一些便于理清思路的问题,希望大家一起完善啊~~~

1.内存寻址的方式,线性地址、物理地址的转换关系?

2.页目录表、页表作用?物理页面是如何分配的?

3.硬件高速缓存、TLB作用是?

4.进程页表、内核页表关系、作用是?

5.page、zone、node关系,分别是管理的目标是?

6.请求、释放页框的函数流程是?

7.高端内存页框的内核映射原理?

8.buddy system 提出的目的,方法?实现的过程是?

9.slab分配的原理?

10.非连续内存管理,连续的线性地址是如何与非连续的页框关联起来的?

11.进程结构是如何与内存管理系统关联起来的?

12.vm_area_struct是如何实现线性区的?

13.申请、释放线性地址区间的过程是?

14.do_mmap do_munmap  do_mremap实现的过程?

15.什么时候发生缺页,缺页异常处理程序的处理流程是?

16.如何做到请求调页?

17.进程堆管理,函数调用流程?这里如何与页框产生关系的?

18.系统没有空闲内存了会触发什么动作?

19.页框回收的方式有哪些?

20.页框回收时,是如何与slab、zone、node关联起来的?如何收缩slab?

21.pageout写回磁盘的流程是?

22.swap 信息的结构,缺页发生后,如何从磁盘唤回内存的?

23.内存管理中涉及到的高速缓存有哪些?

24.内存管理中数据结构有哪些?
作者: zhanglong71    时间: 2012-04-12 20:33
good good study, day day up!
作者: lingfeng892    时间: 2012-04-13 19:32
新手只知道改/proc/sys/vm/drop_caches可以释放内存
作者: fuxinrong    时间: 2012-04-14 00:02
实现各个task共享内核空间的机制:
1、各个task拥有自己的页表。
2、在切换task时将一级页表的高4K部分拷贝到新task的一级页表的高4K部分,再将新的task的页表基址告诉MMU,从而使MMU看到的内核空间和原任务的内核空间的映射关系是一样的。

一年前研究过的,把当时跟踪的一个问题答案贴出来。呵呵
作者: rootsecurity    时间: 2012-04-14 09:29
以前盲目的认为Linux可用内存就是free -m所看到的free,但是后来与前辈讨论得知自己的观点是错误的。
当在Linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching。这个问题,貌似有不少人在问,不过都没有看到有什么很好解决的办法。

通常情况:
  1. [root@localhost ~]#free -m
  2. total used free shared buffers cached
  3. Mem: 249 163 86 0 10 94
  4. -/+ buffers/cache: 58 191
  5. Swap: 511 0 511
复制代码
其中:
total 内存总数
used 已经使用的内存数
free 空闲的内存数
shared 多个进程共享的内存总额
buffers Buffer Cache和cached Page Cache 磁盘缓存的大小
-buffers/cache 的内存数:used - buffers - cached
+buffers/cache 的内存数:free + buffers + cached

可用的memory=free memory+buffers+cached。

手动释放:cat /proc/sys/vm/drop_caches
cache的好处就是可以减少很多硬盘的访问。
作者: 瀚海书香    时间: 2012-04-14 13:45
回复 98# rootsecurity
的确是这样的。除非内存的确紧张了,否则没必要手动的清楚page cache。

   
作者: EnjoyKernel    时间: 2012-04-14 17:21
本帖最后由 EnjoyKernel 于 2012-04-14 17:23 编辑

结贴!
据我所知,Linux内存管理的粒度为页。内存分配粒度为页内大小块。

我100楼!!!LZ发奖品。。速度的。


额。。楼层怎么变了。。
作者: 望海捉襟    时间: 2012-04-14 23:53
研究中。。。。。
作者: titer1    时间: 2012-04-14 23:59
凌晨学习中。
作者: titer1    时间: 2012-04-15 01:01
本帖最后由 titer1 于 2012-04-15 12:58 编辑


我来试着回答刚才 提出的问题,即兴发挥。
部分问题,明天早上补上。

讨论架构当前只是:x86,如果各位UCer有其他架构经历,可以倾听高见。

1.内存寻址的方式,线性地址、物理地址的转换关系?

A: 进入操作系统后,内存使用保护模式寻址。线性地址经过分页后转换为物理地址。


-----------------------------------
2.页目录表、页表作用?物理页面是如何分配的?
A:
由页目录表、cr3、页目录偏移(线性地址高10bit)可以查出 页目录项  ;
页目录 项用于在页表中定位,结合页表偏移得到 物理页基地址。
A2:物理页面的分配,分配方式是first-fit...(见前面的帖子),分配策略是buddy system.


-----------------------------------

3.硬件高速缓存、TLB作用是?

A:(?)硬件高速缓存指cpu相连的L1,L2
A2:TLB是页表缓存


-----------------------------------

4.进程页表、内核页表关系、作用是?


A:内核页表 专指 页全局目录的768项以上的目录项指向的页表
二者区别在于用户空间必须MMU映射访问,内核空间可以直接访问页表,无需映射。


-----------------------------------

5.page、zone、node关系,分别是管理的目标是?

A:1个node (NUMA)可以管理3个zone(dma,normal,high),   1个zone对应若干page


-----------------------------------

6.请求、释放页框的函数流程是?
简答关键词:alloc_pages ,free_pages
A:详细:
参考:http://www.orczhou.com/index.php ... emory-management-3/ (大牛作品)

-----------------------------------
7.高端内存页框的内核映射原理?
简答关键词:page_address_htable散列表, kmalloc
A:李万鹏文章:http://blog.csdn.net/woshixingaaa/article/details/6823194
-----------------------------------


8.buddy system 提出的目的,方法?实现的过程是?

A:目标:管理物理页,尽量少外存碎片。
方法:按照(2^0,2^1,2^2...2^n)大小管理“空闲物理页”,
过程:
当外界申请时,有空余的,当2^i大小满足,直接分配;
当2^i不满足,而2^(i+1)满足时,split 2^(i+1)为两半,
多出来剩余的加入buddy system;
当外界释放时,加入对应链表,如果有相邻的空余页可构成更大的页,就合并。

-----------------------------------

9.slab分配的原理?
简答关键词:小粒度内核内存分配机制,内存池,非真正释放
A:
基本思想:
其基本思想是:一次向内核获取整数页,slab根据数据结构的大小进行划分为一个个小的数据结构,
当需要时直接从该链表上摘取一个返回应用程序,
当应用程序释放时,而“非真正释放”,只需要该空间放回到链表中,
当分散的一页多块又聚集一页时,又会拼成一页,同时判断slab空闲的页数,
如果空闲页超过一定的页数,就会向系统释放一定的页数。
一个slab分配器只能管理一个指定大小的数据结构分配。

//http://blog.chinaunix.net/uid-20687780-id-3154025.html
再来一个6b的图示:

//来源:李万鹏
我觉得图中将slab的3个链表(尤其外在环境)讲解得很清晰。

其他详细分析请看:李万鹏(一个系列,非常清晰)
http://blog.csdn.net/woshixingaaa/article/details/6772585
ibm专家分析:
https://www.ibm.com/developerwor ... nux-slab-allocator/
csdn专家讲解:
http://blog.csdn.net/yunsongice/article/details/5272715
(此作者考虑的更完整)
-----------------------------------

10.非连续内存管理,连续的线性地址是如何与非连续的页框关联起来的?
简答关键词:虚存连续,物理不连续;页表机制
下面是高手解答:
http://blog.csdn.net/yunsongice/article/details/5536197

-----------------------------------

11.进程结构是如何与内存管理系统关联起来的?

A:task_struct中 有指针mm指向mm_struct

-----------------------------------

12.vm_area_struct是如何实现线性区的?
简言之:通过链表
详细请看下面vm_area_struct的数据结构:
A:
Linux通过类型为vm_area_struct的对象实现线性区,它的字段如下所示:

struct vm_area_struct {
struct mm_struct * vm_mm; /* 指向线性区所属的内存描述符 */
unsigned long vm_start;  /* 线性区内的第一个线性地址 */
unsigned long vm_end;  /* 线性区之后的第一个线性地址 */

/* linked list of VM areas per task, sorted by address 进程链表中的下一个线性区*/
struct vm_area_struct *vm_next;

pgprot_t vm_page_prot;  /* 线性区中页框的访问许可权 */
unsigned long vm_flags;  /* 线性区的标志 */

struct rb_node vm_rb;           /* 用于红-黑树的数据 */

-----------------------------------



13.申请、释放线性地址区间的过程是?
简答关键词:malloc ,free mmap.(用户级); vmalloc ,slab 核心级
A:
-----------------------------------


14.do_mmap do_munmap  do_mremap实现的过程?
A:不懂do_mremap。
对于do_mmap,do_munmap分别是申请和释放。

这两个操作都是对 线性区的操作。
详细的解释:
李万鹏 http://blog.csdn.net/woshixingaaa/article/details/6856939
csdn yunsongice
http://blog.csdn.net/yunsongice/article/details/5637543
http://blog.csdn.net/yunsongice/article/details/5637554

-----------------------------------


15.什么时候发生缺页,缺页异常处理程序的处理流程是?
A:页表项为空时。

缺页流程很经典:贴上高手解答
http://blog.csdn.net/yunsongice/article/details/5637671
经典的图示:

-----------------------------------

16.如何做到请求调页?(demand paging)
核心思想:页框的分配推迟到不能再推迟为止。lazy mechanism
函数实现:handle_mm_fault
实现过程:
当进程就会存取一些不在物理内存中的虚拟内存页,
这时处理器会通知linux发生了页面错误。
页面错误将会描述页面错误发生时的虚拟内存地址和存取内存操作的类型。

linux首先查找代表发生页面错误的虚拟内存区的vm_area_struct。如果没有代表此出错虚
拟地址的vm_area_struct,就说明进程存取了一个非法的虚拟内存地址。linux系统将向进程发
出sigsegv 信号。

linux下一步检查页面错误的类型是否和此虚拟内存区所允许的操作类型相符。如果不符,
linux系统也将报告内存错误。

如果linux认为此页面错误是合法的,它将处理此页面错误。(!!)
linux还必须区分页面是在交换文件中还是作为文件镜像的一部分存在于磁盘中。它靠检
查出错页面的页面表来区分:
如果页面表的入口是无效的,但非空,说明页面在交换文件中。
最后,linux调入所需的页面并更新进程的页面表

详见:
http://blog.csdn.net/yunsongice/article/details/5637694#comments

17.进程堆管理,函数调用流程?这里如何与页框产生关系的?
简答关键词:malloc,free ; 交互叶匡就是 如何调用到 alloc_pages.

-----------------------------------

18.系统没有空闲内存了会触发什么动作?
A:换页.swap,直到系统空余页数到page_high(上限阀值)


-----------------------------------

19.页框回收的方式有哪些?
简要关键词:free PFRA LRU
A:两种方式,第一种就是主动回收,比如调用free,kmem_cache_free(slab释放);
第二种系统回收,通过linux内核提供的页框回收算法(PFRA)进行回收,
回收时机如:删除文件、进程退出等等
关于PFRA,有以下浅析:
PFRA总会从用户态进内存程空间和页面缓存中,“窃取”页框满足供给。
所谓”窃取”,指的是:将用户进程内存空间对应占用的页框中的数据swap out至磁盘(称为交换区),或者将OS页面缓存中的内存页(还有用户进程mmap()的内存页)flush(同步fsync())至磁盘设备。
详细的页框回收请看:
http://hi.baidu.com/_kouu/blog/item/bdc4172bfb89be27d52af17e.html (作者很多linux专题浅析,大道至简啊)
http://www.perfgeeks.com/?p=770 (这个博主分享了很多 内存学习心得)

-----------------------------------

20.页框回收时,是如何与slab、zone、node关联起来的?如何收缩slab?
ps:这个话题很大,忙活了半个小时理出头绪。
先谈回收与zone:
alloc_pages等分配函数,就是从管理物理页面的伙伴系统
(管理区zone上的free_area空闲链表)上直接分配页面,而回收就是把
不用的页面放回zone上的free_area空闲链表。
回收过程中,磁盘高速缓存的页面(包括文件映射的页面)都是可以被丢弃并回收的。
所有这些磁盘高速缓存页面和匿名映射页面都被放到了一组LRU里面。
(实际上,每个zone就有一组这样的LRU,页面都被放到自己对应的zone的LRU中。

三者如何作用于回收,这是个大话题:
详细看:
http://www.ibm.com/developerworks/cn/linux/l-cn-pagerecycle/
http://blog.csdn.net/bullbat/article/details/7311205 (前者的学习笔记,更加图文并茂)

关于收缩slab:
简要说是 遍历 shrinker 链表,从而对所有注册了 shrinker 函数的磁盘缓存进行处理。

--------------------------------------

21.pageout写回磁盘的流程是?
我理解:pageout就是kswapd(网文称为:pageout daemon)干的活:
详细请看:http://oss.org.cn/kernel-book/ch06/6.6.2.htm

-----------------------------------

22.swap 信息的结构,缺页发生后,如何从磁盘唤回内存的?
A: 不懂swap信息结构,请各位UCer补充。

我理解的换回内存,就是 "请求调页"过程,如上16问。

23.内存管理中涉及到的高速缓存有哪些?
高速缓存有:缓冲区高速缓存,缓冲区高速缓存,交换高速缓存,硬件高速缓存

缓冲区高速缓存
缓冲区高速缓存中保存着块设备驱动程序所用到的数据缓冲区。
这些缓冲区的大小固定,一般包括从块设备中读入的和将要写入到块设备中的信息块。快
设备一次只能处理大小固定的数据块。硬盘就是块设备中的一种。
缓冲区高速缓存使用设备标识符和块号作为索引来快速地查找数据块。块设备只通过缓冲
区高速缓存进行存取。如果所需要的数据存在于缓冲区高速缓存中,那么就不需要从物理块设
备中读取,这样存取的速度就会加快。

缓冲区高速缓存
页面高速缓存用来加速磁盘中文件镜像和数据的存取,我们将在后面的1 0 . 1 2节中对它进
行详细讨论。

交换高速缓存
交换文件中只保存那些被修改过的页面。
只要在页面被写入到交换文件中后没有被修改过,那么此页面下一次从内存中交换出来时
就不用再写入到交换文件中了,因为交换文件中已经有了该页面。这样,该页面就可以简单地
扔掉,节省了大量的系统操作。

硬件高速缓存
一个常用的硬件高速缓存是在处理器中,它一般保存着页面表的入口。

-----------------------------------

24.内存管理中数据结构有哪些?
A:在此理出重要的几个:
overview:
page (物理页面描述)
zone(直接管理page的)
pglist_data(个人认为管理zone的)
free_area_struct(分配、释放处理的对象)
mm_struct 进程所有的虚存空间的数据结构描述
vm_area_struct 进程虚存空间的数据结构描述
...
一个看起来不错的总结在此:
http://blog.csdn.net/zhengtingt108/article/details/4680495
http://blog.sina.com.cn/s/blog_51dc0fba0100spul.html


最后附上好资源:
关于vm的图示网上链接:
http://www.csn.ul.ie/~mel/projects/vm/guide/text/graphs/

小结:在回答 doit_zzg的问题时,
学到很多啊。
同时建议大家逛逛 鹏兄,还有yunsongice(csdn)的博客。这才是深刻的积累啊。

update
作者: doit_zzg    时间: 2012-04-15 07:50

我们实际运行环境,的确也是通过更改 /proc/sys/vm/drop_caches 这个,来到达手动释放的目的。

另外,@titer1,好样的,好好学习,天天向上~




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2