内存

linux内存

Linux 的虚拟内存管理有几个关键概念:

1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址;

2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址;

3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

  

linux虚拟地址空间如何分布

linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为:

1.只读段:该部分空间只能读,不可写;(包括:代码段、rodata段(C常量字符串和#define定义的常量) )

2、数据段:保存全局变量、静态变量的空间;

3、堆 :就是平时所说的动态内存,malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。

4、文件映射区域 :如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。

5、栈:用于维护函数调用的上下文空间,一般为8M ,可通过 ulimit –s 查看。

6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。

32 位系统有4G 的地址空间::

 

      其中0x08048000~0xbfffffff (较低的3G)是用户空间,0xc0000000~0xffffffff(高的1G) 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk 函数控制堆顶_edata往高地址方向变化。

 

64 位系统的虚拟地址空间划分发生了改变:

1、地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要 2^64 这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,

2、其中,0x0000000000000000~0x00007fffffffffff表示用户空间, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF 表示内核空间,共提供256TB(2^48) 的寻址空间。

这两个区间的特点是,第 47 位与 48~63 位相同,若这些位为 0 表示用户空间,否则表示内核空间。

3、用户空间由低地址到高地址仍然是只读段、数据段、堆、文件映射区域和栈;

bss、data和rodata区别:

在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的或初始化为0全局变量保存在.bss 段中。text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。rodata的意义同样明显,ro代表read only,即只读数据(const),rodata是在多个进程间是共享的,这可以提高空间利用率。

用户进程内存空间

    每个进程都有完全属于自己的,独立的,不被干扰的内存空间。此空间,被分成几个段(Segment),分别是Text, Data, BSS, Heap, Stack。用户进程内存空间,也是系统内核分配给该进程的VM(虚拟内存),但并不表示这个进程占用了这么多的RAM(物理内存)。

 

VM分配与释放

 

   “内存总是被进程占用”,当fork()或者exec()一个进程的时候,系统内核就会分配一定量的VM给进程,作为进程的内存空间,大小由BSS段,Data段的已定义的全局变量、静态变量、Text段中的字符直接量、程序本身的内存映像等,还有Stack段的局部变量决定。当然,还可以通过malloc()等函数动态分配内存,向上扩大heap。

 

    动态分配与静态分配,二者最大的区别在于:1. 直到Run-Time的时候,执行动态分配,而在compile-time的时候,就已经决定好了分配多少Text+Data+BSS+Stack。2.通过malloc()动态分配的内存,需要程序员手工调用free()释放内存,否则容易导致内存泄露,而静态分配的内存则在进程执行结束后系统释放(Text, Data), 但Stack段中的数据很短暂,函数退出立即被销毁。

内存管理

    内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常是字(甚至字节),但是,内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址的硬件)通常以页为单位进行处理,因此,从虚拟内存的角度来看,页就是最小单位。在32位的系统中,一页的大小为4KB。所以,64M的物理内存将被分为16384个页。每一个物理页对应地用一个struct page来维护,注意,该结构体是用来维护物理页,而不是虚拟页,结构体记录该页是否被使用,对应的虚拟地址是多少等信息。

         由于硬件的限制,内核并不能对所有的页一视同仁,有一些页位于内存中特定的物理地址上,所以不能将其用于一些特定的任务,由于存在这种限制,所以内核把页分成不同的区。

如有些硬件的访问只能在24位的地址空间寻址,出于这总访问限制,linux把前16MB划分

ZONE_DMA--这个区的页只能用来执行DMA操作

ZONE_DMA32--和ZONE_DMA类似,该区包含的都是用来执行DMA操作,而和ZONE_DMA不同之处在于,这些页面只能被32位设备访问,而某些体系结构中,该区将比ZONE_DMA更大。

ZONE_NORMAL--这个区包含的都是能正常映射的页面

ZONE-HIGHMEM-高端内存,其中的页并不能永久映射到内核地址空间

1)这些分区是指linux自己分的,当然,如果普通分区不够用,当然也可以占用其他区的空间。

2)分区的大小是根据体系结构而定的,一般的ARM下,ZONE_NORMAL就是所有的可用内存区域。

slab层

    用于频繁使用的数据结构的缓存,且避免因频繁分配和使用导致的内存碎片。slab层是由高速缓存组成的,而每个高速缓存可以由多个slab组成,slab由一个或多个物理上连续的页组成。每个slab都包含一些缓存的数据结构。举个inode的例子。

  inode是磁盘文件在内存中的体现,会频繁地进行创建和释放,所以有必要进行缓存管理。在这里高速缓存是inode_cachep,它由多个slab组成,而每个slab包含尽可能多的struct inode对象。所以当我们需要一个新的inode结构时,不必现创建,只需从部分满或空的slab返回一个指向已分配但未使用的inode结构的指针即可。当内核使用完这个inode对象时,slab分配器就把该对象标记为空闲,留给后来者。

