Windows内存机制解析(http://blog.youkuaiyun.com/leezy_2000)

Windows 内存机制说明 1.内存分布概述        每个进程都有自己的虚拟地址空间,对于32位进程来说,这个地址空间的大小为4GB,这是因为32位指针可以表示从0x00000000到0xFFFFFFFF之间的任意一值。对于64位进程来说,由于64位指针可以表示从0x00000000’00000000到0xFFFFFFFF’FFFFFFFF之间的任一值,因此这个地址空间大小为16EB。因为每个进程都有自己专用的地址空... 阅读详情


Windows内存机制解析

By leezy_2000 03-9-3 9:38

前言

 

写这篇文章之前相当长的一段时间里,对windows内存机制是有着相当的困惑的。各个进程的内存空间是如何隔离和共享的?GDT(全局描述表)尚在,可分段机制去了那里?既然我们有虚拟的4G空间和结构化异常为何分配内存仍可能失败?在什么时候stack会溢出?―――

当我把这些问题都弄清楚后,我写了这篇文章为自己做了个总结,希望对大家也有帮助。同时由于写Windows内存这块的文章比较多,我将尽力做到与别人的内容不重合。

动笔后不久,我发现imquestion对于Windows内存写了几篇非常不错的文章,总题目叫《JIURL玩玩Win2k内存篇》,推荐阅读。

 

一、总论

 

       Windows内存管理机制,底层最核心的东西是分页机制。分页机制使每个进程有自己的4G虚拟空间,使我们可以用虚拟线性地址来跑程序。每个进程有自己的工作集,工作集中的数据可以指明虚拟线性地址对应到怎样的物理地址。进程切换的过程也就是工作集切换的过程,如Matt Pietrek所说如果只给出虚拟地址而不给出工作集,那这个地址是无意义的。(见图一)

 

       在分页机制所形成的线性地址空间里,我们对内存进行进一步划分涉及的概念有堆、栈、自由存储等。对堆进行操作的API有HeapCreate、HeapAlloc等。操纵自由存储的API有VirtualAlloc等。此外内存映射文件使用的也应该算是自由存储的空间。栈则用来存放函数参数和局部变量,随着stack frame的建立和销毁其自动进行增长和缩减。

 

       说到这里,也许有人会提出疑问:对x86 CPU分段机制是必须的,分页机制是可选的。为什么这里只提到了分页机制。那么我告诉你分段机制仍然存在,一是为了兼容以前的16位程序,二是Windows毕竟要区分ring 0和ring 3两个特权级。用SoftIce看一下GDT(全局描述表)你基本上会看到如下内容:

 

GDTbase=80036000 Limit=03FF

 

0008 Code32 Base=00000000 Lim=FFFFFFFF DPL=0 P RE 

//内核态driver代码段

 

0010 Data32 Base=00000000 Lim=FFFFFFFF DPL=0 P RW

//内核态driver的数据段

 

001B Code32 Base=00000000 Lim=FFFFFFFF DPL=3 P RE 

//应用程序的代码段

 

0023 Data32 Base=00000000 Lim=FFFFFFFF DPL=3 P RW 

//应用程序的数据段

 

这意味着什么呢?

 

我们再看一下线性地址的生成过程(见图一)。从中我们应该可以得出结论,如果segmeng base address为0的话,那么这个段可以看作不存在,因为偏移地址就是最终的线性地址。

 

此外还有两个段存在用于Kernel Processor Control Region和user thread environment block。所以如果你在反汇编时看到MOV ECX,FS:[2C]就不必惊讶,怎么这里使用逻辑地址而不是线性地址。在以后涉及异常处理的地方会对此再做说明。

 

 

二、从Stack说开去

 

从我个人的经验看,谈到内存时说堆的文章最多,说stack的最少。我这里反其道而行的原因是stack其实要比堆更重要,可以有不使用堆的程序,但你不可能不使用stack,虽然由于对stack的管理是由编译器确定了的,进而他较少出错。

 

通过链接开关/STACK:reserve[,commit]可以指定进程主线程的stack大小,当你建立其他线程时如果不指定dwStackSize参数,则也将使用/STACK所指定的值。微软说,如果指定较大的commit值将有利于提升程序的速度,我没验证过,但理应如此。通常并不需要对STACK进行什么设定,缺省的情况下将保留1M空间,并提交两个页(8K for x86)。而1M空间对于大多数程序而言是足够的,但为防止stack overflow有三点需要指出一是当需要非常大的空间时最好用全局数组或用VirtualAlloc进行分配,二是引用传递或用指针传递尺寸较大的函数参数(这点恐怕地球人都知道),三是进行深度递归时一定要考虑会不会产生stack溢出,如果有可能,可以采用我在《递归与goto》一文中提到的办法来仿真递归,这时候可以使用堆或自由存储来代替stack。同时结构化异常被用来控制是否为stack提交新的页面。(这部分写的比较简略因为很多人都写过,推荐阅读Jeffery Ritcher《Windows核心编程》第16章)

 

下面我们来看一下stack的使用。

假设我们有这样一个简单之极的函数:

 

 

int __stdcall add_s(int x,int y)

{

       int sum;

 

       sum=x+y;

 

       return sum;

}

 

这样在调用函数前,通常我们会看到这样的指令。

mov         eax,dword ptr [ebp-8]

push        eax

mov         ecx,dword ptr [ebp-4]

push        ecx

此时把函数参数压入堆栈,而stack指针ESP递减,stack空间减小。

 

在进入函数后,你将会看到如下指令:

push        ebp

mov         ebp,esp

sub         esp,44h

这三句建立stack框架,并减小esp为局部变量预留空间。建立stack框架后,[ebp+*]指向函数参数,[ebp-*]指向局部变量。

 

另外在很多情况下你会看到如下三条指令

push        ebx

push        esi

push        edi

这三句把三个通用寄存器压入堆栈,这样这三个寄存器就可以用来存放一些变量,进而提升运行速度。

很奇怪,我这个函数根本用不到这三个寄存器,可编译器也生成了上述三条指令。

 

对stack中内容的读取,是靠基址指针ebp进行的。所以对应于sum=x+y;一句你会看到

mov         eax,dword ptr [ebp+8]

add         eax,dword ptr [ebp+0Ch]

mov         dword ptr [ebp-4],eax

其中[ebp+8]是x,[ebp+0Ch]是y,记住压栈方向为从右向左,所以y要在x上边。

 

我们再看一下函数退出时的情况:

pop         edi

pop         esi

pop         ebx

mov         esp,ebp

pop         ebp

ret         8

此时恢复stack框架,使esp与刚进入这个函数时相同,ret 8使esp再加8,使esp与没调用这个函数的时候一致。如果使用__cdecl调用规则,则由调用方以类似add  esp,8进行清场工作,使stack的大小与未进行函数调用时一致。Stack的使用就这样完全被编译器实现了,只要不溢出就和我们无关,也许也算一种内存的智能管理。最后要补充的两点是:首先stack不像heap会自动扩充,如果你用光了储备,他会准时溢出。其次是不要以为你使用了缺省参数进行链接,你就有1M的stack,看看启动代码你就知道在你拥有stack之前,C Run –Time

    Library以用去了一小部分stack的空间。




 

                           by leezy_2000

                           2003-10-8 17:01

三、浅谈一下Heap

(鉴于Matt Pietrek在它的《Windows 95 系统程式设计大奥秘》对9x系统的heap做了非常详细的讲解,此处涉及的内容将仅限于Win2000)

 

Heap与Stack正好相反,你需要手动来管理每一块内存的申请和释放(在没有垃圾收集机制的情况下),而对于C/C++程序员来说,操作Heap的方式实在是太多了点。下面是几乎所有可以操作堆内存的方法的列表:

malloc/free

new/delete

GlobalAlloc/GlobalFree

LocalAlloc/LocalFree

HeapAlloc/HeapFree

 

其中malloc/free由运行时库提供,new/delete为C++内置的操作符。他们都使用运行时库的自己的堆。运行时库的在2000和win9x下都有自己独立的堆。这也就意味着只要你一启动进程,你将至少有两个堆,一个作为进程缺省,一个给C/C++运行时库。

 

GlobalAlloc/GlobalFree和LocalAlloc/LocalFree现在已失去原有的含义,统统从进程缺省堆中分配内存。

 

HeapAlloc/HeapFree则从指定的堆中分配内存。

 

单就分配内存而言(new/delete还要管构造和析构),所有这些方式最终要归结到一点2000和98下都是是HeapAlloc。所以微软才会强调GlobalAlloc/GlobalFree和LocalAlloc/LocalFree会比较慢,推荐使用HeapAlloc,但由于Global**和Local**具有较简单的使用界面,因此即使在微软所提供的源代码中他们仍被大量使用。必须指出的是HeapAlloc并不在kernel32.dll中拥有自己的实现,而是把所有调用转发到ntdll.RtlAllocateHeap。下面这张从msdn中截取的图(图2),应该有助于我们理解同堆相关的API。

 

堆内部的运作同SGI STL的分配器有些类似,大体上是这样,OS为每个堆维护几个链表,每个链表上存放指定大小范围的区块。当你分配内存时,操作系统根据你所提供的尺寸,先确定从那个链表中进行分配,接下来从那个链表中找到合适的块,并把其线性地址返还给你。如果你所要求的尺寸,在现存的区块中找不到,那么就新分配一块较大的内存(使用VirtualAlloc),再对他进行切割,而后向你返还某一区块的线性地址。这只是一个大致的情形,操作系统总在不停的更新自己的堆算法,以提高堆操作的速度。

堆本身的信息(包括标志位和链表头等)被存放在Heap Header中,而堆句柄正是指向Heap Header的指针,Heap Header的结构没有公开,稍后我们将试着做些分析。非常有趣的是微软一再强调只对toolhelp API有效的HeapID其实就是堆句柄。

 

原来是准备分析一下堆内部的一些结构的,可后来一想这么做实用价值并不是很大,所需力气却不小。因此也就没具体进行操作。但这里把实现监测堆中各种变化的小程序的实现思路公开一下,希望对大家有所帮助。这个小程序非常的简单,主要完成的任务就是枚举进程内所有的堆的变化情况。由于涉及到比较两个链表的不同,这里使用了STL的vector容器和某些算法来减少编码。同时为了使STL的内存使用不对我们要监测的对象产生干扰,我们需要建立自己的分配器,以使用我们单独创建的堆。此外还需要特别注意的一点是由于toolhelp API Heap32Next在运行过程中不允许对任何堆进行扰动(否则他总返回TRUE),导致我们只能使用vector,并预先保留足够的空间。(访问堆内部某些信息的另一种方式是使用HeapWalk API,看个人喜好了)。

 

程序的运行过程是这样的,先对当前进程中存在的堆进行枚举,并把结果存入一个set类型的变量heapid1,接下来创建自己的堆给分配器使用,并对进程中存在的堆再次进行枚举并把结果存入另一个set类型的变量heapid2,这样就可以调用set_difference求出我们新建堆的ID,再以后列举队内部的信息时将排除这个ID所表示的堆。接下来就可以在两点之间分别把堆内部的信息存入相应的vector,比较这两个vector,就可以得到对应于分配内存操作,堆内部的变化情况了。

 

(图2 from msdn by Murali R. Krishnan)

 

下面是一些悬而未决的问题,那位感兴趣可以自己探索。

 

Heap Header结构是什么样的?

 

堆内部对内存块的组织方式?(是链表么)

 

每一个小块的描述信息在那里?(如果是链表,那就应该有指针使这些小块彼此相连。)




 

//myallocator.h

#ifndef _MYALLOCATOR_
#define _MYALLOCATOR_


#include <iostream>
#include <windows.h>


namespace MyLib {
   template <class T>
   class MyAlloc {
     public:
    static HANDLE hHeap;
       // type definitions
       typedef T        value_type;
       typedef T*       pointer;
       typedef const T* const_pointer;
       typedef T&       reference;
       typedef const T& const_reference;
       typedef size_t    size_type;
       typedef ptrdiff_t difference_type;

       // rebind allocator to type U
       template <class U>
       struct rebind {
           typedef MyAlloc<U> other;
       };

       // return address of values
       pointer address (reference value) const {
           return &value;
       }
       const_pointer address (const_reference value) const {
           return &value;
       }

       /* constructors and destructor
        * - nothing to do because the allocator has no state
        */
       MyAlloc() throw() {
       }
       MyAlloc(const MyAlloc&) throw() {
       }
       ~MyAlloc() throw() {
       }

       // return maximum number of elements that can be allocated
       size_type max_size () const throw() {
     size_type N;
           N=(size_type)(-1)/ sizeof(T);

     return  (0 < N ? N : 1);
       }

       // allocate but don't initialize num elements of type T
       pointer allocate (size_type num, const void* = 0) {
           // print message and allocate memory with global new
           /*std::cerr << "allocate " << num << " element(s)"
                     << " of size " << sizeof(T) << std::endl;
   */
           pointer ret = (pointer)(HeapAlloc(hHeap,0,num*sizeof(T)));
          // std::cerr << " allocated at: " << (void*)ret << std::endl;
           return ret;
       }
  char *_Charalloc(size_type N)//vc 所附带的stl的特色
  {
   return (char*)HeapAlloc(hHeap,0,N*sizeof(T));
  }
       // initialize elements of allocated storage p with value value
       void construct (pointer p, const T& value) {
           // initialize memory with placement new
           new((void*)p)T(value);
       }

       // destroy elements of initialized storage p
       void destroy (pointer p) {
           // destroy objects by calling their destructor
           p->~T();
       }

       // deallocate storage p of deleted elements
    //原本应该为pointer
       void deallocate (void* p, size_type num) {
           // print message and deallocate memory with global delete
           /*
     std::cerr << "deallocate " << num << " element(s)"
                     << " of size " << sizeof(T)
                     << " at: " << (void*)p << std::endl;
   */
           HeapFree(hHeap,0,(void*)p);
       }
   };

   // return that all specializations of this allocator are interchangeable
   template <class T1, class T2>
   bool operator== (const MyAlloc<T1>&,
                    const MyAlloc<T2>&) throw() {
       return true;
   }
   template <class T1, class T2>
   bool operator!= (const MyAlloc<T1>&,
                    const MyAlloc<T2>&) throw() {
       return false;
   }
}//end namespace MyLib
#endif

 

//teststlmem.cpp

/*
 written by leezy_2000
 03-9-5 15:12
*/
#include "stdafx.h"

#pragma warning(disable:4786)

//#define _STLP_USE_MALLOC
#include "myallocator.h"
#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
#include <windows.h>
#include <Tlhelp32.h>

typedef unsigned long ULONG_PTR, *PULONG_PTR;

using namespace std;

/*
  本程序需要注意的几点:

  1、在实现自己的分配器,这样可以使stl容器的变化不影响我们要监测的堆

  2、容器只能用vector否则任何堆的任何变化将导致Heap32Next始终返回TRUE
  这应该是微软的bug

  3、分配内存失败的时候应该抛出std::bad_alloc内存,此处考虑不会出现低
  内存的情况,没抛出此异常。即认定自编写分配器分配内存时不会失败。
*/

//用于比较堆内存块的仿函数
//以块大小来判定两个HEAPENTRY32的大小
class HeapInfoCompare
{
public:
 bool operator() (const HEAPENTRY32& he1,const HEAPENTRY32& he2) const
 {
  return (he1.dwBlockSize < he2.dwBlockSize);
 }
};

typedef vector < HEAPENTRY32, MyLib::MyAlloc<HEAPENTRY32> > HEAPENTRYSET;

void heapinfo(HEAPENTRYSET& hset,ULONG_PTR heapid);

void getheapid(set<ULONG_PTR>& heapid)
{
 HANDLE hSnapShot=CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST,GetCurrentProcessId());
 HEAPLIST32  heaplist32;

 heaplist32.dwSize=sizeof(HEAPLIST32);

 BOOL bRet=Heap32ListFirst(hSnapShot,&heaplist32);

 while(bRet)
 {
  heapid.insert(heaplist32.th32HeapID);

  cout<<heaplist32.th32HeapID<<endl;

  bRet=Heap32ListNext(hSnapShot,&heaplist32);
 }
 CloseHandle(hSnapShot);

 cout<<"the end"<<endl;
}

