精华内容
下载资源
问答
  • 虚拟内存管理

    2019-07-08 23:01:09
    虚拟内存管理这是一种利用虚拟存储器来逻辑扩充物理内存的管理方式。其基本思想是用软硬件技术把内存与外存这两级存储器当成一级存储器来用,从而给用户提供了一个比内存也比任何应用程序大得多的虚拟存储器,使得...

    虚拟内存管理这是一种利用虚拟存储器来逻辑扩充物理内存的管理方式。其基本思想是用软硬件技术把内存与外存这两级存储器当成一级存储器来用,从而给用户提供了一个比内存也比任何应用程序大得多的虚拟存储器,使得用户编程时再也不用考虑内存大小的限制了,给用户编程带来极大的方便。

    传统存储管理方式的特征

    段页式的内存管理策略都是为了同时将多个进程保存在内存中以便允许多道程序设计。它们都具有以下两个共同的特征:

    1)一次性:作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生:①当作业很大,不能全部被装入内存时,将使该作业无法运行:②当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。

    2)驻留性:作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待I/O而被阻塞,可能处于长期等待状态。

    由以上分析可知,许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间,而一些需要运行的作业又无法装入运行,显然浪费了宝贵的内存资源。

    局部性原理

    局部性原理:指程序在执行过程中的一个较短时期内,所执行的指令地址和指令的操作数地址,分别局限于一定区域。

    局部性主要表现:

    时间局部性:是指一段指令在某一时间段内会被反复执行。即程序某一部分的数据或指令被重复性地访问,它们对应于程序结构中的循环、子程序、常用到的变量及数据等 ;

    空间局部性:是指一旦某一个存储单元被访问,那么它附近的单元也将很快被访问。这对应于程序结构中的顺序执行的指令、线性数据结构以及在相邻位置存放的数据或变量等。而程序中的分支和调用子程序只是将程序的访问空间从一处移到另外一处,仍具有局部性。

    排他性:程序运行不但体现在时间、空间的局部性,还体现在某些程序段执行的排他性。

    即程序设计者编程时要考虑程序执行时所能遇到的各种情况,但具体到一次程序的执行,并不会发生所有的状况。因而某些程序段在进程整个运行期间,可能根本不使用,如出错处理、分支语句等。因而,没有用到的程序段就不必调入内存。另外,有些程序段仅执行一次,以后就再也不会用到,这样的程序段也没有必要一直占用内存空间。

    综上所述:程序只要装入内存一部分就可以运行,当用到不在内存的部分时,再将其装入内存。换句话就是说程序全部装入内存并不是程序运行的必要条件。

    虚拟内存的定义和特征

    虚拟存储器

    基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其余部分留在外存,就可以启动程序执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟存储器。

    之所以将其称为虚拟存储器,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入、请求调入和置换功能后(对用户完全透明),给用户的感觉是好像存在-一个比实际物理内存大得多的存储器.虚拟存储器的大小由计算机的地址结构决定,并非是内存和外存的简单相加。
    虚拟存储器有以下三个主要特征:

    • 多次性,是指无需在作业运行时- 次性地全部装入内存,而是允许被分成多次调入内存运行。
    • 对换性,是指无需在作业运行时一直常驻内存,而是允许在作业的运行过程中,进行换进和换出。
    • 虚拟性,是指从逻辑上扩充内存的容量,使用户所看到的内存容量,远大于实际的内存容量。

    虚拟内存的实现

    虚拟内存中,允许将-一个作业分多次调入内存。采用连续分配方式时,会使相当一部分内存空间都处于暂时或“永久”的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虛拟内存的实现需要建立在离散分配的内存管理方式的基础上。

    虚拟内存的实现有以下三种方式:

    • 请求分页存储管理。
    • 请求分段存储管理。
    • 请求段页式存储管理。

    不管哪种方式,都需要有一-定的硬件支持。一般需要的支持有以下几个方面:

    • 一定容量的内存和外存。
    • 页表机制(或段表机制),作为主要的数据结构。
    • 中断机构,当用户程序要访问的部分尚未调入内存,则产生中断。
    • 地址变换机构,逻辑地址到物理地址的变换。

    请求分页存储管理方式

    为了实现请求分页,系统必须提供- -定的硬件支持。除了需要- -定容量的内存及外存的计算
    机系统,还需要有页表机制、缺页中断机构和地址变换机构。

    1.页表机制

    请求分页系统的页表机制不同于基本分页系统,请求分页系统在.一个作业运行之前不要求全部一次性调入内存,因此在作业的运行过程中,必然会出现要访问的页面不在内存的情况,如何发现和处理这种情况是请求分页系统必须解决的两个基本问题。为此,在请求页表项中增加了四个字段。

    增加的四个字段说明如下:

    • 状态位P:用于指示该页是否已调入内存,供程序访问时参考。
    • 访问字段A:用于记录本页在- 段时间内被访问的次数,或记录本页最近已有多长时间未被访问,供置换算法换出页面时参考。
    • 修改位M:标识该页在调入内存后是否被修改过。
    • 外存地址:用于指出该页在外存上的地址,通常是物理块号,供调入该页时参考。

    2.缺页中断机构

    在请求分页系统中,每当所要访问的页面不在内存时,便产生-一个缺页中断,请求操作系统将所缺的页调入内存。此时应将缺页的进程阻塞(调页完成唤醒),如果内存中有空闲块,则分配-一个块,将要调入的页装入该块,并修改页表中相应页表项,若此时内存中没有空闲块,则要淘汰某页(若被淘汰页在内存期间被修改过,则要将其写回外存)。

    3.地址变换机构

    请求分页系统中的地址变换机构,是在分页系统地址变换机构的基础上,为实现虚拟内存,又增加了某些功能而形成的。

                                                    

    页面置换算法

    最佳置换算法(OPT)

    方法:根据未来使用情况将未来的近期里不用的页替换出去。
    实现:

    • 确定要替换的时刻T。
    • 找出主存中每个页将来要用到的时刻Ti。
    • Ti 减 T最大的页将被替换。

    特点:命中率高,但难于实现(必须运行一遍,才能知道未来的时刻ti),是理想算法,用于评价其它替换算法。

                           

    发生了6次页面置换,9次缺页中断,总访问次数20次,缺页率9/20=45% 。

    先进先出(FIFO)页面置换算法

    优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置- 一个指针总指向最早的页面。但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。

                            

    发生了12次页面置换,15次缺页中断,总访问次数20次,缺页率15/20=75% 。

    最近最久未使用(LRU)换算法

    方法:近期最久未访问过的页作为被替换的页
    实现:赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,当须淘汰一个页面时,选择现有页面中其t值最大的页面予以淘汰。
    特点:计数器硬件较少,主存页面表可由软硬件实现修改,根据“历史”预测“未来”。

                               

    发生了9次页面置换,12次缺页中断,总访问次数20次,缺页率12/20=60% 。

    LRU置换算法的硬件支持:

    • 每个页面设立移位寄存器:被访问时左边最高位置1,定期右移并且最高位补0,于是寄存器数值最小的是最久未使用页面。
    • 一个特殊的栈:把被访问的页面移到栈顶,于是栈底的是最久未使用页面。

    Clock置换算法

    每页设置一位访问位,若该页被访问则其访问位被置1;
    内存中所有页面都通过链接指针链接成一个循环队列,置换时采用一个指针,从当前指针位置开始按地址先后检查各页,寻找访问位为0的页面作为被置换页;
    指针经过的访问位为1的页都将其访问位置0,最后指针停留在被置换页的下一个页。

    简单的Clock置换算法

                                                           

    页面分配策略

    駐留集大小

    对于分页式的虚拟内存,在准备执行时,不需要也不可能把-.个进程的所有页都读取到主存,因此,操作系统必须决定读取多少页。也就是说,给特定的进程分配多大的主存空间,这需要考虑以下几点:

    1. 分配给一个进程的存储量越小,在任可时候驻留在主存中的进程数就越多,从而可以提高处理机的时间利用效率。
    2. 如果一个进程在主存中的页数过少,尽管有局部性原理,页错误率仍然会相对较高。
    3. 如果页数过多,由于局部性原理,给特定的进程分配更多的主存空间对该进程的错误率没有明显的影响。

    固定分配、局部置换

    •  为每个进程分配固定页数的内存空间、且运行过程中不变。
    •  当进程缺页时,只能从该进程在内存的几个页面中选出一页换出,然后再调入一页,保证进程的页数不变。

    可变分配、全局置换

    • 系统开始先为每个进程分配一定数目的物理块。整个系统有一空闲物理块链,当某进程缺页时,系统从空闲链中选出一块分配给进程。
    • 空闲链为空时,OS从所有进程的页面中权衡选择一页换出。

    可变分配、局部置换

    • 分配同上,但进程缺页时,只能从该进程在内存的页面中选出一页换出。

    调入页面的时机

    为确定系统将进程运行时所缺的页面调入内存的时机,可采取以下两种调页策略:

    (1)预调页策略。根据局部性原理,- -次调入若千个相邻的页可能会比一次调入- -页更高效。但如果调入的一批页面中大多数都未被访问,则又是低效的。所以就需要采用以预测为基础的预调页策略,将预计在不久之后便会被访问的页面预先调入内存。但目前预调页的成功率仅约50%。故这种策略主要用于进程的首次调入时,由程序员指出应该先调入哪些页。

    (2)请求调页策略。进程在运行中需要访问的页面不在内存而提出请求,由系统将所需页面调入内存。由这种策略调入的页- -定会被访问, 且这种策略比较易于实现,故在目前的虚拟存储器中大多采用此策略。它的缺点在于每次只调入一页,调入调出页面数多时会花费过多的I/O开销。

    预调入实际上就是运行前的调入,请求调页实际上就是运行期间调入。一般情况下,两种调页策略会同时使用。

    从何处调入页面

    请求分页系统中的外存分为两部分:用于存放文件的文件区和用于存放对换页面的对换区。对换区通常是采用连续分配方式,而文件区采用离散分配方式,故对换区的磁盘IO速度比文件区的更快。这样从何处调入页面有三种情况:

    (1)系统拥有足够的对换区空间:可以全部从对换区调入所需页面,以提高调页速度。为此,在进程运行前,需将与该进程有关的文件从文件区复制到对换区。

    (2)系统缺少足够的对换区空间:凡不会被修改的文件都直接从文件区调入;而当换出这些页面时,由于它们未被修改而不必再将它们换出。但对于那些可能被修改的部分,在将它们换出时须调到对换区,以后需要时再从对换区调入(这是因为读的速度比写的速度快)。

    (3)UNIX方式:与进程有关的文件都放在文件区,故未运行过的页面,都应从文件区调入。曾经运行过但又被换出的页面,由于是被放在对换区,因此F次调入时应从对换区调入。进程请求的共享页面若被其他进程调入内存,则无需再从对换区调入。

    抖动

    在页面置换过程中的一种最糟糕的情形是,刚刚换出的页面马上又要换入主存,刚刚换入的页面马上就要换出主存,这种频繁的页面调度行为称为抖动,或颠簸。如果一个进程在换页 上用的时间多于执行时间,那么这个进程就在颠簸。

    频繁的发生缺页中断(抖动), 其主要原因是某个进程频繁访问的页面数目高于可用的物理页帧数目。虚拟内存技术可以在内存中保留更多的进程以提高系统效率。在稳定状态,几乎主存的所有空间都被进程块占据,处理机和操作系统可以直接访问到尽可能多的进程。但如果管理不当,处理机的大部分时间都将用于交换块,即请求调入页面的操作,而不是执行进程的指令,这就会大大降低系统效率。

                                                    

    工作集

    工作集(或驻留集)是指在某段时间间隔内,进程要访问的页面集合。经常被使用的页面需要在工作集中,而长期不被使用的页面要从工作集中被丢弃。为了防止系统出现抖动现象,需要选择合适的工作集大小。

    工作集模型的原理是:让操作系统跟踪每个进程的工作集,并为进程分配大于其工作集的物理块。如果还有空闲物理块,则可以再调一个进程到内存以增加多道程序数。如果所有工作集之和增加以至于超过了可用物理块的总数,那么操作系统会暂停一个进程, 将其页面调出并且将其物理块分配给其他进程,防止出现抖动现象。

    正确选择工作集的大小,对存储器的利用率和系统吞吐量的提高,都将产生重要影响。

    最后总结

    虚拟内存管理的优点:

    1. 主存利用率比较高:平均每个用户作业只浪费一半的页空间,内存规范易于管理。
    2. 对磁盘管理比较容易:因为页的大小一般取磁盘物理块大小的整数倍。
    3. 地址映射和变换的速度比较快:在把用户程序装入到主存储器的过程中,只要建立用户程序的虚页号与主存储器的实页号之间的对应关系即可(拼接得到物理地址),不必使用整个主存的地址长度,也不必考虑每页的长度等
    4. 大程序:可在较小的可用内存中执行较大的用户程序;
    5. 大的用户空间:提供给用户可用的虚拟内存空间通常大于物理内存(real memory)
    6. 并发:可在内存中容纳更多程序并发执行;
    7. 易于开发:与覆盖技术比较,不必影响编程时的程序结构

    虚拟内存管理的缺点:

    (1)程序的模块化性能不好。
    由于用户程序是强制按照固定大小的页来划分的,而程序段的实际长度一般是不固定的。因此,虚拟页式存储器中一页通常不能表示一个完整的程序功能。一页可能只是一个程序段中的一部分,也可能在一页中包含了两个或两个以上程序段。
    (2)页表很长,需要占用很大的存储空间。
    通常,虚拟存储器中的每一页在页表中都要占一个页表项。假设有一个虚拟页式存储器,它的虚拟存储空间大小为4GB,每一页的大小为1KB,则页表的容量为4M(个页表项)。如果每个页表项占用4个字节,则页表的存储容量为16MB。

    总结:总容量不超过物理内存和外存交换区容量之和。其运行速度接近于内存,每位的成本又接近于外存,是一种性能非常优越的存储管理技术。

     

    可能有的小问题

    1 虚拟内存管理发生在什么时候?

    首先当cpu发出一个虚拟地址请求后,把虚拟地址送给MMU处理。

    MMU得到地址后,通过计算得到页号/段号,先判断是否有读写权限,如果没有就返回错误,如果有再查询表(STL表/普通页表),

    • 如果表中没有该页/该段,就产生一个缺页中中断,虚拟内存管理技术就会通过一系列算法来把缺少的页从外存中取出来,放到内存中。
    • 如果表中有该页,那就通过表计算得到物理地址(即内存中的地址)。

    CPU得到物理地址后就可以对内存进行读写操作。

    2 为什么有时候多个的进程中有相同的虚拟地址,难道它们不会打架吗?

    因为每个用户都有一份属于自己的页表,所以即使虚拟地址相同,但是对应的物理地址不同,即使物理地址相同,通过虚拟存储即使对内存中的某些页进行替换操作。

     

     

     

     

     

     

    展开全文
  • Windows 虚拟内存管理

    2019-12-25 19:32:04
    文章目录虚拟内存管理1. 基本管理2. 虚拟内存管理结构3. Windbg查看VAD4. 原理4.1 NtAllocateVirtualMemory4.2 MiProtectVirtualMemory5. 应用5.1 模块枚举5.2 隐藏进程模块5.3 内存隐藏 虚拟内存管理 1. 基本管理 ...

    虚拟内存管理

    1. 基本管理

    对于内存管理的方式有很多,内存也分为很多种,例如:

    1. 堆内存
    2. 栈内存

    对于内存的分配函数也分为很多种,例如:new,malloc,Alloc, HeapAlloc;对于这些内存管理函数,其实都是封装了底层内存管理,然后对上层提供了相关的内存接口,最原始的内存管理函数应该是分为如下几个:

    //分配内存
    LPVOID WINAPI VirtualAllocEx(
      _In_     HANDLE hProcess,
      _In_opt_ LPVOID lpAddress,
      _In_     SIZE_T dwSize,
      _In_     DWORD  flAllocationType,
      _In_     DWORD  flProtect
    );
    
    //释放内存
    BOOL WINAPI VirtualFreeEx(
      _In_ HANDLE hProcess,
      _In_ LPVOID lpAddress,
      _In_ SIZE_T dwSize,
      _In_ DWORD  dwFreeType
    );
    
    //修改内存的属性
    BOOL WINAPI VirtualProtect(
      _In_  LPVOID lpAddress,
      _In_  SIZE_T dwSize,
      _In_  DWORD  flNewProtect,
      _Out_ PDWORD lpflOldProtect
    );
    
    //查询内存属性信息
    SIZE_T WINAPI VirtualQueryEx(
      _In_     HANDLE                    hProcess,
      _In_opt_ LPCVOID                   lpAddress,
      _Out_    PMEMORY_BASIC_INFORMATION lpBuffer,
      _In_     SIZE_T                    dwLength
    );
    

    所有的上层内存函数,底层都是通过这些函数分配一个大块内存,然后在底层中,通过管理向上层提供小块内存操作接口。

    2. 虚拟内存管理结构

    在windows中,虚拟内存是针对进程的,每个进程的虚拟内存是不同的,因此在进程结构中存在管理虚拟内存的变量VadRoot,如下:

    0: kd> dt  nt!_EPROCESS ffff9c84`80b133c0 -n VadRoot
    nt!_EPROCESS
       +0x658 VadRoot : _RTL_AVL_TREE
    

    对于VadRoot的结构,每个系统的版本都存在不同,WRK中的结构信息如下:

    typedef struct _MMVAD {
        union {
            LONG_PTR Balance : 2;
            struct _MMVAD *Parent;
        } u1;
        struct _MMVAD *LeftChild;
        struct _MMVAD *RightChild;
        ULONG_PTR StartingVpn;
        ULONG_PTR EndingVpn;
    
        union {
            ULONG_PTR LongFlags;
            MMVAD_FLAGS VadFlags;
        } u;
        PCONTROL_AREA ControlArea;
        PMMPTE FirstPrototypePte;
        PMMPTE LastContiguousPte;
        union {
            ULONG LongFlags2;
            MMVAD_FLAGS2 VadFlags2;
        } u2;
    } MMVAD, *PMMVAD;
    

    这个是一个RVL树结构:

    1. StringVpn 起始页 / EndingVpn结束页;两者算法是不同的。起始页:startingVpn0x1000/结束页:EndVpn0x1000+0xfff。
    2. Parent、LeftChild、RightChild - 其父节点、左子树、右子树。
    3. VadFlags属性信息,如下:
    typedef struct _MMVAD_FLAGS {
        ULONG_PTR CommitCharge : COMMIT_SIZE; // limits system to 4k pages or bigger!
        ULONG_PTR NoChange : 1;
        ULONG_PTR VadType : 3;
        ULONG_PTR MemCommit: 1;
        ULONG_PTR Protection : 5;
        ULONG_PTR Spare : 2;
        ULONG_PTR PrivateMemory : 1;    // used to tell VAD from VAD_SHORT
    } MMVAD_FLAGS;
    
    1. ContraArea 控制结构,如下:
    typedef struct _CONTROL_AREA {
        PSEGMENT Segment;
        LIST_ENTRY DereferenceList;
        ULONG NumberOfSectionReferences;    // All section refs & image flushes
        ULONG NumberOfPfnReferences;        // valid + transition prototype PTEs
        ULONG NumberOfMappedViews;          // total # mapped views, including
                                            // system cache & system space views
        ULONG NumberOfSystemCacheViews;     // system cache views only
        ULONG NumberOfUserReferences;       // user section & view references
        union {
            ULONG LongFlags;
            MMSECTION_FLAGS Flags;
        } u;
        PFILE_OBJECT FilePointer;    //文件对象,例如dll,sys等等
        PEVENT_COUNTER WaitingForDeletion;
        USHORT ModifiedWriteCount;
        USHORT FlushInProgressCount;
        ULONG WritableUserReferences;
    #if !defined (_WIN64)
        ULONG QuadwordPad;
    #endif
    } CONTROL_AREA, *PCONTROL_AREA;
    

    我们知道,对于一个进程,所有的模块都会被映射到内存空间中,所以遍历VadRoot中的CONTROL_AREA文件对象就可以遍历到所有加载的模块信息了

    如果我们希望别人看不到我们的内存,那么我们隐藏VadRoot中的结构就可以到达目的。

    3. Windbg查看VAD

    在windbg中,提供了!vad命令来查看vad内存信息,查看结构如下:

    0: kd> dt nt!_EPROCESS ffff9c84`af3980c0 -n VadRoot
       +0x658 VadRoot : _RTL_AVL_TREE
    0: kd> dx -id 0,0,ffff9c8480b133c0 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffff9c84af398718))
    (*((ntkrnlmp!_RTL_AVL_TREE *)0xffff9c84af398718))                 [Type: _RTL_AVL_TREE]
        [+0x000] Root             : 0xffff9c84b16d61c0 [Type: _RTL_BALANCED_NODE *]
    
    0: kd> !vad 0xffff9c84af398718
    VAD             Level         Start             End              Commit
    ffff9c84b1ac5770  7           7ffe0           7ffe0               1 Private      READONLY           
    ffff9c84b1ac58b0  6           7ffe6           7ffe6               1 Private      READONLY           
    ffff9c84b1ac5220  8         72ae870         72ae8ef              11 Private      READWRITE          
    ffff9c84aaf468e0  7         72ae8f0         72ae96f              11 Private      READWRITE          
    ffff9c84b1ac5a90  5         72aea00         72aebff               5 Private      READWRITE          
    ffff9c84a8a5d0c0  7        1eeb0400        1eeb040f               0 Mapped       READWRITE          Pagefile section, shared commit 0
    ffff9c84a8a5d980  8        1eeb0410        1eeb0417               0 Mapped       READWRITE          Pagefile section, shared commit 0
    ffff9c84ac05e4f0  6        1eeb0420        1eeb043a               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c849b3440d0  7        1eeb0440        1eeb0443               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c84b1ac5360  4        1eeb0450        1eeb0451               2 Private      READWRITE          
    ffff9c84af58e410  8        1eeb0460        1eeb0491               4 Private      READWRITE          
    ffff9c84af591b60  7        1eeb04a0        1eeb04d1               1 Private      READWRITE          
    ffff9c84a8a5d700  8        1eeb04e0        1eeb04e0               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c84a8a5d7a0  6        1eeb04f0        1eeb04f0               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c84a8a5d840  8        1eeb0500        1eeb050d               0 Mapped       READONLY           \Windows\servicing\CbsMsg.dll
    ffff9c84af58a0e0  7        1eeb0510        1eeb060f             255 Private      READWRITE          
    ffff9c84a8a5cc60  5        1eeb0610        1eeb06d6               0 Mapped       READONLY           \Windows\System32\locale.nls
    ffff9c84a8a5ea60  9        1eeb06e0        1eeb06e7               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c84a8a5ec40  8        1eeb06f0        1eeb07b0               0 Mapped       READONLY           Pagefile section, shared commit 0
    ffff9c84af597ba0  7        1eeb07c0        1eeb07cf               8 Private      READWRITE          
    ffff9c84b16d7b10  9        1eeb07d0        1eeb07df               9 Private      READWRITE          
    //...
    
    ffff9c84a8a5e2e0  9       7fff30380       7fff3039e               3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\profapi.dll
    ffff9c84a8a5dde0  6       7fff303a0       7fff303b0               3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\kernel.appcore.dll
    ffff9c84a8a5eba0  9       7fff303c0       7fff30409               3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\powrprof.dll
    ffff9c84a8a5fbe0  8       7fff30410       7fff305a3               8 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\gdi32full.dll
    ffff9c84a8a5ce40  7       7fff305b0       7fff3064d               7 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\msvcp_win.dll
    ffff9c84a8a5c9e0  8       7fff30650       7fff30749               4 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\ucrtbase.dll
    ffff9c84a8a5d200  4       7fff30750       7fff309f2               9 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\KernelBase.dll
    ffff9c84a8a5dac0  8       7fff30a00       7fff30b48              10 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\crypt32.dll
    ffff9c84a8a5e9c0  9       7fff30b50       7fff30b70               2 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\win32u.dll
    ffff9c84a8a5e240  7       7fff30b80       7fff30bdb               4 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\wintrust.dll
    ffff9c84a8a5e560  8       7fff30be0       7fff30c29               5 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\cfgmgr32.dll
    ffff9c84a8a5cd00  6       7fff30c30       7fff30caf               2 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\bcryptprimitives.dll
    ffff9c84a8a5e060  8       7fff30d60       7fff30d76               3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\cryptsp.dll
    ffff9c84a8a5db60  9       7fff30d80       7fff30da5               3 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\bcrypt.dll
    ffff9c84a8a5d160  7       7fff31590       7fff31626               6 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\sechost.dll
    ffff9c84a8a5d3e0  9       7fff31630       7fff316cd              10 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\msvcrt.dll
    ffff9c84a8a5f6e0  8       7fff316d0       7fff31863               6 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\user32.dll
    ffff9c84a8a5ca80  9       7fff31900       7fff31a1f               5 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\rpcrt4.dll
    ffff9c84a8a5c8a0  5       7fff31bd0       7fff31c81               5 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\kernel32.dll
    ffff9c84a8a5c300  7       7fff31c90       7fff31fc5               9 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\combase.dll
    ffff9c84a8a5e7e0  8       7fff31fd0       7fff32072               8 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\advapi32.dll
    ffff9c84a8a5d2a0  6       7fff32110       7fff321d3               6 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\oleaut32.dll
    ffff9c84a8a5e600  8       7fff32550       7fff326a5               6 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\ole32.dll
    ffff9c84a8a5e6a0  9       7fff32da0       7fff32dc5               4 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\gdi32.dll
    ffff9c84a8a5e740  7       7fff332c0       7fff33361               9 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\clbcatq.dll
    ffff9c84a8a5fdc0  9       7fff33370       7fff333de               4 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\ws2_32.dll
    ffff9c84ac05d5f0  8       7fff33480       7fff3366f              17 Mapped  Exe  EXECUTE_WRITECOPY  \Windows\System32\ntdll.dll
    
    

    4. 原理

    4.1 NtAllocateVirtualMemory

    我们调用VirtualAlloc分配内存的时候,底层就会通过NtAllocateVirtualMemory分配虚拟内存,这个函数的基本流程如下:

    NTSTATUS
    NtAllocateVirtualMemory(
        __in HANDLE ProcessHandle,
        __inout PVOID *BaseAddress,
        __in ULONG_PTR ZeroBits,
        __inout PSIZE_T RegionSize,
        __in ULONG AllocationType,
        __in ULONG Protect
        )
    {
        //...
        PMMVAD Vad;
        Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD_SHORT), 'SdaV');
    
        Status = MiFindEmptyAddressRange (CapturedRegionSize,
                                                      Alignment,
                                                      (ULONG)ZeroBits,
                                                      &StartingAddress);
    
        Status = MiInsertVadCharges (Vad, Process);                                              
    }
    

    这里创建一个内存管理节点,然后保存内存信息,并插入到进程的用户内存空间中。

    4.2 MiProtectVirtualMemory

    上面的VirtualAlloc分配了一个内存区,插入到了进程的用户内存表中,但是,我们知道可以使用VirtualProtect来改变其中某些页面的属性。

    因此对于一个MMVAD还会形成一系列的Region,每个Region的属性是不同的,所有的Region组成了MMVAD,修改属性的函数为MiProtectVirtualMemory,在Reactos中,这个实现过程如下:

    在这里插入图片描述

    至于MmSplitRegion这个函数,是修改其中某块的属性,然后针对属性进行修改,如果修改过后的属性和其他相邻的Region相同,则形成合并(这里是Reactos的代码,但是原理上面来说,应该都差不多)。

    5. 应用

    5.1 模块枚举

    从上面的分析,我们知道可以直接使用如下函数枚举进程的模块

    NTSTATUS ZwQueryVirtualMemory(
      _In_      HANDLE                   ProcessHandle,
      _In_opt_  PVOID                    BaseAddress,
      _In_      MEMORY_INFORMATION_CLASS MemoryInformationClass,
      _Out_     PVOID                    MemoryInformation,
      _In_      SIZE_T                   MemoryInformationLength,
      _Out_opt_ PSIZE_T                  ReturnLength
    );
    
    typedef enum _MEMORY_INFORMATION_CLASS {
        MemoryBasicInformation
    #if DEVL
        ,MemoryWorkingSetInformation
    #endif
        ,MemoryMappedFilenameInformation
        ,MemoryRegionInformation
        ,MemoryWorkingSetExInformation
    } MEMORY_INFORMATION_CLASS;
    

    更加底层的,我们可以直接枚举进程空间中的内存获取模块信息。

    5.2 隐藏进程模块

    既然我们可以通过VadRoot获取进程模块,那么通过同样的方法,我们也可以通过这里进行隐藏,具体实现可以参考网上的方法,但是这种方法不是特别稳定应当慎重使用。

    5.3 内存隐藏

    很容易,如果我们将MMVDA中的内存长度进行修改,就会导致内存隐藏,具体实现参考网上的一些实现方法,但是这种方法也不是稳定的方案,应当慎重使用。

    展开全文
  • 文章目录虚拟内存空间空户空间内核空间用户空间内存分配malloc内核空间内存分配kmallocvmalloc 虚拟内存空间 空户空间 内核空间 用户空间内存分配 malloc 内核空间内存分配 kmalloc vmalloc


    Linux 内存管理 | 物理内存管理:内存碎片、伙伴系统、slab分配器
    在上一篇博客中我介绍了Linux中对于物理内存的管理方式,这次再来介绍一下Linux对虚拟内存的管理方式

    虚拟内存空间

    即使是在现代,内存依旧是一项宝贵的资源,并且内存的管理以及访问控制向来都是难题。如果直接使用物理内存,通常都会面临以下几种问题

    • 内存缺乏访问控制,安全性不足
    • 各进程同时访问物理内存,可能会互相产生影响,没有独立性
    • 物理内存极小,而并发执行进程所需又大,容易导致内存不足
    • 进程所需空间不一,容易导致内存碎片化问题。

    基于以上几种原因,Linux通过mm_struct结构体来描述了一个虚拟的,连续的,独立的地址空间,也就是我们所说的虚拟地址空间

    在建立了虚拟地址空间后,并没有分配实际的物理内存,而当进程需要实际访问内存资源的时候就会由内核的请求分页机制产生缺页中断,这时才会建立虚拟地址和物理地址的映射,调入物理内存页。通过这种方法,就能够保证我们的物理内存只在实际使用时才进行分配,避免了内存浪费的问题。

    下图则为Linux下的虚拟地址空间
    在这里插入图片描述
    在Linux中,虚拟内存空间的内部又被划分为用户空间内核空间


    用户空间

    用户空间即进程在用户态下能够访问的虚拟地址空间,每个进程都有自己独立的用户空间,在32位系统下总容量为3G
    在这里插入图片描述
    用户空间由以下部分组成

    • :栈用来存放程序中临时创建的局部变量,如函数的参数、内部变量等。每当一个函数被调用时,就会将参数压入进程调用栈中,调用结束后返回值也会被放回栈中。同时,每调用一次函数就会创建一个新的栈,所以在递归较深时容易导致栈溢出。栈内存的申请和释放由编译器自动完成,并且栈容量由系统预先定义。栈从高地址向低地址增长
    • 文件映射段:也叫共享区,文件映射段中主要包括共享内存、动态链接库等共享资源,从低地址向高地址增长。
    • :堆用来存放动态分配的内存。堆内存由用户申请分配和释放,从低地址向高地址增长。
    • BSS段:BSS段用来存放程序中未初始化的全局变量和静态变量
      -数据段:数据段用来存放程序中已初始化全局变量与静态变量
    • 代码段:代码段用来存放程序执行代码,也可能包含一些只读的常量。这块区域的大小在程序运行时就已经确定,并且为了防止代码和常量遭到修改,代码段被设置为只读。

    内核空间

    内核空间即进程陷入内核态后才能够访问的空间。虽然每个进程都具有自己独立的虚拟地址空间,但是这些虚拟地址空间中的内核空间 ,其实都关联的是同一块物理内存,如下图。
    在这里插入图片描述
    通过这种方法,保证了进程在切换至内核态后能够快速的访问内核空间。

    在32位系统中,内核空间的大小为1G,从0xC0000000到0xFFFFFFFF
    在这里插入图片描述

    如上图,内核空间主要分为直接映射区高端内存映射区两部分

    直接映射区

    从内核空间起始位置开始,从低地址往高地址增长,最大为896M的区域即为直接映射区。

    直接映射区的896M的虚拟地址与物理地址的前896M进行直接映射,所以虚拟地址和分配的物理地址都是连续的。

    那么它们是如何转换的呢?其实它们之间存在着一个偏移量PAGE_OFFSET,偏移量的大小即为0xC0000000

    所以虚拟地址 = PAGE_OFFSET + 物理地址

    高端内存映射区

    在上面也提到了,内核空间利用直接映射区来将896M的内存直接映射到物理内存中,但是我们的物理内存远远不止这么点,那么对于剩下的物理内存的寻址工作,就交给了高端内存映射区

    由于我们的内核空间只有1G,而直接映射区又占据了896M,因此我们将剩下的128M空间划分成了三个高端内存的映射区,从上往下分别是固定内存映射区,永久内存映射区,动态内存映射区

    • 动态内存映射区该区域的特点是虚拟地址连续,但是其对应的物理地址并不一定连续。该区域使用内核函数vmalloc进行分配,分配的虚拟地址的物理页可能会处于低端内存,也可能处于高端内存
    • 永久内存映射区该区域可以访问高端内存。使用alloc_page(_GFP_HIGHMEM)分配高端内存页,或者使用kmap将分配的高端内存映射到该区域
    • 固定内存映射区该区域的每个地址项都服务于特定的用途,如ACPI_BASE

    用户空间内存分配

    malloc

    了解C语言的同学都应该知道,在C语言中我们可以使用malloc来在用户空间中动态的分配内存,而malloc作为库函数,其本质就是对系统调用进行了一层封装,因此在不同的系统下其实现不同。

    在Linux中,当我们申请的内存小于128K时,malloc会使用sbrk或者brk区分配内存。而当我们申请大于128K的大块空间时,会使用mmap映射区进行分配。

    但是由于上述的brk/sbrk/mmap都属于系统调用,因此当我们每次调用它们时,就会从用户态切换至内核态,在内核态完成内存分配后再返回用户态。

    倘若每次申请内存都要因为系统调用而产生大量的CPU开销,那么性能会大打折扣。并且从上面的图我们也可以看出来,堆是从低地址往高地址增长,如果低地址的内存没有被释放,则高地址的内存就不能被回收,就会产生内存碎片的问题。

    malloc是如何实现解决这个问题的呢?

    为了减少内存碎片和系统调用的开销,malloc在底层采用了内存池来解决这个问题。

    它会先申请大块内存作为堆区,然后将这块内存拆分为多个不同大小的内存块,以作为内存管理的基本单位。同时,会使用隐式链表来连接所有的内存块,包括已分配块和未分配块。为了方便内存空闲块的管理,malloc采用显式链表来管理所有的空闲块

    当我们调用malloc进行内存分配时,就会去搜索空闲链表,找到满足需求的内存块,如果内存块过大,则会将内存块拆分为两部分,即一部分用来分配,另一部分则变为新的空闲块。

    同理,当我们释放内存块时,会通过遍历隐式链表,判断释放块前后内存块是否空闲,来决定是否需要合并内存块


    内核空间内存分配

    在内核空间中,通过与malloc类似的两个系统调用来进行内存的分配,它们 分别是kmallocvmalloc

    kmalloc

    kmalloc与上面介绍的用户空间的malloc函数非常类似,其用于为内核空间的直接内存映射区分配内存。

    kmalloc以字节为分配单位,通常用于分配小块内存,并且kmalloc确保分配的页在物理地址上是连续的(虚拟地址也必然连续)。并且kmalloc为了防止内存碎片的问题,其底层页面分配算法是基于slab分配器实现的。

    vmalloc

    vmalloc用于为内核空间中的动态内存映射区进行内存分配。

    vmalloc的工作方式与kmalloc类似,不同的地方在于vmalloc分配的内存只保证了虚拟地址是连续的,而物理地址不一定连续。它通过分配非连续的物理内存块,再通过修正页表的映射关系,把内存映射到虚拟地址空间的连续区域,就能够做到这一点。

    在这里插入图片描述
    如上图,就是内核空间中进行内存分配的具体流程

    展开全文
  • Linux>内存管理,虚拟内存管理,brk和mmap

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

    1.每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址;
    2.虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址;
    3.如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

    内存分配的原理

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

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

     维护一个位置。brk/sbrk改变这个位置
    		brk改变绝对位置。
    		sbrk相对改变位置。
    

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

    void *mmap(
                void *start,//指定映射的虚拟地址 0由系统指定开始位置)
                size_t length,//映射空间大小 pagesize倍数
                int prot,//映射权限  PROT_NONE | PROT_READ PROT_WRITE PROT_EXEC
                int flags,//映射方式MAP_ANONYMOUS MAP_SHARED和MAP_PRIVATE二选一 等
                int fd,//文件描述符号
                offset_t off);//文件中的映射开始位置(必须是pagesize的倍数)
    

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

    参考:https://blog.csdn.net/weixin_36145588/article/details/78363836

    展开全文
  • Linux虚拟内存管理

    千次阅读 2017-05-22 21:21:15
    Linux的虚拟内存管理有几个关键概念: 每个进程有独立的虚拟地址空间,进程访问的虚拟地址空间并不是真正的物理地址 虚拟地址可通过每个进程上页表与物理地址进行映射,获得真正的物理地址 如果虚拟地址所对应...
  • 一、页式虚拟内存管理概念 目标: 使得大的程序能在较小的内存中运行 使得多个程序能在较小的内存中运行(能容纳) 使得多个程序并发运行时地址不冲突(方便,高效) 使得内存利用效率高:无碎片,共享方便 ...
  • 浅析虚拟内存管理

    千次阅读 2015-07-23 12:29:38
    1.进程各段在内存中的分布在谈虚拟内存管理之前,我想先介绍一下进程在执行时各内存段(这里不做具体介绍,读者可自行了解)在X86体系下的分布情况: 内核映射到程序虚拟内存,但程序无法访问 argv,environ ...
  • 1.页式虚拟内存管理概念 物理内存(即实内存)管理 虚拟内存目标: 虚拟内存的实现思路:在程序运行时,只把当前必要的很小的一部分代码和数据装入内存中。其余代码和数据需要时再装入。不再运行的代码和数据及时从...
  • 虚拟内存管理 文章目录虚拟内存管理一、虚拟内存的基本概念1. 传统存储管理方式的缺点2. 虚拟内存的定义3. 虚拟内存的特征4. 知识回顾二、请求分页管理方式1. 页表机制2. 缺页中断机构3. 知识回顾三、页面置换算法1....
  • C#图形界面,基于linux伙伴算法,对虚拟内存管理。包含可视化窗格,界面简单易懂
  • 操作系统思维导图——虚拟内存管理
  • 四川大学虚拟内存管理实验代码,实现虚拟内存,并处理缺页中断
  • 深入理解Linux虚拟内存管理

    热门讨论 2007-09-02 11:44:20
    深入理解Linux虚拟内存管理
  • 实现虚拟内存管理的nachos操作系统实验代码
  • 内存管理:页式虚拟内存管理

    千次阅读 2019-12-17 21:00:38
    页式存储管理——虚拟内存——缺页中断,页面替换算法 开章明意: 创建一个进程(创建进程是在磁盘中),进程以字节为单位编号,然后再进程分为许多页(每页4KB),内存中有对应的页框(设定同页)。通过页表(记录...
  • 3.2 虚拟内存管理

    千次阅读 2020-10-21 22:01:38
    3.2.1 虚拟内存的基本概念 1、传统存储管理方式的特征 1)一次性: 作业必须一次性全部装入内存后,才开始运行 2)驻留性:作业被装入内存后,就一直驻留在内存中 2、局部性原理: 时间局部性和空间局部性 时间局部...
  • 虚拟内存管理的作用/好处

    千次阅读 2016-05-09 11:59:36
    虚拟内存管理的作用/好处
  • 操作系统学习笔记(三):虚拟内存管理虚拟内存管理虚拟存储覆盖交换虚拟存储局部性原理 虚拟内存管理 虚拟存储 覆盖 需要程序员划分功能模块 增加编程的复杂度 增加编程时间 从外存装入覆盖模块 时间换空间 ...
  • 在基本的内存管理策略中,所有的策略的相同点都是:每个进程在执行之前需要完全处于内存中。那有没有一种方法可以不需要将进程所有页面加载到内存中就可以开始运行进程呢?有没有可能在进程需要某些页面时再将其调入...
  • XV6源代码阅读-虚拟内存管理 Exercise1 源代码阅读 1.内存管理部分: kalloc.c vm.c 以及相关其他文件代码 kalloc.c:char * kalloc(void)负责在需要的时候为用户空间、内核栈、页表页以及缓冲区分配...
  • 深入理解linux虚拟内存管理(中+英)

    热门讨论 2012-12-02 19:55:30
    深入理解linux虚拟内存管理(中+英) 中文+英文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,291
精华内容 5,716
关键字:

虚拟内存管理