inode,中文译名为"索引节点",存储比如文件的创建者、文件的创建日期、文件的大小等等这种储存文件元信息的区域

 

 

 

内存分配的原理

 

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

 

1、brk是将数据段(.data)的最高地址指针_edata往高地址推;

 

2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

 

     这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

 

举例说明内存分配的原理:

情况一、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),  也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。

情况二、malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)。

kmalloc、vmalloc、malloc的区别

1.kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存

2.kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续

3.内存只有在要被DMA访问的时候才需要物理上连续,vmalloc比kmalloc要慢

4. kmalloc分配内存是基于slab,因此slab的一些特性包括着色,对齐等都具备,性能较好

缺页中断

majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。

当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:

1、检查要访问的虚拟地址是否合法

2、查找/分配一个物理页

3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)

4、建立映射关系(虚拟地址到物理地址)

重新执行发生缺页中断的那条指令

如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。

 

 

 

Windows内存

基本概念

物理地址(physicaladdress):用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。

逻辑地址(logicaladdress):是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及。应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。

虚拟地址(lvirtual  address):在32位系统上,虚拟地址空间可以达到4GB大小,也就是说,整个空间可以有2的32次方4294967296个字节单元。

虚拟内存:编程时我们面对的都是虚拟地址,win32中对于每个进程来说都拥有4G的虚拟内存(4G虚拟内存中,高2G内存属于内核部分,是所有进程共有的,低2G内存数据时进程独有的,每个进程低2G内存都不一样),但是需要注意虚拟地址并不是真正存在的,所以不构成任何资源损失,比如我们要在)X80000000的地方写“sga”的时候,操作系统就会将这个虚拟地址映射到一块物理地址A中,写这块虚拟地址就相当于写入物理地址A,但是假如我们申请一段1Kb的虚拟内存空间,并未读写,系统是不会分配任何物理内存的,只有当虚拟内存要使用的时候操作系统才会分配相应的物理空间。

 

内存结构

 1.进程虚拟地址空间布局:

 

     每个进程都被赋予它自己的虚拟地址空间。对于3 2位进程来说,这个地址空间是4GB,Win2K在IA-32架构的CPU下面的进程地址空间分布如下表所示:

 

                                

 

     可见Win2K的内存布局非常简单,主要有4个部分,两个不能被存取的64K;然后剩下的部分就是分别是用户空间(2G–128K)和系统空间(2G)了。

 

进程虚拟地址空间布局说明:

 

1.       64KNULL指针分配区:

 

这个分区的设置是为了帮助程序员掌握NULL指针的分配情况。如果你的进程中的线程试图读取该分区的地址空间的数据,或者将数据写入该分区的地址空间,那么C P U就会引发一个访问违规。也就是说在Win2K中,NULL的宏定义不必一定是0;可以是64K之内的任何地址;比如在win2K下面定义一个指针变量pVar;令pVar取值在0~65535(64K)之间的时候都会发生存取异常;当令pVar = 65536就没有问题了。

 

 

2.       用户空间:

在Windows 2000中,所有的. exe和DLL模块均加载这个分区。每个进程可以将这些D L L加载到该分区的不同地址中(不过这种可能性很小)。系统还可以在这个分区中映射该进程可以访问的所有内存映射文件。

 

3.       64K禁入区:

 

64K禁入区的作用很明显是隔离了用户和内核空间;防止用户程序跨越到内核空间中。

 

4.       内核空间:

 

这个分区是存放操作系统代码的地方。用于线程调度、内存管理、文件系统支持、网络支持和所有设备驱动程序的代码全部在这个分区加载。驻留在这个分区中的一切均可被所有进程共享。在Windows 2000中,这些组件是完全受到保护的。

 

 

 

2.如何分配地址空间:

 

     对一个地址空间的区域进行分配的操作称为保留( reserving)。若要使用已保留的地址空间区域,必须分配物理存储器,然后将该物理存储器映射到已保留的地址空间区域。这个过程称为提交物理存储器。物理存储器总是以页面的形式来提交的。若要将物理存储器提交给一个已保留的地址空间区域,也要调用VirtualAlloc函数。

 

    分配地址空间的两个规则:要确保保留区域从一个分配粒度的边界开始(系统自身保留地址空间时未必遵守这个约定)。x86使用64KB这个分配粒度;还要确保保留区域的大小是系统页面大小的倍数。x86使用的页面大小是4KB。