HANDLE MyLib::MyAlloc<HEAPENTRY32>::hHeap=NULL;

HANDLE hHeap;

int main(int argc, char* argv[])
{
 //枚举此时所有堆并在建立新堆后再次枚举这样从中剔除新建堆
 set<ULONG_PTR> heapid1,heapid2,heapid3;
 
 getheapid(heapid1);

 hHeap=HeapCreate(0,0,0);

 getheapid(heapid2);

 insert_iterator<set<ULONG_PTR> > iter(heapid3,heapid3.begin());

 set_difference(heapid2.begin(),heapid2.end(),heapid1.begin(),heapid1.end(),
  iter);

 set<ULONG_PTR>::iterator pos;
 ULONG_PTR newheapid;

 for( pos=heapid3.begin(); pos !=heapid3.end(); ++pos)
 {
  cout<<"The new heap id is/t"<<(*pos)<<endl;
  newheapid=*pos;
 }


 MyLib::MyAlloc<HEAPENTRY32>::hHeap=hHeap;

 //vector<int, MyLib::MyAlloc<int> > v1;
 HEAPENTRYSET heapset1,heapset2,heapset3;
 
 heapset1.reserve(400);//保证vector不自动增长
 heapset2.reserve(400);
 heapset3.reserve(400);

 int size;

 heapinfo(heapset1,newheapid);
 
 sort(heapset1.begin(),heapset1.end(),HeapInfoCompare());

 size=heapset1.size();

 HANDLE hCurHeap=GetProcessHeap();

// HeapAlloc(hCurHeap,HEAP_ZERO_MEMORY,4*1024);

 char* p=new char[4*1024];

// GlobalAlloc(GHND,4*1024);

 char* q=(char*)malloc(4*1024);

 cout<< "the p is"<<(int)p<<endl;

 heapinfo(heapset2,newheapid);

 sort(heapset2.begin(),heapset2.end(),HeapInfoCompare());
 size=heapset2.size();

 insert_iterator<HEAPENTRYSET> miter(heapset3,heapset3.begin());

 set_difference(heapset2.begin(),heapset2.end(),heapset1.begin(),heapset1.end(),
  miter,HeapInfoCompare());

 size=heapset3.size();

 HEAPENTRYSET::iterator mpos;
 for( mpos=heapset3.begin(); mpos !=heapset3.end(); ++mpos)
 {
  cout<<"The size of the different block is/t"<<(*mpos).dwBlockSize<<"/tand the addresss is/t"<<(*mpos).dwAddress<<"/tdwFlags is/t"<<(*mpos).dwFlags <<endl;
  cout<<"The heapid is:/t"<<(*mpos).th32HeapID <<endl;
 }

 return 0;
}
void heapinfo(HEAPENTRYSET& hset,ULONG_PTR hid)
{
 HANDLE hSnapShot=CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST,GetCurrentProcessId());
 HEAPLIST32  heaplist32;

 heaplist32.dwSize=sizeof(HEAPLIST32);

 BOOL bRet=Heap32ListFirst(hSnapShot,&heaplist32);
 
 static int i=0;

 while(bRet)
 {
  HEAPENTRY32  he32;
  DWORD totalsize=0,freesize=0;

  if(heaplist32.th32HeapID==hid)
  {
   bRet=Heap32ListNext(hSnapShot,&heaplist32);
   continue;
  }

  DWORD number=10;
  HANDLE ProcessHeap[10];

  DWORD numget=GetProcessHeaps(number,ProcessHeap);

  HANDLE hHeap=GetProcessHeap();

  he32.dwSize=sizeof(HEAPENTRY32);

  Heap32First(&he32,heaplist32.th32ProcessID,heaplist32.th32HeapID);

  if(he32.dwFlags & LF32_FREE)
   freesize +=he32.dwBlockSize;
  
  totalsize +=he32.dwBlockSize;

  cout<< "the heapid is :"<<he32.th32HeapID<<endl;
  cout<<"the information of first block: "<< "Blocksize: "<<he32.dwBlockSize<<"/t Address: "<<(LONG)he32.dwAddress<<endl;
  
  if((he32.dwFlags & LF32_FIXED) || (he32.dwFlags & LF32_MOVEABLE))
  hset.push_back(he32);

  while(Heap32Next(&he32))
   {
    cout<< "the information of block: " << "Blocksize: "<<he32.dwBlockSize<<"/t Address: "<<(LONG)he32.dwAddress<<endl;

    totalsize +=he32.dwBlockSize;

    if(he32.dwFlags & LF32_FREE)
    freesize +=he32.dwBlockSize;
    
    //cout<< ++i <<endl;    
    if((he32.dwFlags & LF32_FIXED) || (he32.dwFlags & LF32_MOVEABLE))
    hset.push_back(he32);
    //char*p =(char*)malloc(300);

   }

  cout<<"the total size of heap is: "<<totalsize<<endl;
  cout<<"the free  size of heap is: "<<freesize <<endl;
  cout<<"the commited  size of heap is: "<<(totalsize-freesize)<<endl;

  bRet=Heap32ListNext(hSnapShot,&heaplist32);
 }

 CloseHandle(hSnapShot);

 cout<<"the end"<<endl;
}





