精华内容
下载资源
问答
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm ... Linux内存管理 http://blog.csdn.net/vanbreaker/article/details/75799411 前景回顾前面我们讲到服务器体系(SMP, NUMA, M
    日期内核版本架构作者GitHubCSDN
    2016-09-01Linux-4.7X86 & armgatiemeLinuxDeviceDriversLinux内存管理

    1 前景回顾


    前面我们讲到服务器体系(SMP, NUMA, MPP)与共享存储器架构(UMA和NUMA)

    1.1 UMA和NUMA两种模型


    共享存储型多处理机有两种模型

    • 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型

    • 非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型

    UMA模型

    物理存储器被所有处理机均匀共享。所有处理机对所有存储字具有相同的存取时间,这就是为什么称它为均匀存储器存取的原因。每台处理机可以有私用高速缓存,外围设备也以一定形式共享。

    NUMA模型

    NUMA模式下,处理器被划分成多个”节点”(node), 每个节点被分配有的本地存储器空间。 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多。

    1.2 Linux如何描述物理内存


    Linux把物理内存划分为三个层次来管理

    层次描述
    存储节点(Node)CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点
    管理区(Zone)每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存
    页面(Page)内存被细分为多个页面帧, 页面是最基本的页面分配的单位 |
    • 首先, 内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list链表中<而其中的每个节点利用pg_data_tnode_next字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构.

    • 接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射.

    1.3 典型架构(x86)上内存区域划分


    对于x86机器,管理区(内存区域)类型如下分布

    类型区域
    ZONE_DMA0~15MB
    ZONE_NORMAL16MB~895MB
    ZONE_HIGHMEM896MB~物理内存结束

    而由于32位系统中, Linux内核虚拟地址空间只有1G, 而0~895M这个986MB被用于DMA和直接映射, 剩余的物理内存被成为高端内存. 那内核是如何借助剩余128MB高端内存地址空间是如何实现访问可以所有物理内存?

    当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存

    关于高端内存的内容, 我们后面会专门抽出一章进行讲解

    因此, 传统和X86_32位系统中, 前16M划分给ZONE_DMA, 该区域包含的页框可以由老式的基于ISAS的设备通过DMA使用”直接内存访问(DMA)”, ZONE_DMA和ZONE_NORMAL区域包含了内存的常规页框, 通过把他们线性的映射到现行地址的第4个GB, 内核就可以直接进行访问, 相反ZONE_HIGHME包含的内存页不能由内核直接访问, 尽管他们也线性地映射到了现行地址空间的第4个GB. 在64位体系结构中, 线性地址空间的大小远远好过了系统的实际物理地址, 内核可知直接将所有的物理内存映射到线性地址空间, 因此64位体系结构上ZONE_HIGHMEM区域总是空的.

    2 内核空间和用户空间


    传统32bit cpu上, 最大寻址的物理内存空间是4GByte, 线性地址空间也是4GByte的情况下, linux内存管理会 : 应用程序3G + 内核 1G这样划分

    2.1 把内核空间和用户空间分开


    过去,CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了IO地址空间,实际内存容量小于4G),逻辑空间也只能描述4G的线性地址空间。

    为了合理的利用逻辑4G空间,Linux采用了3:1的策略,即内核占用1G的线性地址空间,用户占用3G的线性地址空间。所以用户进程的地址范围从0~3G,内核地址范围从3G~4G,也就是说,内核空间只有1G的逻辑线性地址空间。

    把内核空间和用户空间分开是方便为了MMU映射
    如果内核空间和用户空间都是0~4G范围的话,
    那么当从用户态切入到内核态时(系统调用或者中断),就必须要切换MMU映射
    (程序里一个逻辑地址在用户态和内核态肯定被映射到不同的物理地址上)
    这种切换代价是很大,但是用户态/内核态的切换却是非常频繁的。
    而且从技术上你根本没法切换,因为这个时候程序内的任何地址都被映射给用户进程,你根本没法取到内核数据。
    就算进入内核态时你切换MMU映射,如果这个时候你要读写用户进程的数据怎么办呢?
    难道又去映射MMU?
    把0~3G分给用户进程、3~4G就能很好的解决这个问题

    这样一来就是你用户程序里的所有变量和函数的 逻辑地址都是介于0~3G的

    内核代码里所有变量和函数的逻辑地址都是介于3~4G的

    所谓用户空间和内核空间,从cpu角度来说,只是运行的级别不一样,是否处于特权级别而已

    逻辑地址到物理地址的MMU映射是不变的。

    不管是用户态还是内核态,最终都是通过MMU映射到你的物理上的896M内存

    mmap就可以映射物理内存到用户空间,你可以清楚的看到同一块物理内在用户态和内核态的不同逻辑地址

    两个不同的逻辑地址(一个介于0~3G,一个介于3-4G) 通过MMU映射到同一块物理内存

    2.2 linux为什么把内核映射到3G-4G这个地址呢


    假如linux把内核映射到0-1G的空间,其他进程共享1-4G的空间不可以吗?

    这个技术上也是可以的,而且不难实现。为什么不采用估计有历史原因吧

    毕竟cpu和程序出来的年代比MMU早多了

    没有MMU的年代里,对于x86,逻辑地址就是物理地址。物理地址从0开始,那么程序的逻辑地址从一开始也是从0开始的

    对于任何用户进程,0~3G的映射都是不同的,但是所有用户进程3~4G的映射都是相同的

    一个进程从用户态切入到内核态,MMU映射不需要变。你能很方便取得内核数据和用户进程的数据

    2.3 应用程序线性地址和动态内存分配


    应用程序能使用的最大线性地址就是3G, 根据linux应用的分区方法:

    ---------------------------- 4G
    
    内核空间
    
    ---------------------------- 3G
    栈 - 向下增长
    
    map - 固定映射
    
    堆 - 向上增长
    
    数据段
    
    代码段
    
    ---------------------------- 0G

    应用层的动态内存分配, 比如malloc, 一般使用的是dlmalloc库, 具体见:http://opendevkit.com/?e=56

    所以, 低端内核和高端内存是内核的概念, 跟应用程序没有直接关系.

    如果Linux物理内存小于1G的空间,通常内核把物理内存与其地址空间做了线性映射,也就是一一映射,这样可以提高访问速度。但是,当Linux物理内存超过1G时,线性访问机制就不够用了,因为只能有1G的内存可以被映射,剩余的物理内存无法被内核管理,所以,为了解决这一问题,Linux把内核地址分为线性区和非线性区两部分,线性区规定最大为896M,剩下的128M为非线性区。从而,线性区映射的物理内存成为低端内存,剩下的物理内存被成为高端内存。与线性区不同,非线性区不会提前进行内存映射,而是在使用时动态映射。

    2.4 高端内存和低端内存的划分


    那么既然内核态的地址范围只有1G的,如果你有4G物理上的内存,显然你没法一次性全部映射所有的物理内存到内核态地址。
    所有才有了高端内存。低896M的内存做直接映射,剩下的128M 根据需要动态的映射到剩下的物理内存。

    64位的内核就可以很好的解决这个问题,但是64位系统不是意味着就有64根地址线。因为没必要,实际不需要那么大内存。
    假设你有38位地址线,可以寻址到2048G的内存,也按照3:1划分,那么内核态就有512G范围,你的512G物理内存可以一次性的全部映射到内核空间,根本不需要高端内存

    Linux物理内存空间分为DMA内存区(DMA Zone)、低端内存区(Normal Zone)与高端内存区(Highmem Zone)三部分。DMA Zone通常很小,只有几十M,低端内存区与高端内存区的划分来源于Linux内核空间大小的限制。

    当物理内存大于1G的时候, 内核是不能完全用低端内存管理全部物理内存的, 所以低端内存剩下的部分就是高端内存了, 高端内存没有直接映射的, 是动态映射到内核空间的.

    一般 128M给高端内存分配用, 因为很少, 所以不用要赶紧释放掉

    64bit的时候, 就不存在低端内存和高端内存的概念了, 因为空间很大都可以直接管理了.

    2.5 例子


    假设物理内存为2G,则低段的896M为低端内存,通过线性映射给内核使用,其他的1128M物理内存为高端内存,可以被内核的非线性区使用。由于要使用128M非线性区来管理超过1G的高端内存,所以通常都不会映射,只有使用时才使kmap映射,使用完后要尽快用kunmap释放。

    对于物理内存为1G的内核,系统不会真的分配896M给线性空间,896M是最大限制

    下面是一个1.5G物理内存linux系统的真实分配情况,只有721M分配给了低端内存区,如果是1G的linxu系统,分配的就更少了。

    区域大小
    MemTotal1547MB
    HighTotal825MB
    LowTotal721MB

    申请高端内存时,如果高端内存不够了,linux也会去低端内存区申请,反之则不行。

    3 Linux内核高端内存的由来


    3.1 为什么需要高端内存?


    高端内存是指物理地址大于 896M 的内存。对于这样的内存,无法在“内核直接映射空间”进行映射。

    内核空间只有1GB线性地址,如果使用大于1GB的物理内存就没法直接映射到内核线性空间了。

    当系统中的内存大于896MB时,把内核线性空间分为两部分,内核中低于896MB线性地址空间直接映射到低896MB的物理地址空间;高于896MB的128MB内核线性空间用于动态映射ZONE_HIGHMEM内存区域(即物理地址高于896MB的物理空间)。

    因为“内核直接映射空间”最多只能从 3G 到 4G,只能直接映射 1G 物理内存,对于大于 1G 的物理内存,无能为力。
    实际上,“内核直接映射空间”也达不到 1G, 还得留点线性空间给“内核动态映射空间” 呢。
    因此,Linux 规定“内核直接映射空间” 最多映射 896M 物理内存。

    对于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。

    3.2 高端内存


    在传统的x86_32系统中, 当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0×3,0xc0000004对应的物理地址为0×4,… …,

    逻辑地址与物理地址对应的关系为

    物理地址 = 逻辑地址 – 0xC0000000

    这是内核地址空间的地址转换关系,注意内核的虚拟地址在“高端”,但是ta映射的物理内存地址在低端。

    逻辑地址    物理内存地址
    0xc0000000  0×0
    0xc0000001  0×1
    0xc0000002  0×2
    0xc0000003  0×3
    …   …
    0xe0000000  0×20000000
    …   …
    0xffffffff  0×40000000 ??

    假设按照上述简单的地址映射关系,那么内核逻辑地址空间访问为0xc0000000 ~ 0xffffffff,那么对应的物理内存范围就为0×0 ~ 0×40000000,即只能访问1G物理内存。若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,因为内核 的地址空间已经全部映射到物理内存地址范围0×0 ~ 0×40000000。即使安装了8G物理内存,那么物理地址为0×40000001的内存,内核该怎么去访问呢?代码中必须要有内存逻辑地址 的,0xc0000000 ~ 0xffffffff的地址空间已经被用完了,所以无法访问物理地址0×40000000以后的内存。

    显 然不能将内核地址空间0xc0000000 ~ 0xfffffff全部用来简单的地址映射。因此x86架构中将内核地址空间划分三部分:ZONE_DMA、ZONE_NORMAL和 ZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。

    在x86结构中,三种类型的区域(从3G开始计算)如下:

    区域位置
    ZONE_DMA内存开始的16MB
    ZONE_NORMAL16MB~896MB
    ZONE_HIGHMEM896MB ~ 结束(1G)

    3.3 Linux内核高端内存的理解


    前 面我们解释了高端内存的由来。 Linux将内核地址空间划分为三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高端内存HIGH_MEM地址空间范围为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是如何借助128MB高端内存地址空间是如何实现访问可以所有物理内存?

    当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。如下图。

    例 如内核想访问2G开始的一段大小为1MB的物理内存,即物理地址范围为0×80000000 ~ 0x800FFFFF。访问之前先找到一段1MB大小的空闲地址空间,假设找到的空闲地址空间为0xF8700000 ~ 0xF87FFFFF,用这1MB的逻辑地址空间映射到物理地址空间0×80000000 ~ 0x800FFFFF的内存。映射关系如下:

    逻辑地址    物理内存地址
    0xF8700000  0×80000000
    0xF8700001  0×80000001
    0xF8700002  0×80000002
    …   …
    0xF87FFFFF  0x800FFFFF

    当内核访问完0×80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF内核线性空间释放。这样其他进程或代码也可以使用0xF8700000 ~ 0xF87FFFFF这段地址访问其他物理内存。

    从上面的描述,我们可以知道高端内存的最基本思想, 借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。

    看到这里,不禁有人会问:万一有内核进程或模块一直占用某段逻辑地址空间不释放,怎么办?若真的出现的这种情况,则内核的高端内存地址空间越来越紧张,若都被占用不释放,则没有建立映射到物理内存都无法访问了。

    4 Linux内核高端内存的划分与映射


    在32位的系统上, 内核占有从第3GB~第4GB的线性地址空间, 共1GB大小

    内核将其中的前896MB与物理内存的0~896MB进行直接映射, 即线性映射, 将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口. 引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线性地址空间腾出来,用以完成对高端内存的暂时性映射。而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。

    下图描述了内核1GB线性地址空间是如何划分的

    内核1GB线性地址空间是如何划分的

    其中可以用来完成上述映射目的的区域为vmalloc area,Persistent kernel mappings区域和固定映射线性地址空间中的FIX_KMAP区域,这三个区域对应的映射机制分别为非连续内存分配,永久内核映射和临时内核映射。

    内核将高端内存划分为3部分:

    • VMALLOC_START~VMALLOC_END

    • KMAP_BASE~FIXADDR_START

    • FIXADDR_START~4G

    对 于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。

    这里写图片描述

    对应高端内存的3部分,高端内存映射有三种方式:

    4.1 映射到”内核动态映射空间”(noncontiguous memory allocation)


    这种方式很简单,因为通过vmalloc() ,在”内核动态映射空间”申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到”内核动态映射空间”中

    4.2 持久内核映射(permanent kernel mapping)


    如果是通过alloc_page获得了高端内存对应的 page, 如何给它找个线性空间?

    内核专门为此留出一块线性空间,从PKMAP_BASE 到 FIXADDR_START, 用于映射高端内存

    在2.6内核上, 这个地址范围是4G-8M到4G-4M之间. 这个空间起叫”内核永久映射空间”或者”永久内核映射空间”, 这个空间和其它空间使用同样的页目录表,对于内核来说,就是 swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(),可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。

    4.3 临时映射(temporary kernel mapping)


    内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的临时映射。

    这块空间具有如下特点:

    1. 每个 CPU 占用一块空间

    2. 在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。

    当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。通过 kmap_atomic() 可实现临时映射。

    5 页框管理


    5.1 页框管理


    Linux采用4KB页框大小作为标准的内存分配单元。内核必须记录每个页框的状态,这种状态信息保存在一个类型为page的页描述符中,所有的页描述存放在mem_map中

    virt_to_page(addr)产生线性地址对应的页描述符地址。pfn_to_page(pfn)产生对应页框号的页描述符地址。

    在页框描述符中,几个关键的字段我认为:flags、_count、_mapcount。
    由于CPU对内存的非一致性访问,系统的物理内存被划分为几个节点(每个节点的描述符为pg_data_t),每个节点的物理内存又可以分为3个管理区:ZONE_DMA(低于16M的页框地址),ZONE_NORMAL(16MB-896MB的页框地址)和ZONE_HIGHMEM(高于896MB的页框地址)。

    每个管理区又有自己的描述符,描述了该管理区空闲的页框,保留页数目等。每个页描述符都有到内存节点和到节点管理区的连接(被放在flag的高位字段)。
    内核调用一个内存分配函数时,必须指明请求页框所在的管理区,内核通常指明它愿意使用哪个管理区。

    5.2 保留的页框池


    如果有足够的空闲内存可用、请求就会被立刻满足。否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被释放。但是有些控制路径不能被阻塞,例如一些内核路径产生一些原子内存分配请求。尽管无法保证一个原子内存分配请求不失败,但是内核会减少这中概率。为了做到如此,内核采取的方案为原子内存分配请求保留一个页框池,只有在内存不足时才使用。页框池有ZONE_DMA和ZONE_NORMAL两个区贡献出一些页框。
    常用的请求页框和释放页框函数:
    alloc_pages(gfp_mask, order): 获得连续的页框,返回页描述符地址,是其他类型内存分配的基础。
    __get_free_pages(gfp_mask, order): 获得连续的页框,返回页框对应的线性地址。线性地址与物理地址是内核直接映射方式。不能用于大于896M的高端内存。
    __free_pages(page,order);
    __free_pages(addr,order);

    6 常见问题


    6.1 用户空间(进程)是否有高端内存概念?


    用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。

    6.2 64位内核中有高端内存吗?


    目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。

    6.3 用户进程能访问多少物理内存?内核代码能访问多少物理内存?


    32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。

    64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。

    6.4 高端内存和物理地址、逻辑地址、线性地址的关系?


    高端内存只和逻辑地址有关系,和逻辑地址、物理地址没有直接关系。

    6.5 为什么不把所有的地址空间都分配给内核?


    若把所有地址空间都给内存,那么用户进程怎么使用内存?怎么保证内核使用内存和用户进程不起冲突?

    1. 让我们忽略Linux对段式内存映射的支持。 在保护模式下,我们知道无论CPU运行于用户态还是核心态,CPU执行程序所访问的地址都是虚拟地址,MMU 必须通过读取控制寄存器CR3中的值作为当前页面目录的指针,进而根据分页内存映射机制(参看相关文档)将该虚拟地址转换为真正的物理地址才能让CPU真 正的访问到物理地址。

    2. 对于32位的Linux,其每一个进程都有4G的寻址空间,但当一个进程访问其虚拟内存空间中的某个地址时又是怎样实现不与其它进程的虚拟空间混淆 的呢?每个进程都有其自身的页面目录PGD,Linux将该目录的指针存放在与进程对应的内存结构task_struct.(struct mm_struct)mm->pgd中。每当一个进程被调度(schedule())即将进入运行态时,Linux内核都要用该进程的PGD指针设 置CR3(switch_mm())。

    3. 当创建一个新的进程时,都要为新进程创建一个新的页面目录PGD,并从内核的页面目录swapper_pg_dir中复制内核区间页面目录项至新建进程页面目录PGD的相应位置,具体过程如下:
      do_fork() –> copy_mm() –> mm_init() –> pgd_alloc() –> set_pgd_fast() –> get_pgd_slow() –> memcpy(&PGD + USER_PTRS_PER_PGD, swapper_pg_dir + USER_PTRS_PER_PGD, (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t))
      这样一来,每个进程的页面目录就分成了两部分,第一部分为“用户空间”,用来映射其整个进程空间(0x0000 0000-0xBFFF FFFF)即3G字节的虚拟地址;第二部分为“系统空间”,用来映射(0xC000 0000-0xFFFF FFFF)1G字节的虚拟地址。可以看出Linux系统中每个进程的页面目录的第二部分是相同的,所以从进程的角度来看,每个进程有4G字节的虚拟空间, 较低的3G字节是自己的用户空间,最高的1G字节则为与所有进程以及内核共享的系统空间。

    4. 现在假设我们有如下一个情景:

    在进程A中通过系统调用sethostname(const char *name,seze_t len)设置计算机在网络中的“主机名”.
    在该情景中我们势必涉及到从用户空间向内核空间传递数据的问题,name是用户空间中的地址,它要通过系统调用设置到内核中的某个地址中。让我们看看这个 过程中的一些细节问题:系统调用的具体实现是将系统调用的参数依次存入寄存器ebx,ecx,edx,esi,edi(最多5个参数,该情景有两个 name和len),接着将系统调用号存入寄存器eax,然后通过中断指令“int 80”使进程A进入系统空间。由于进程的CPU运行级别小于等于为系统调用设置的陷阱门的准入级别3,所以可以畅通无阻的进入系统空间去执行为int 80设置的函数指针system_call()。由于system_call()属于内核空间,其运行级别DPL为0,CPU要将堆栈切换到内核堆栈,即 进程A的系统空间堆栈。我们知道内核为新建进程创建task_struct结构时,共分配了两个连续的页面,即8K的大小,并将底部约1k的大小用于 task_struct(如#define alloc_task_struct() ((struct task_struct ) __get_free_pages(GFP_KERNEL,1))),而其余部分内存用于系统空间的堆栈空间,即当从用户空间转入系统空间时,堆栈指针 esp变成了(alloc_task_struct()+8192),这也是为什么系统空间通常用宏定义current(参看其实现)获取当前进程的 task_struct地址的原因。每次在进程从用户空间进入系统空间之初,系统堆栈就已经被依次压入用户堆栈SS、用户堆栈指针ESP、EFLAGS、 用户空间CS、EIP,接着system_call()将eax压入,再接着调用SAVE_ALL依次压入ES、DS、EAX、EBP、EDI、ESI、 EDX、ECX、EBX,然后调用sys_call_table+4%EAX,本情景为sys_sethostname()。

    1. 在sys_sethostname()中,经过一些保护考虑后,调用copy_from_user(to,from,n),其中to指向内核空间 system_utsname.nodename,譬如0xE625A000,from指向用户空间譬如0x8010FE00。现在进程A进入了内核,在 系统空间中运行,MMU根据其PGD将虚拟地址完成到物理地址的映射,最终完成从用户空间到系统空间数据的复制。准备复制之前内核先要确定用户空间地址和 长度的合法性,至于从该用户空间地址开始的某个长度的整个区间是否已经映射并不去检查,如果区间内某个地址未映射或读写权限等问题出现时,则视为坏地址, 就产生一个页面异常,让页面异常服务程序处理。过程如 下:copy_from_user()->generic_copy_from_user()->access_ok()+__copy_user_zeroing().

    7 小结


    • 进程寻址空间0~4G

    • 进程在用户态只能访问0~3G,只有进入内核态才能访问3G~4G

    • 进程通过系统调用进入内核态

    • 每个进程虚拟空间的3G~4G部分是相同的

    • 进程从用户态进入内核态不会引起CR3的改变但会引起堆栈的改变

    Linux 简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的 1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统 内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

    Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。从图中可以看出(这里无法表示图),每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

    7.1 虚拟内核空间到物理空间的映射


    内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。读者会问,系 统启动时,内核的代码和数据不是被装入到物理内存吗?它们为什么也处于虚拟内存中呢?这和编译程序有关,后面我们通过具体讨论就会明白这一点。
    虽 然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始。对内核空间来说,其地址映射是很简单 的线性映射,0xC0000000就是物理地址与线性地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET。

    我们来看一下在include/asm/i386/page.h中对内核空间中地址映射的说明及定义:

    /*
    * This handles the memory map.. We could make this a config
    * option, but too many people screw it up, and too few need
    * it.
    *
    * A __PAGE_OFFSET of 0xC0000000 means that the kernel has
    * a virtual address space of one gigabyte, which limits the
    * amount of physical memory you can use to about 950MB. 
    *
    * If you want more physical memory than this then see the CONFIG_HIGHMEM4G
    * and CONFIG_HIGHMEM64G options in the kernel configuration.
    */
    
    #define __PAGE_OFFSET           (0xC0000000)
    ……
    #define PAGE_OFFSET             ((unsigned long)__PAGE_OFFSET)
    #define __pa(x)                 ((unsigned long)(x)-PAGE_OFFSET)
    #define __va(x)                 ((void *)((unsigned long)(x)+PAGE_OFFSET))

    如果你的物理内存大于950MB,那么在编译内核时就需要加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G选 项,这种情况我们暂不考虑。如果物理内存小于950MB,则对于内核空间而言,给定一个虚地址x,其物理地址为“x- PAGE_OFFSET”,给定一个物理地址x,其虚地址为“x+ PAGE_OFFSET”。
    这里再次说明,宏__pa()仅仅把一个内核空间的虚地址映射到物理地址,而决不适用于用户空间,用户空间的地址映射要复杂得多。

    7.2 内核映像


    在下面的描述中,我们把内核的代码和数据就叫内核映像(kernel image)。当系统启动时,Linux内核映像被安装在物理地址0x00100000开始的地方,即1MB开始的区间(第1M留作它用)。然而,在正常 运行时, 整个内核映像应该在虚拟内核空间中,因此,连接程序在连接内核映像时,在所有的符号地址上加一个偏移量PAGE_OFFSET,这样,内核映像在内核空间 的起始地址就为0xC0100000。
    例如,进程的页目录PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器CR3设置成指 向新进程的页目录PGD,而该目录的起始地址在内核空间中是虚地址,但CR3所需要的是物理地址,这时候就要用__pa()进行地址转换。在 mm_context.h中就有这么一行语句:
    asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
    这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址next_pgd,通过__pa()转换成物理地址,存放在某个寄存器中,然后用mov指令将其写入CR3寄存器中。经过这行语句的处理,CR3就指向新进程next的页目录表PGD了。

    7.3 高端内存映射方式:


    高端内存映射有三种方式:

    1. 映射到“内核动态映射空间”

    这种方式很简单,因为通过 vmalloc() ,在“内核动态映射空间”申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到“内核动态映射空间” 中。

    1. 永久内核映射

    如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?

    内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.4 内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫“内核永久映射空间”或者“永久内核映射空间”

    这个空间和其它空间使用同样的页目录表,对于内核来说,就是 swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。

    通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。

    通过 kmap(), 可以把一个 page 映射到这个空间来

    由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,应该及时从这个空间释放掉(也除映射关就是解系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。

    1. 临时映射

    内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间”

    这个空间中,有一部分用于高端内存的临时映射。

    这块空间具有如下特点:

    1. 每个 CPU 占用一块空间

    2. 在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。

    当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。

    通过 kmap_atomic() 可实现临时映射。

      下图简单简单表达如何对高端内存进行映射
    

    !对高端内存进行映射

    Linux内存线性地址空间大小为4GB,分为2个部分:用户空间部分(通常是3G)和内核空间部分(通常是1G)。在此我们主要关注内核地址空间部分。
    内核通过内核页全局目录来管理所有的物理内存,由于线性地址前3G空间为用户使用,内核页全局目录前768项(刚好3G)除0、1两项外全部为0,后256项(1G)用来管理所有的物理内存。内核页全局目录在编译时静态地定义为swapper_pg_dir数组,该数组从物理内存地址0x101000处开始存放。

    由图可见,内核线性地址空间部分从PAGE_OFFSET(通常定义为3G)开始,为了将内核装入内存,从PAGE_OFFSET开始8M线性地址用来映射内核所在的物理内存地址(也可以说是内核所在虚拟地址是从PAGE_OFFSET开始的);接下来是mem_map数组,mem_map的起始线性地址与体系结构相关,比如对于UMA结构,由于从PAGE_OFFSET开始16M线性地址空间对应的16M物理地址空间是DMA区,mem_map数组通常开始于PAGE_OFFSET+16M的线性地址;从PAGE_OFFSET开始到VMALLOC_START – VMALLOC_OFFSET的线性地址空间直接映射到物理内存空间(一一对应影射,物理地址<==>线性地址-PAGE_OFFSET),这段区域的大小和机器实际拥有的物理内存大小有关,这儿VMALLOC_OFFSET在X86上为8M,主要用来防止越界错误;在内存比较小的系统上,余下的线性地址空间(还要再减去空白区即VMALLOC_OFFSET)被vmalloc()函数用来把不连续的物理地址空间映射到连续的线性地址空间上,在内存比较大的系统上,vmalloc()使用从VMALLOC_START到VMALLOC_END(也即PKMAP_BASE减去2页的空白页大小PAGE_SIZE(解释VMALLOC_END))的线性地址空间,此时余下的线性地址空间(还要再减去2页的空白区即VMALLOC_OFFSET)又可以分成2部分:第一部分从PKMAP_BASE到FIXADDR_START用来由kmap()函数来建立永久映射高端内存;第二部分,从FIXADDR_START到FIXADDR_TOP,这是一个固定大小的临时映射线性地址空间,(引用:Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time such as the APIC),在X86体系结构上,FIXADDR_TOP被静态定义为0xFFFFE000,此时这个固定大小空间结束于整个线性地址空间最后4K前面,该固定大小空间大小是在编译时计算出来并存储在__FIXADDR_SIZE变量中。

    正是由于vmalloc()使用区、kmap()使用区及固定大小区(kmap_atomic()使用区)的存在才使ZONE_NORMAL区大小受到限制,由于内核在运行时需要这些函数,因此在线性地址空间中至少要VMALLOC_RESERVE大小的空间。VMALLOC_RESERVE的大小与体系结构相关,在X86上,VMALLOC_RESERVE定义为128M,这就是为什么ZONE_NORMAL大小通常是16M到896M的原因。

    展开全文
  • linux内存管理理解之释放linux内存

    千次阅读 2015-11-21 16:24:47
    总有很多朋友对于Linux内存管理有疑问,之前一篇[转]理解Linux的性能日志似乎也没能清除大家的疑虑。而在新版核心中,似乎对这个问题提供了新的解决方法,特转出来给大家参考一下。最后,还附上我对这方法的意见,...

         总有很多朋友对于Linux的内存管理有疑问,之前一篇[转]理解Linux的性能日志似乎也没能清除大家的疑虑。而在新版核心中,似乎对这个问题提供了新的解决方法,特转出来给大家参考一下。最后,还附上我对这方法的意见,欢迎各位一同讨论。
         当在Linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching。这个问题,貌似有不少人在问,不过都没有看到有什么很好解决的办法。那么我来谈谈这个问题。


    一、通常情况

    先来说说free命令:

    引用
    [root@server ~]# free -m
    total used free shared buffers cached
    Mem: 249 163 86 0 10 94
    -/+ buffers/cache: 58 191
    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


    有了这个基础后,可以得知,我现在used为163MB,free为86MB,buffer和cached分别为10MB,94MB。

    那么我们来看看,如果我执行复制文件,内存会发生什么变化.


    引用
    [root@server ~]# cp -r /etc ~/test/
    [root@server ~]# free -m
    total used free shared buffers cached
    Mem: 249 244 4 0 8 174
    -/+ buffers/cache: 62 187
    Swap: 511 0 511

    在我命令执行结束后,used为244MB,free为4MB,buffers为8MB,cached为174MB,天呐,都被cached吃掉了。别紧张,这是为了提高文件读取效率的做法。


    为了提高磁盘存取效率,Linux做了一些精心的设计,除了对dentry进行缓存(用于VFS,加速文件路径名到inode的转换),还采取了两种主要 Cache方式:Buffer Cache和Page Cache。前者针对磁盘块的读写,后者针对文件inode的读写。这些Cache有效缩短了 I/O系统调用(比如read,write,getdents)的时间。


    那么有人说过段时间,linux会自动释放掉所用的内存。等待一段时间后,我们使用free再来试试,看看是否有释放?


    引用
    [root@server test]# free -m
    total used free shared buffers cached
    Mem: 249 244 5 0 8 174
    -/+ buffers/cache: 61 188
    Swap: 511 0 511

    似乎没有任何变化。(实际情况下,内存的管理还与Swap有关)

    那么我能否手动释放掉这些内存呢?回答是可以的!


    二、手动释放缓存

    /proc是一个虚拟文件系统,我们可以通过对它的读写操作做为与kernel实体间进行通信的一种手段。也就是说可以通过修改/proc中的文件,来对 当前kernel的行为做出调整。那么我们可以通过调整/proc/sys/vm/drop_caches来释放内存。操作如下:


    引用
    [root@server test]# cat /proc/sys/vm/drop_caches
    0

    首先,/proc/sys/vm/drop_caches的值,默认为0。


    引用
    [root@server test]# sync

    手动执行sync命令(描述:sync 命令运行 sync 子例程。如果必须停止系统,则运行sync 命令以确保文件系统的完整性。sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件)


    引用
    [root@server test]# echo 3 > /proc/sys/vm/drop_caches
    [root@server test]# cat /proc/sys/vm/drop_caches
    3

    将/proc/sys/vm/drop_caches值设为3


    引用
    [root@server test]# free -m
    total used free shared buffers cached
    Mem: 249 66 182 0 0 11
    -/+ buffers/cache: 55 194
    Swap: 511 0 511

    再来运行free命令,会发现现在的used为66MB,free为182MB,buffers为0MB,cached为11MB。那么有效的释放了buffer和cache。


    ◎ 有关/proc/sys/vm/drop_caches的用法在下面进行了说明


    引用
    /proc/sys/vm/drop_caches  (since Linux 2.6.16)
    Writing to this file causes the kernel to drop clean caches,
    dentries and inodes from memory, causing that memory to become
    free.

    To free pagecache, use echo 1 > /proc/sys/vm/drop_caches; to
    free dentries and inodes, use echo 2 > /proc/sys/vm/drop_caches;
    to free pagecache, dentries and inodes, use echo 3 >
    /proc/sys/vm/drop_caches.

    Because this is a non-destructive operation and dirty objects
    are not freeable, the user should run sync first.

    三、我的意见

    上述文章就长期以来很多用户对Linux内存管理方面的疑问,给出了一个比较“直观”的回复,我更觉得有点像是核心开发小组的妥协。


    对于是否需要使用这个值,或向用户提及这个值,我是有保留意见的:


    引用
    1、从man可以看到,这值从2.6.16以后的核心版本才提供,也就是老版的操作系统,如红旗DC 5.0、RHEL 4.x之前的版本都没有;
    2、若对于系统内存是否够用的观察,我还是原意去看swap的使用率和si/so两个值的大小;

    用户常见的疑问是,为什么free这么小,是否关闭应用后内存没有释放?


    但实际上,我们都知道这是因为Linux对内存的管理与Windows不同,free小并不是说内存不够用了,应该看的是free的第二行最后一个值:


    引用
    -/+ buffers/cache: 58  191

    这才是系统可用的内存大小。


    实际项目中告诉我们,如果因为是应用有像内存泄露、溢出的问题,从swap的使用情况是可以比较快速可以判断的,但free上面反而比较难查看。


    相反,如果在这个时候,我们告诉用户,修改系统的一个值,“可以”释放内存,free就大了。用户会怎么想?不会觉得操作系统“有问题”吗?


    所以说,我觉得既然核心是可以快速清空buffer或cache,也不难做到(这从上面的操作中可以明显看到),但核心并没有这样做(默认值是0),我们就不应该随便去改变它。


    一般情况下,应用在系统上稳定运行了,free值也会保持在一个稳定值的,虽然看上去可能比较小。

    展开全文
  • Linux技术——linux下查看内存和CPU的使用情况

    一、查看内存

    1、命令free常用来查看内存的使用情况

    输入:free

    输出:

                 total       used       free     shared    buffers     cached
    Mem:       3598396    1490744    2107652       6464      87024     722192
    -/+ buffers/cache:     681528    2916868
    Swap:      2095100          0    2095100
    total:物理内存的总大小
    used:已经使用的大小
    free:剩余的大小
    shared:多个进程之间共享的内存大小
    buffers/cached:磁盘缓存的大小
    Swap:交换空间的大小和使用情况(当前内存还有很多剩余,没有用到swap空间)
    备注:
    第二行的已使用1490744大小是操作系统使用的内存大小,其中包括buffers(87024)和cached(722192)。1490744=操作系统使用的内存+应用程序使用的内存+buffers+cached
    第三行的-/+ buffers/cache是从应用程序的角度来看,应用程序可以使用的内存大小是2916868,2916868包括磁盘缓冲在内存上的空间。因为当应用程序需要内存的时候操作系统会将内存上磁盘缓存的空间回收以供应用程序使用。2916868 = free(2107652) + buffers(87024) + cached(722192)

    2、还可以查看/proc/meminfo文件中记录更详细的内存信息

    输入:cat /proc/meminfo
    输出:

    MemTotal:        3598396 kB
    MemFree:         2121804 kB
    Buffers:           87116 kB
    Cached:           722244 kB
    SwapCached:            0 kB
    Active:           899664 kB
    Inactive:         415884 kB
    Active(anon):     506916 kB
    Inactive(anon):     5732 kB
    Active(file):     392748 kB
    Inactive(file):   410152 kB
    Unevictable:          32 kB
    Mlocked:              32 kB
    SwapTotal:       2095100 kB
    SwapFree:        2095100 kB
    Dirty:                 0 kB
    Writeback:             0 kB
    AnonPages:        506216 kB
    Mapped:           104192 kB
    Shmem:              6464 kB
    Slab:              95644 kB
    SReclaimable:      76152 kB
    SUnreclaim:        19492 kB
    KernelStack:        3104 kB
    PageTables:        25168 kB
    NFS_Unstable:          0 kB
    Bounce:                0 kB
    WritebackTmp:          0 kB
    CommitLimit:     3894296 kB
    Committed_AS:    3188768 kB
    VmallocTotal:   34359738367 kB
    VmallocUsed:       39156 kB
    VmallocChunk:   34359694855 kB
    HardwareCorrupted:     0 kB
    AnonHugePages:    256000 kB
    HugePages_Total:       0
    HugePages_Free:        0
    HugePages_Rsvd:        0
    HugePages_Surp:        0
    Hugepagesize:       2048 kB
    DirectMap4k:       42944 kB
    DirectMap2M:     3627008 kB

    3、下面是free的一些常见的参数:

    Usage:
     free [options]
    
    Options:
     -b, --bytes         show output in bytes
     -k, --kilo          show output in kilobytes
     -m, --mega          show output in megabytes
     -g, --giga          show output in gigabytes
         --tera          show output in terabytes
     -h, --human         show human-readable output
         --si            use powers of 1000 not 1024
     -l, --lohi          show detailed low and high memory statistics
     -o, --old           use old format (without -/+buffers/cache line)
     -t, --total         show total for RAM + swap
     -s N, --seconds N   repeat printing every N seconds
     -c N, --count N     repeat printing N times, then exit
    
         --help     display this help and exit
     -V, --version  output version information and exit

    二、查看CPU

    1、命令1:lscpu 用来查看简单cpu信息(包括大小端信息)

    输入:lscpu
    输出:

    Architecture:          x86_64
    CPU 运行模式:    32-bit, 64-bit
    Byte Order:            Little Endian
    CPU(s):                1
    On-line CPU(s) list:   0
    每个核的线程数:1
    每个座的核数:  1
    Socket(s):             1
    NUMA 节点:         1
    厂商 ID:           GenuineIntel
    CPU 系列:          6
    型号:              58
    步进:              9
    CPU MHz:             0.000
    BogoMIPS:              6967.29
    L1d 缓存:          32K
    L1d 缓存:          32K
    L2d 缓存:          6144K

    2、还可以查看/proc/cpuinfo文件中CPU的信息

    输入:cat /proc/cpuinfo 
    输出:
    processor       : 0
    vendor_id       : GenuineIntel
    cpu family      : 6
    model           : 58
    model name      : Intel(R) Core(TM) i5-3450 CPU @ 3.10GHz
    stepping        : 9
    microcode       : 0x19
    cpu MHz         : 0.000
    cache size      : 6144 KB
    physical id     : 0
    siblings        : 1
    core id         : 0
    cpu cores       : 1
    apicid          : 0
    initial apicid  : 0
    fpu             : yes
    fpu_exception   : yes
    cpuid level     : 5
    wp              : yes
    flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl pni monitor ssse3 lahf_lm
    bogomips        : 6967.29
    clflush size    : 64
    cache_alignment : 64
    address sizes   : 36 bits physical, 48 bits virtual
    power management:

    三、top命令详解

    输入:top

    输出:


    第一行:

    15:39:41 当前系统时间
    9:05 系统已经运行了9小时5分钟(在这期间没有重启过)
    4 users 当前有4个用户登录系统(每一个终端登陆该系统就相当于是一个用户)
    load average: 0.00, 0.01, 0.05 load average后面的三个数分别是1分钟、5分钟、15分钟的负载情况。
    load average数据是每隔5秒钟检查一次活跃的进程数,然后按特定算法计算出的数值。如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。 

    第二行:

    Tasks 任务(进程),系统现在共有166个进程,其中处于运行中的有2个,164个在休眠(sleep),stoped状态的有0个,zombie状态(僵尸)的有0个。 

    第三行:cpu状态

    0.0% us 用户空间占用CPU的百分比。
    0.0% sy 内核空间占用CPU的百分比。
    0.0% ni 改变过优先级的进程占用CPU的百分比
    100.0% id 空闲CPU百分比
    0.0% wa IO等待占用CPU的百分比
    0.0% hi 硬中断(Hardware IRQ)占用CPU的百分比
    0.0% si 软中断(Software Interrupts)占用CPU的百分比

    第四行:内存状态

    3598396k total 物理内存总量
    1490564k used 使用中的内存总量
    2107832k free 空闲内存总量
    87244k buffers 缓存的内存量

    第五行:swap交换分区

    2095100k total 交换区总量
    0k used 使用的交换区总量
    2095100k free 空闲交换区总量
    722252k cached 缓冲的交换区总量


    参考:

    http://www.cnblogs.com/xd502djj/archive/2011/03/01/1968041.html


    展开全文
  • Linux内存管理

    千次阅读 2013-10-29 15:00:34
    Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux内存管理与...

    Linux内存管理

    摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux的内存管理与使用。在本章最后,我们给出一个内存映射的实例,帮助网友们理解内核内存管理与用户内存管理之间的关系,希望大家最终能驾驭Linux内存管理。

    前言

    内存管理一向是所有操作系统书籍不惜笔墨重点讨论的内容,无论市面上或是网上都充斥着大量涉及内存管理的教材和资料。因此,我们这里所要写的Linux内存管理采取避重就轻的策略,从理论层面就不去班门弄斧,贻笑大方了。我们最想做的和可能做到的是从开发者的角度谈谈对内存管理的理解,最终目的是把我们在内核开发中使用内存的经验和对Linux内存管理的认识与大家共享。

    当然,这其中我们也会涉及到一些诸如段页等内存管理的基本理论,但我们的目的不是为了强调理论,而是为了指导理解开发中的实践,所以仅仅点到为止,不做深究。

    遵循“理论来源于实践”的“教条”,我们先不必一下子就钻入内核里去看系统内存到底是如何管理,那样往往会让你陷入似懂非懂的窘境(我当年就犯了这个错误!)。所以最好的方式是先从外部(用户编程范畴)来观察进程如何使用内存,等到大家对内存的使用有了较直观的认识后,再深入到内核中去学习内存如何被管理等理论知识。最后再通过一个实例编程将所讲内容融会贯通。

    进程与内存

    进程如何使用内存?

    毫无疑问,所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。

    对任何一个普通进程来讲,它都会涉及到5种不同的数据段。稍有编程知识的朋友都能想到这几个数据段中包含有“程序代码段”、“程序数据段”、“程序堆栈段”等。不错,这几种数据段都在其中,但除了以上几种数据段之外,进程还另外包含两种数据段。下面我们来简单归纳一下进程对应的内存空间中所包含的5种不同的数据区。

    代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

    数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配[1]的变量和全局变量。

    BSS段[2]:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。

    堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

    :栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

    进程如何组织这些区域?

    上述几种内存区域中数据段、BSS和堆通常是被连续存储的——内存位置上是连续的,而代码段和栈往往会被独立存放。有趣的是,堆和栈两个区域关系很“暧昧”,他们一个向下“长”(i386体系结构中栈向下、堆向上),一个向上“长”,相对而生。但你不必担心他们会碰头,因为他们之间间隔很大(到底大到多少,你可以从下面的例子程序计算一下),绝少有机会能碰到一起。

    下图简要描述了进程内存区域的分布:

    “事实胜于雄辩”,我们用一个小例子(原形取自《User-Level Memory Management》)来展示上面所讲的各种内存区的差别与位置。

    #include<stdio.h>

    #include<malloc.h>

    #include<unistd.h>

    int bss_var;

    int data_var0=1;

    int main(int argc,char **argv)

    {

      printf("below are addresses of types of process's mem\n");

      printf("Text location:\n");

      printf("\tAddress of main(Code Segment):%p\n",main);

      printf("____________________________\n");

      int stack_var0=2;

      printf("Stack Location:\n");

      printf("\tInitial end of stack:%p\n",&stack_var0);

      int stack_var1=3;

      printf("\tnew end of stack:%p\n",&stack_var1);

      printf("____________________________\n");

      printf("Data Location:\n");

      printf("\tAddress of data_var(Data Segment):%p\n",&data_var0);

      static int data_var1=4;

      printf("\tNew end of data_var(Data Segment):%p\n",&data_var1);

      printf("____________________________\n");

      printf("BSS Location:\n");

      printf("\tAddress of bss_var:%p\n",&bss_var);

      printf("____________________________\n");

      char *b = sbrk((ptrdiff_t)0);

      printf("Heap Location:\n");

      printf("\tInitial end of heap:%p\n",b);

      brk(b+4);

      b=sbrk((ptrdiff_t)0);

      printf("\tNew end of heap:%p\n",b);

    return 0;

     }

    它的结果如下

    below are addresses of types of process's mem

    Text location:

       Address of main(Code Segment):0x8048388

    ____________________________

    Stack Location:

       Initial end of stack:0xbffffab4

       new end of stack:0xbffffab0

    ____________________________

    Data Location:

       Address of data_var(Data Segment):0x8049758

       New end of data_var(Data Segment):0x804975c

    ____________________________

    BSS Location:

       Address of bss_var:0x8049864

    ____________________________

    Heap Location:

       Initial end of heap:0x8049868

       New end of heap:0x804986c

    利用size命令也可以看到程序的各段大小,比如执行size example会得到

    text data bss dec hex filename

    1654 280   8 1942 796 example

    但这些数据是程序编译的静态统计,而上面显示的是进程运行时的动态值,但两者是对应的。

    通过前面的例子,我们对进程使用的逻辑内存分布已先睹为快。这部分我们就继续进入操作系统内核看看,进程对内存具体是如何进行分配和管理的。

    从用户向内核看,所使用的内存表象形式会依次经历“逻辑地址”——“线性地址”——“物理地址”几种形式(关于几种地址的解释在前面已经讲述了)。逻辑地址经段机制转化成线性地址;线性地址又经过页机制转化为物理地址。(但是我们要知道Linux系统虽然保留了段机制,但是将所有程序的段地址都定死为0-4G,所以虽然逻辑地址和线性地址是两种不同的地址空间,但在Linux中逻辑地址就等于线性地址,它们的值是一样的)。沿着这条线索,我们所研究的主要问题也就集中在下面几个问题。

    1.     进程空间地址如何管理?

    2.     进程地址如何映射到物理内存?

    3.     物理内存如何被管理?

    以及由上述问题引发的一些子问题。如系统虚拟地址分布;内存分配接口;连续内存分配与非连续内存分配等。

     

    进程内存空间

    Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G的线性虚拟空间,用户所看到和接触到的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理内存),而且更重要的是,用户程序可使用比实际物理内存更大的地址空间(具体的原因请看硬件基础部分)。

    在讨论进程空间细节前,这里先要澄清下面几个问题:

    l         第一、4G的进程地址空间被人为的分为两个部分——用户空间与内核空间。用户空间从0到3G(0xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。

    l         第二、用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表(init_mm.pgd),用户进程各自有不同的页表。

    l         第三、每个进程的用户空间都是完全独立、互不相干的。不信的话,你可以把上面的程序同时运行10次(当然为了同时运行,让它们在返回前一同睡眠100秒吧),你会看到10个进程占用的线性地址一模一样。

     

    进程内存管理

    进程内存管理的对象是进程线性地址空间上的内存镜像,这些内存镜像其实就是进程使用的虚拟内存区域(memory region)。进程虚拟空间是个32或64位的“平坦”(独立的连续区间)地址空间(空间的具体大小取决于体系结构)。要统一管理这么大的平坦空间可绝非易事,为了方便管理,虚拟空间被划分为许多大小可变的(但必须是4096的倍数)内存区域,这些区域在进程线性地址中像停车位一样有序排列。这些区域的划分原则是“将访问属性一致的地址空间存放在一起”,所谓访问属性在这里无非指的是“可读、可写、可执行等”。

    如果你要查看某个进程占用的内存区域,可以使用命令cat /proc/<pid>/maps获得(pid是进程号,你可以运行上面我们给出的例子——./example &;pid便会打印到屏幕),你可以发现很多类似于下面的数字信息。

    由于程序example使用了动态库,所以除了example本身使用的的内存区域外,还会包含那些动态库使用的内存区域(区域顺序是:代码段、数据段、bss段)。

    我们下面只抽出和example有关的信息,除了前两行代表的代码段和数据段外,最后一行是进程使用的栈空间。

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

    08048000 - 08049000 r-xp 00000000 03:03 439029                               /home/mm/src/example

    08049000 - 0804a000 rw-p 00000000 03:03 439029                               /home/mm/src/example

    ……………

    bfffe000 - c0000000 rwxp ffff000 00:00 0

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

    每行数据格式如下:

    (内存区域)开始-结束 访问权限  偏移  主设备号:次设备号 i节点  文件。

    注意,你一定会发现进程空间只包含三个内存区域,似乎没有上面所提到的堆、bss等,其实并非如此,程序内存段和进程地址空间中的内存区域是种模糊对应,也就是说,堆、bss、数据段(初始化过的)都在进程空间中由数据段内存区域表示。

    在Linux内核中对应进程内存区域的数据结构是: vm_area_struct, 内核将每个内存区域作为一个单独的内存对象管理,相应的操作也都一致。采用面向对象方法使VMA结构体可以代表多种类型的内存区域--比如内存映射文件或进程的用户空间栈等,对这些区域的操作也都不尽相同。

    vm_area_strcut结构比较复杂,关于它的详细结构请参阅相关资料。我们这里只对它的组织方法做一点补充说明。vm_area_struct是描述进程地址空间的基本管理单元,对于一个进程来说往往需要多个内存区域来描述它的虚拟空间,如何关联这些不同的内存区域呢?大家可能都会想到使用链表,的确vm_area_struct结构确实是以链表形式链接,不过为了方便查找,内核又以红黑树(以前的内核使用平衡树)的形式组织内存区域,以便降低搜索耗时。并存的两种组织形式,并非冗余:链表用于需要遍历全部节点的时候用,而红黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

    下图反映了进程地址空间的管理模型:

    进程的地址空间对应的描述结构是“内存描述符结构”,它表示进程的全部地址空间,——包含了和进程地址空间有关的全部信息,其中当然包含进程的内存区域。

    进程内存的分配与回收

    创建进程fork()、程序载入execve()、映射文件mmap()、动态内存分配malloc()/brk()等进程相关操作都需要分配内存给进程。不过这时进程申请和获得的还不是实际内存,而是虚拟内存,准确的说是“内存区域”。进程对内存区域的分配最终都会归结到do_mmap()函数上来(brk调用被单独以系统调用实现,不用do_mmap()),

    内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况, do_mmap()函数都会将一个地址区间加入到进程的地址空间中--无论是扩展已存在的内存区域还是创建一个新的区域。

    同样,释放一个内存区域应使用函数do_ummap(),它会销毁对应的内存区域。

    如何由虚变实!

        从上面已经看到进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存(物理页面——页的概念请大家参考硬件基础一章),获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。

    该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去真正为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上。(当然,如果页被换出到磁盘,也会产生缺页异常,不过这时不用再建立页表了)

    这种请求页机制把页面的分配推迟到不能再推迟为止,并不急于把所有的事情都一次做完(这种思想有点像设计模式中的代理模式(proxy))。之所以能这么做是利用了内存访问的“局部性原理”,请求页带来的好处是节约了空闲内存,提高了系统的吞吐率。要想更清楚地了解请求页机制,可以看看《深入理解linux内核》一书。

    这里我们需要说明在内存区域结构上的nopage操作。当访问的进程虚拟内存并未真正分配页面时,该操作便被调用来分配实际的物理页,并为该页建立页表项。在最后的例子中我们会演示如何使用该方法。

     

     

    系统物理内存管理 

    虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,概括地讲,地址转换需要将虚拟地址分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一级别的页表或者指向最终的物理页面。

    每个进程都有自己的页表。进程描述符的pgd域指向的就是进程的页全局目录。下面我们借用《linux设备驱动程序》中的一幅图大致看看进程地址空间到物理页之间的转换关系。

     

     

         上面的过程说起来简单,做起来难呀。因为在虚拟地址映射到页之前必须先分配物理页——也就是说必须先从内核中获取空闲页,并建立页表。下面我们介绍一下内核管理物理内存的机制。

     

    物理内存管理(页管理)

    Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存[3],系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存时还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。

    鉴于上述需求,内核分配物理页面时为了尽量减少不连续情况,采用了“伙伴”关系来管理空闲页面。伙伴关系分配算法大家应该不陌生——几乎所有操作系统方面的书都会提到,我们不去详细说它了,如果不明白可以参看有关资料。这里只需要大家明白Linux中空闲页面的组织和管理利用了伙伴关系,因此空闲页面分配时也需要遵循伙伴关系,最小单位只能是2的幂倍页面大小。内核中分配空闲页面的基本函数是get_free_page/get_free_pages,它们或是分配单页或是分配指定的页面(2、4、8…512页)。

     注意:get_free_page是在内核中分配内存,不同于malloc在用户空间中分配,malloc利用堆动态分配,实际上是调用brk()系统调用,该调用的作用是扩大或缩小进程堆空间(它会修改进程的brk域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数为单位,扩张或收缩对应的内存区域,但brk值并非以页面大小为倍数修改,而是按实际请求修改。因此Malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然会是以页为单位分配的。

       另外,需要提及的是,物理页在系统中由页结构struct page描述,系统中所有的页面都存储在数组mem_map[]中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页面则可由上述提到的以伙伴关系组织的空闲页链表(free_area[MAX_ORDER])来索引。

     

    文本框: 伙伴关系维护

    内核内存使用

    Slab

        所谓尺有所长,寸有所短。以页为最小单位分配内存对于内核管理系统中的物理内存来说的确比较方便,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用来存放描述符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。

      为了满足内核对这种小内存块的需要,Linux系统采用了一种被称为slab分配器的技术。Slab分配器的实现相当复杂,但原理不难,其核心思想就是“存储池[4]”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。

    Slab技术不但避免了内存内部分片(下文将解释)带来的不便(引入Slab分配器的主要目的是为了减少对伙伴系统分配算法的调用次数——频繁分配和回收必然会导致内存碎片——难以找到大块连续的可用内存),而且可以很好地利用硬件缓存提高访问速度。

        Slab并非是脱离伙伴关系而独立存在的一种内存分配方式,slab仍然是建立在页面基础之上,换句话说,Slab将页面(来自于伙伴关系管理的空闲页面链表)撕碎成众多小内存块以供分配,slab中的对象分配和销毁使用kmem_cache_alloc与kmem_cache_free。

    Kmalloc

    Slab分配器不仅仅只用来存放内核专用的结构体,它还被用来处理内核对小块内存的请求。当然鉴于Slab分配器的特点,一般来说内核程序中对小于一页的小块内存的请求才通过Slab分配器提供的接口Kmalloc来完成(虽然它可分配32 到131072字节的内存)。从内核内存分配的角度来讲,kmalloc可被看成是get_free_page(s)的一个有效补充,内存分配粒度更灵活了。

    有兴趣的话,可以到/proc/slabinfo中找到内核执行现场使用的各种slab信息统计,其中你会看到系统中所有slab的使用信息。从信息中可以看到系统中除了专用结构体使用的slab外,还存在大量为Kmalloc而准备的Slab(其中有些为dma准备的)。

     

     

    内核非连续内存分配(Vmalloc)

     

    伙伴关系也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外部分片和内部分片之说,所谓内部分片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费;外部分片是指系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求。无论何种分片都是系统有效利用内存的障碍。slab分配器使得一个页面内包含的众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存。伙伴关系把内存块按大小分组管理,一定程度上减轻了外部分片的危害,因为页框分配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部分片,但并未彻底消除。你自己比划一下多次分配页面后,空闲内存的剩余情况吧。

    所以避免外部分片的最终思路还是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的情况很类似于用户空间分配虚拟内存,内存逻辑上连续,其实映射到并不一定连续的物理内存上。Linux内核借用了这个技术,允许内核程序在内核地址空间中分配虚拟地址,同样也利用页表(内核页表)将虚拟地址映射到分散的内存页上。以此完美地解决了内核内存使用中的外部分片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不同于kmalloc,它可以分配较Kmalloc大得多的内存空间(可远大于128K,但必须是页大小的倍数),但相比Kmalloc来说,Vmalloc需要对内核虚拟地址进行重映射,必须更新内核页表,因此分配效率上要低一些(用空间换时间)

    与用户进程相似,内核也有一个名为init_mm的mm_strcut结构来描述内核地址空间,其中页表项pdg=swapper_pg_dir包含了系统内核空间(3G-4G)的映射关系。因此vmalloc分配内核虚拟地址必须更新内核页表,而kmalloc或get_free_page由于分配的连续内存,所以不需要更新内核页表。

    文本框: 伙伴关系维护文本框: vmalloc文本框: Kmalloc

    vmalloc分配的内核虚拟内存与kmalloc/get_free_page分配的内核虚拟内存位于不同的区间,不会重叠。因为内核虚拟空间被分区管理,各司其职。进程空间地址分布从0到3G(其实是到PAGE_OFFSET,在0x86中它等于0xC0000000),从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页面表mem_map等等)比如我使用的系统内存是64M(可以用free看到),那么(3G——3G+64M)这片内存就应该映射到物理内存,而vmalloc_start位置应在3G+64M附近(说"附近"因为是在物理内存映射区与vmalloc_start期间还会存在一个8M大小的gap来防止跃界),vmalloc_end的位置接近4G(说"接近"是因为最后位置系统会保留一片128k大小的区域用于专用页面映射,还有可能会有高端内存映射区,这些都是细节,这里我们不做纠缠)。

     

     

    上图是内存分布的模糊轮廓

     

       由get_free_page或Kmalloc函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),你可以很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理映射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核页表是有序对应的,系统中的每个物理页面都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。

    而vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k大小的空闲区间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页面。

     

    这里给出一个小程序帮助大家认清上面几种分配函数所对应的区域。

    #include<linux/module.h>

    #include<linux/slab.h>

    #include<linux/vmalloc.h>

    unsigned char *pagemem;

    unsigned char *kmallocmem;

    unsigned char *vmallocmem;

    int init_module(void)

    {

     pagemem = get_free_page(0);

     printk("<1>pagemem=%s",pagemem);

     kmallocmem = kmalloc(100,0);

     printk("<1>kmallocmem=%s",kmallocmem);

     vmallocmem = vmalloc(1000000);

     printk("<1>vmallocmem=%s",vmallocmem);

    }

    void cleanup_module(void)

    {

     free_page(pagemem);

     kfree(kmallocmem);

     vfree(vmallocmem);

    }

     

    实例

    内存映射(mmap)是Linux操作系统的一个很大特色,它可以将系统内存映射到一个文件(设备)上,以便可以通过访问文件内容来达到访问内存的目的。这样做的最大好处是提高了内存访问速度,并且可以利用文件系统的接口编程(设备在Linux中作为特殊文件处理)访问内存,降低了开发难度。许多设备驱动程序便是利用内存映射功能将用户空间的一段地址关联到设备内存上,无论何时,只要内存在分配的地址范围内进行读写,实际上就是对设备内存的访问。同时对设备文件的访问也等同于对内存区域的访问,也就是说,通过文件操作接口可以访问内存。Linux中的X服务器就是一个利用内存映射达到直接高速访问视频卡内存的例子。

    熟悉文件操作的朋友一定会知道file_operations结构中有mmap方法,在用户执行mmap系统调用时,便会调用该方法来通过文件访问内存——不过在调用文件系统mmap方法前,内核还需要处理分配内存区域(vma_struct)、建立页表等工作。对于具体映射细节不作介绍了,需要强调的是,建立页表可以采用remap_page_range方法一次建立起所有映射区的页表,或利用vma_struct的nopage方法在缺页时现场一页一页的建立页表。第一种方法相比第二种方法简单方便、速度快, 但是灵活性不高。一次调用所有页表便定型了,不适用于那些需要现场建立页表的场合——比如映射区需要扩展或下面我们例子中的情况。

     

    我们这里的实例希望利用内存映射,将系统内核中的一部分虚拟内存映射到用户空间,以供应用程序读取——你可利用它进行内核空间到用户空间的大规模信息传输。因此我们将试图写一个虚拟字符设备驱动程序,通过它将系统内核空间映射到用户空间——将内核虚拟内存映射到用户虚拟地址。从上一节已经看到Linux内核空间中包含两种虚拟地址:一种是物理和逻辑都连续的物理内存映射虚拟地址;另一种是逻辑连续但非物理连续的vmalloc分配的内存虚拟地址。我们的例子程序将演示把vmalloc分配的内核虚拟地址映射到用户地址空间的全过程。

    程序里主要应解决两个问题:

    第一是如何将vmalloc分配的内核虚拟内存正确地转化成物理地址?

    因为内存映射先要获得被映射的物理地址,然后才能将其映射到要求的用户虚拟地址上。我们已经看到内核物理内存映射区域中的地址可以被内核函数virt_to_phys转换成实际的物理内存地址,但对于vmalloc分配的内核虚拟地址无法直接转化成物理地址,所以我们必须对这部分虚拟内存格外“照顾”——先将其转化成内核物理内存映射区域中的地址,然后在用virt_to_phys变为物理地址。

    转化工作需要进行如下步骤:

    a)         找到vmalloc虚拟内存对应的页表,并寻找到对应的页表项。

    b)        获取页表项对应的页面指针

    c)        通过页面得到对应的内核物理内存映射区域地址

    如下图所示:

    第二是当访问vmalloc分配区时,如果发现虚拟内存尚未被映射到物理页,则需要处理“缺页异常”。因此需要我们实现内存区域中的nopaga操作,以能返回被映射的物理页面指针,在我们的实例中就是返回上面过程中的内核物理内存映射区域中的地址由于vmalloc分配的虚拟地址与物理地址的对应关系并非分配时就可确定,必须在缺页现场建立页表,因此这里不能使用remap_page_range方法,只能用vma的nopage方法一页一页的建立。

     

     

    程序组成

    map_driver.c,它是以模块形式加载的虚拟字符驱动程序。该驱动负责将一定长的内核虚拟地址(vmalloc分配的)映射到设备文件上。其中主要的函数有——vaddress_to_kaddress()负责对vmalloc分配的地址进行页表解析,以找到对应的内核物理映射地址(kmalloc分配的地址);map_nopage()负责在进程访问一个当前并不存在的VMA页时,寻找该地址对应的物理页,并返回该页的指针。

    test.c 它利用上述驱动模块对应的设备文件在用户空间读取读取内核内存。结果可以看到内核虚拟地址的内容(ok!),被显示在了屏幕上。

     

    执行步骤

    编译map_driver.c为map_driver.o模块,具体参数见Makefile

    加载模块 :insmod map_driver.o

    生成对应的设备文件

    1 在/proc/devices下找到map_driver对应的设备命和设备号:grep mapdrv /proc/devices

    2 建立设备文件mknod  mapfile c 254 0  (在我的系统里设备号为254)

        利用maptest读取mapfile文件,将取自内核的信息打印到屏幕上。

     

    全部程序下载 mmap.tar (感谢Martin Frey,该程序的主体出自他的灵感)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     



    [1] 静态分配内存就是编译器在编译程序的时候根据源程序来分配内存. 动态分配内存就是在程序编译之后, 运行时调用运行时刻库函数来分配内存的. 静态分配由于是在程序运行之前,所以速度快, 效率高, 但是局限性大. 动态分配在程序运行时执行, 所以速度慢, 但灵活性高.

     

    [2]术语"BSS"已经有些年头了,它是block started by symbol的缩写。因为未初始化的变量没有对应的值,所以并不需要存储在可执行对象中。但是因为C标准强制规定未初始化的全局变量要被赋予特殊的默认值(基本上是0值),所以内核要从可执行代码装入变量(未赋值的)到内存中,然后将零页映射到该片内存上,于是这些未初始化变量就被赋予了0值。这样做避免了在目标文件中进行显式地初始化,减少空间浪费(来自《Linux内核开发》)

    [3] 还有些情况必须要求内存连续,比如DMA传输中使用的内存,由于不涉及页机制所以必须连续分配。

    [4] 这种存储池的思想在计算机科学里广泛应用,比如数据库连接池、内存访问池等等。


    展开全文
  • Linux下手动释放内存

    2014-01-03 14:06:20
    Linux下手动释放内存
  • linux内存管理

    2013-07-26 08:14:24
    摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux内存管理与使用。在本章最后,...
  • Linux内存管理中的 Slab 分配机制.pdf ...总第二三五期"M O D E R N C OM P U T E R 2006.5实践与经验1 Li nux 内存管理体系在 Intel 80x86 体系结构中, Linux 内核的内存管理采用了分页管理方式, 真正实现了虚拟存...
  • Linux 内存管理

    万次阅读 多人点赞 2011-01-19 16:30:00
    1.Linux 进程在内存数据结构 可以看到一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分: 1)代码段:存放CPU执行的机器指令。通常代码区是共享的,即其它执行程序可调用它。...
  • linux 进程占用内存查询

    万次阅读 2014-04-15 17:58:51
    ”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿...【51CTO.com独家译文】本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多...
  • 总有很多朋友对于Linux内存管理有疑问,之前一篇linux下的内存管理方式似乎也没能清除大家的疑虑。而在新版核心中,似乎对这个问题提供了新的解决方法,特转出来给大家参考一下。最后,还附上我对这方法的意见,...
  • Linux内存不足优化 执行free -m 命令查看可用内存,发现cached占比过高,free可用内存过少。 Linux手动释放缓存的方法 Linux释放内存的命令: sync echo 1 > /proc/sys/vm/drop_caches drop_caches的值可以是0-3...
  • 首先让我先说读任意内核内存是一件棘手的事!并且有很多方法可以做到这一点,它们的复杂程度和灵活性各不相同.1)硬编码地址.在内核版本的System.map文件中搜索它:# grep sys_call_table /boot/System.map-2.6.18-238...
  • linux高端内存内存映射——重要

    千次阅读 2018-04-17 20:20:25
    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在...
  • linux CPU和内存详解

    2017-10-30 18:53:09
    linux 查看CPU和内存的使用情况 我是linux小白,不喜勿喷,谢谢,今天花了¥200大洋买了个搬瓦工的VPN,用ssh登上之后,马上进行查看操作,百度了一下,觉得很实用,就有了这篇文章了! top命令: 命令行...
  • linux内存管理介绍

    千次阅读 2013-06-03 22:32:58
    Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux内存管理与...
  • Linux 查看CPU内存状态

    2020-07-30 15:46:22
    查看CPU个数 # cat /proc/cpuinfo | grep "physical id" | uniq | wc -l 查看CPU核数 # cat /proc/cpuinfo | grep ...查看内存信息 # cat /proc/meminfo 查看磁盘信息 # fdisk -l # df -h [root@lo...
  • #内存泄露# linux常用内存相关命令

    万次阅读 2020-06-19 11:14:34
     free 命令会显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存等。 $ free total used free shared buff/cache available Mem: 8047480 6142828 154116 465584 1750536 1078080 Swap: ...
  • Linux 虚拟内存

    千次阅读 2018-03-05 09:14:34
    转载自:http://blog.csdn.net/jincm13/article/details/18302177Linux系统虚拟内存空间一般布局示意图 说明: 1)线性地址空间:是指Linux系统中从0x00000000到0xFFFFFFFF整个4GB虚拟存储空间。 2)内核空间:...
  • Linux内存文件系统

    千次阅读 2016-04-27 12:04:31
    Linux内存文件系统:RamDisk,ramfs,tmpfs
  • Linux内存管理基础

    千次阅读 2016-07-15 14:38:33
    系统启动之Linux内存管理基础系统启动之Linux内存管理基础 Keywords 闲言碎语 操作系统启动过程 1 bootloader的主要任务是 2 为启用分页机制做准备并使能分页机制 21 建立临时页表 3 initmaincstart_kernerl 4 PKmap...
  • 查看linux系统中空闲内存/物理内存使用/剩余内存查看系统内存有很多方法,但主要的是用top命令和free 命令当执行top命令看到结果,要怎么看呢?这里说明一下:Mem: 666666k total, 55555k used,并不是代表你的应用...
  • Linux 查看Tomcat内存占用情况

    万次阅读 2017-09-06 16:04:04
    以前服务器还是用 Windows Server 系统的时候,查看一下各个进程对内存的影响就再简单不过了,打开任务管理器跟踪一下相关的 JAVA ...1.在 Linux 端写批处理文件,调用查看Tomcat 的内存使用情况; 2.在项目中植入.
  • 原文地址:http://www.360doc.com/content/11/0505/00/2104556_114442966.shtml 参考... ...Linux查看CPU和内存使用情况 在系统维护的过程中,随时可能有需要查看 CPU 使用率,并根据
  • Linux内存机制

    2013-10-14 20:37:30
    Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法。力求从外到内、水到渠成地引导网友分析Linux内存管理与使用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 107,834
精华内容 43,133
关键字:

linuxr改变内存

linux 订阅