3.如何回收地址空间:

     当你的程序不再需要访问已经保留的地址空间区域时,保留区域应该被释放。这个过程称为释放地址空间的区域,它是通过调用VirtualFree函数来完成的。

     注意这节说的只是对地址空间的分配,而不是对内存的分配。已经被分配的地址空间没有和内存相对应;这时候对这块空间的读写会发生访问违规。

当你的进程中的一个线程试图访问进程的地址空间中的一个数据块时,将会发生两种情况之一,参见下图的流程图。

      在第一种情况中,线程试图访问的数据是在RAM中。在这种情况下, CPU将数据的虚拟内存地址映射到内存的物理地址中,然后执行需要的访问。

     在第二种情况中,线程试图访问的数据不在RAM中,而是存放在页文件中的某个地方。这时,试图访问就称为页面失效,CPU将把试图进行的访问通知操作系统。这时操作系统就寻找RAM中的一个内存空页。如果找不到空页,系统必须释放一个空页。如果一个页面尚未被修改,系统就可以释放该页面。但是,如果系统需要释放一个已经修改的页面,那么它必须首先将该页面从RAM拷贝到页交换文件中,然后系统进入该页文件,找出需要访问的数据块,并将数据加载到空闲的内存页面。然后,操作系统更新它的用于指明数据的虚拟内存地址现在已经映射到RAM中的相应的物理存储器地址中的表。这时CPU重新运行生成初始页面失效的指令,但是这次CPU能够将虚拟内存地址映射到一个物理RAM地址,并访问该数据块。

 

      当启动一个应用程序的时候,系统将打开该应用程序的. exe文件,确定该应用程序的代码和数据的大小。然后系统要保留一个地址空间的区域,并指明与该区域相关联的物理存储器是在. exe文件本身中。即系统并不是从页文件中分配地址空间,而是将. exe文件的实际内容即映像用作程序的保留地址空间区域。当然,这使应用程序的加载非常迅速,并使页文件能够保持得非常小。

 

4.地址空间可以和什么相对应呢?

     和地址空间相对应的只有两样东西,一个是页面文件,也就是通常所说的虚拟内存,一个是内存映射文件,内存映射文件也是磁盘上的文件,这包括exe,dll等,还包括用户自己创建的内存映射文件。或许你认为和地址空间相对应的应该是ram,不错,当我们真正对一个地址进行读写的时候,确实是对ram进行读写;但是ram只是相当于一个缓冲区;负责对内存映射文件和虚拟内存读写进行缓冲。

 

5.什么是Copy-On-Write保护属性?

 

     在保留地址空间的同时可以指定其保护属性,这些属性无法就是读,写,执行,及其组合,这里重点说一下两个特殊的保护属性。一个是PAGE_WRITECOPY,另一个是PAGE_EXECUTE_WRITECOPY。这两个属性的作用是为了节省RAM的使用量和页文件的空间。Windows支持一种机制,使得两个或多个进程能够共享单个内存块。但是这要求所有实例都将该内存视为只读或只执行的内存。如果一个实例中的线程将数据写入内存修改它,那么其他实例看到的这个内存也将被修改,从而造成一片混乱。为了防止出现这种混乱,操作系统给共享内存块赋予了Copy-On-Write保护属性。当一个.exe或DLL模块被映射到一个内存地址时,系统将计算有多少页面是可以写的(通常包含代码的页面标为PAGE_EXECUTE_READ,而包含数据的页面则标为PAGE_READWRITE)。然后,系统依照计算结果从页文件中分配内存,以防备对这些可写页面写入时的需要。当然如果不对该模块的可写页面进行实际的写入操作,那么这些页文件内存就不会被使用。

 

6.Copy-On-Write保护是如何实现的?

 

当一个进程中的线程试图将数据写入一个共享内存块时,系统就会进行干预,并执行下列操作步骤:

1)系统查找RAM中的一个空闲内存页面。

2)系统将试图被修改的页面内容拷贝到第一步中找到的页面。并将其赋予PAGE_READWRITE或PAGE_EXECUTE_READWRITE保护属性。原始页面的保护属性和数据不发生任何变化。

3)然后系统更新进程的页面表,使得被访问的虚拟地址被转换成新的RAM页面。

当系统执行了这3个操作步骤之后,该进程就可以访问它自己的内存页面的私有实例。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值