windows内存管理 内存管理、文件映射 阅读详情

相关推荐

【数字电源/MATLAB+PLECS】如何进行 Buck 数字电源仿真(二)PLECS 搭建开环 Buck 功率级

本文介绍了如何在PLECS中搭建开环Buck功率级模型,验证数字电源设计的基本思路。重点内容包括: 明确开发顺序:先开环验证功率级,再闭环设计控制器 搭建关键组件:输入电源、PWM模块、MOSFET开关、续流二极管、LC滤波电路及负载 设置初始参数:24V输入、12V输出、200kHz开关频率、22μH电感、100μF电容 测量关键波形:MOSFET Vds验证开关动作、电感电流纹波和输出电压稳态值 提供完整仿真模型和GitHub资源,便于读者复现验证 文章强调通过开环测试先确保功率级正确性,为后续闭环控制

weixin_43234123的博客 1218

读书笔记_栈的创建过程

首先看内核栈的创建过程:PspCreateThread函数是windows内核中用于创建线程的一个重要的内部函数,无论是创建系统线程(PsCreateSystemThread)还是用户线程(NtCreateThread),都离不开这个函数,除了创建重要的ETHREAD结构,PspCreateThread函数的另一个重要的任务是创建内核态栈。 对于GUI线程,Windows会为其创建大内核栈,但是...

wodamazi 533

计算机视觉-深度学习图像检测方法梳理

计算机视觉-深度学习图像检测方法梳理 由于之后要转方向啦,趁这段时间整理手中硕士研究方向的一些阅读笔记,这是一篇关于计算机视觉的基础知识梳理 先搞清一些小知识点 首先我们要弄清楚图像分类、目标定位、语义分割、实例分割的区别 a. 图像分类 :给定一张输入图像,图像分类任务旨在判断该图像所属类别; b. 目标定位 :在图像分类的基础上,我们还想知道图像中的目标具体在图像的什么位置,通常是以包围盒的(bounding box)形式; c. 语义分割 :语义分割是目标检测更进阶的任务,目标检测只需要框出每个目

qq_41662991的博客 9953

windows内存管理操作

堆内存,虚拟内存 , 文件映射

ylh的博客 2150

408计算机复试专业课问答汇总

408计算机复试专业课问答汇总

weixin_45930223的博客 3698

ubuntu 20.04系统离线安装nfs

ubuntu环境下,离线安装nfs

m0_62464865的博客 2241

Windows内存工作原理详解

Windows内存管理系统是操作系统的核心组件,负责物理内存和虚拟内存的管理、分配和保护。现代Windows系统(Windows 10/11)采用基于虚拟内存的复杂内存管理机制,支持32位和64位架构。

m0_63331248的博客 1279

“挑战杯”大赛“揭榜挂帅”赛道-baseline解析

在这个脚本中,txt文件的保存路径为'yolov7/datasets/Fatigue_driving_detection/txt_labels/',而xml文件的路径为'./label_xml/'。标注图片后的产物是会给每一个图片对应生成一个xml格式的文件,但是如果要用yolo框架去训练模型,输入的数据必须是txt格式,因此这里有一步转换格式的过程。注意看:obs这里有三个文件夹,第一个是关键帧的文件夹A,第二个是标注时输出的位置为文件夹B,第三个是智能标注后和手动标注的合集文件夹C。

m0_62899814的博客 2321

全面介绍Windows内存管理机制及C++内存分配实例

(一):进程空间   本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。 本文目的: 对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。 1.      进程地址空间 1.1地址空间 ·        32|

zhujunwsk的专栏 3143

【Rcode】生存分析: KM & COX回归& 随机森林& nomogram

本文使用了R——survival包下的lung数据进行方法测试: 1)KM 2) Cox回归:多因素 3)随机森林因子(根据cox回归结果) 4)nomogram ( 根据cox回归结果,建立了中位生存时间,1年5年生存率的概率计算) -------------------????????‍♀️本文只有干货,非常干!????----------------------------- 一、数据加载 #生存分析 library("survival") library("survminer") data("

weixin_43172152的博客 9273

2025华为杯第二十二届中国研究生数学建模竞赛A题:通用神经网络处理器下的核内调度问题 - 完整解题分析+完整MATLAB源码!

🎓 本文收录于 《滚雪球学数学建模》专栏,本专栏专注于从零基础出发,带你循序渐进掌握数学建模的核心方法与思维路径。通过“滚雪球式”学习法,从最简单的生活化案例到经典建模工具,再到省赛、国赛真题实战演练,逐步积累知识与技能,帮助你搭建完整的建模体系。无论你是刚接触建模的新生,还是希望提升科研与竞赛能力的学者与工程师,本专栏都将为你提供一条清晰、高效、可实操的建模成长路线,助力你快速进阶,突破难点,迈向数学建模竞赛与实际应用的更高峰 🚀!

**My Coding Family** 2051

Windows的内存管理机制

内存管理方式建议大家多看看计算机组成原理,回过头来看windows的内存管理机制就能更好的明白windows为啥这么做~而不是直接让你操作物理内存

Albert_weiku的博客 4665

threejs中使用Reflector实现出倒影效果,并实现背景透明的效果(Reflector镜面背景透明, 倒影弱化)

在项目中遇到要实现倒影效果,threejs给我们提供了Reflector,可以轻松完成镜面效果,实现倒影。但背景不透明;并且反射出的倒影太亮了;网上没搜到解决方案, 自己修改了下源码实现了想要的效果,做个记录...

weixin_40856652的博客 5720

Windows内存机制

Windows内存机制 1 基本原理 Cr3寄存器 描述: 在所有的寄存器中,只有Cr3存储的是物理地址,其它寄存器存的都是线性地址 Cr3所存储的物理地址指向了一个页目录表(PDT-Page Directory Table) 在Windows中,一个页的大小通常为4KB,即一个页可以存储1024个页目录表项(PDE) PD/PDE(Page Directory Table/Entry 页目录表/项) 描述: 页目录表(PDT)的每一项元素称为页目录表项(PDE) 每个页目录表项指向一个页表(PTT

RatOnSea的博客 2088

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Windows内存管理(二):内存架构 浅谈一二

windows内存管理

Mr.Sugarcane 2431

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Windows管理内存的3种方式——堆、虚拟内存、共享内存

本文主要介绍了Windows管理内存的3种方式——堆、虚拟内存、共享内存

Tandy12356_的博客 5748

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

第十二章:纤程

1. 线程时在内核中实现的,纤程是在用户模式下实现(即windows不负责对前程进行处理). 将线程->纤程:PVOID ConvertThreadToFiber(PVOID pvParam); 函数功能:这个函数会为纤程的执行上下文分配大约200字节的内存.上下文的组成如下: ● 一个用户自定义值(他被初始化为传给ConvertThreadToFiber的pvParam参数的 值 ● 结

sunliangyuan的专栏 1282

物理地址、虚拟地址、逻辑地址、线性地址、虚拟内存

1.物理地址 用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。 在实地址模式(因为实模式没有分段或分页机制,Cpu不进行自动地址转换)下,程序员操作的就是物理地址,所谓的物理地址就是物理内存上的32位地址,即物理地址可以直接定位到物理内存上的位置,无论任何操作,最终都必须要得到物理地址才能在物理内存上进行操作. 2.虚拟地址 操作系统都提供了虚拟内存(virtual mem

林竹清_清清清 4084

探索Windows的内存机制

 内存是操作系统的核心部分,所以我们非常有必要了解内存的分配机制。在DOS下,访问内存的指针是用段地址:偏移量来表示,所有程序共用一个内存空间,由低向高分配内存空间,所以任何程序都可以随便修改内存中的数据,包括不属于自己程序的内存空间和中断向量表。而且所有程序被局限在1M的基本内存(Base Memory)中,不能直接访问扩充内存。对于Windows下的程序来说,它所访问的内存地址不再是真实的。

蓝色的海的专栏 1356

windows下的内存管理

1.Windows的内存结构 Windows系统中的每个进程都被赋予它自己的虚拟地址空间。对于32位进程来说,这个地址空间是4GB,因为32位指针可以拥有从0x00000000至0xFFFFFFFF之间的任何一个值。对于64位进程来说,则这个空间是16EB。由于每个进程可以接收它自己的私有的地址空间,因此当进程中的一个线程正在运行时,该线程也只能访问只属于它的进程的内存。属于所有其他进程

鑫的专栏 1024

Windows内存管理总结

Windows内存管理

qq_43147121的博客 1059

Windows内存原理与内存管理

<br />1.进程地址空间<br />    Windows为每个进程分配了4GB的虚拟地址空间,让每个进程都认为自己拥有4GB的内存空间,4GB怎么来的? 32位 CPU可以取地址的空间为2的32次方,就是4GB.    当我们在Windows中双击一个应用程序图标后,系统为该应用程序创建一个进程,Windows使得每个进程都拥有2GB的地址空间,这2GB地址空间用于程序存放代码,数据,堆栈,自由存储区(堆),另外2GB用于共享系统使用.<br /><br />2.虚拟地址到实际地址的映射<br /><

jackmacro的专栏 1128

WINDOWS核心编程——Windows内存管理

想要了解Windows内存体系结构首先要对系统的内存的分段分页和进程隔离机制要有所了解。系统为了对进程进行隔离,使得每个进程只能访问自己申请的内存而不能访问其他进程的内存资源,对每个进程的内存使用线性地址编制,在通过内存的分页机制在进程需要访问物理内存时通过进程的页表找到世界的物理内存的地址通过系统读写内存中的数据。在早期总线(20位寻址1M)大于寄存器(16位寻址64k)的情况下为了表示更多的物

pokeyode的专栏 9388

第13章 Windows内存体系结构

https://www.cnblogs.com/5iedu/p/4846223.htmlWindows核心编程http://www.cnblogs.com/5iedu/category/699744.html13.1 Windows的虚拟地址空间安排13.1.1虚拟地址空间的分区(即虚拟地址空间布局) 进程的地址空间划分分区x86 32位Windows3GB用户模式下的x8632位WindowsX...

chenyijun的专栏 986
上一篇: Samba-HOWTO-Collection中文翻译版(2.20)
下一篇: 说说字符集(http://blog.youkuaiyun.com/leezy_2000)
freexploit
博客等级 码龄22年 315粉丝 778原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值