北京的医疗网站建设,毕业设计网站开发的目的和意义,开发网站需要时间,校园官方网站建设的书籍一、操作系统管理内存概述
在 Windows 操作系统中#xff0c;每个进程都被分配了 4GB 的虚拟地址空间#xff0c;这被称为进程的虚拟地址空间。虚拟地址空间提供了一个抽象的地址空间#xff0c;使得每个进程都可以认为它拥有自己的独立内存空间。这个虚拟地址空间被分为两…一、操作系统管理内存概述
在 Windows 操作系统中每个进程都被分配了 4GB 的虚拟地址空间这被称为进程的虚拟地址空间。虚拟地址空间提供了一个抽象的地址空间使得每个进程都可以认为它拥有自己的独立内存空间。这个虚拟地址空间被分为两个部分即用户空间和内核空间。这个虚拟地址空间的前 2GB 是给操作系统内核使用的这个部分被称为内核空间。内核空间是由操作系统内核所控制和管理的。它包含了操作系统所需的内存例如内核代码、驱动程序和系统数据结构等。在 Windows 操作系统中内核空间的大小是固定的且不可更改。
现在我来讲一下重点内容
有很多同学搞不懂虚拟内存是个什么东西首先我声明一下我们这里讲的是每个进程拥有的独立的4GB虚拟内存空间这4GB虚拟内存空间是操作系统给每个进程许诺的一张空头支票平时这4GB空间不会真的分配给你而是当你程序的一部分真正需要运行的时候操作系统才会为你分配物理页也就是真正的物理内存。再说通俗一点虚拟内存就是你代码编译的时候编译器给你写死的一个地址你的变量存放的位置(比如数组变量int类型的变量之类的)在编译的时候已经写死了包括你通过malloc动态申请的内存这些虚拟地址写死在exe里面真正执行的时候操作系统需要将虚拟内存给物理内存之间建立一一映射关系这种映射关系就记录在操作系统的PCB里面然后上处理机执行的时候会通过MMU自动将虚拟地址转换为物理地址这里不再赘述可以简单提示一下MMU无非就是从PCB里面取出来当前进程的基地址然后从虚拟地址里获取偏移就可以正确映射物理地址了至于怎么获取偏移感兴趣的同学可以自行查找资料。这里顺便说一下你64位的程序也可以把虚拟地址设置为4GB你只要映射的时候物理地址的基地址选取好他的寻址空间同样可以扩充到8G16G32G等等这里的4GB虚拟地址空间和你的寻址范围没有什么必然联系 而虚拟地址空间的后 2GB 则是给用户进程使用的这个部分被称为用户空间。用户空间中存放着进程所需的代码、数据和堆栈等。用户空间的大小是由操作系统所分配的内存大小决定的它是可以动态调整的也就是说当进程需要更多的内存时操作系统会自动为进程分配更多的空间。
在 Windows 操作系统中虚拟地址空间中的每一个地址都是虚拟地址每一个虚拟内存空间都是和操作系统的物理页一一对应的。即虚拟内存通过操作系统内核来将其转换为物理地址从而能够真正访问对应的内存空间。这个地址转换过程是透明的进程并不需要关心物理地址是什么。这样的虚拟地址转换机制保证了每个进程在使用内存时的独立性也避免了进程之间相互干扰的情况发生。为什么能够保证内存物理空间上的隔离这是因为操作系统在为进程分配内存的时候首先选择一块空闲的真实物理内存页然后将分配页的基址以及分配空间的大小写在进程的PCB当中这样每个程序加上偏移所得到的虚拟地址与物理内存一一映射从而保证了进程之间的相互隔离。
二、堆内存
我们都知道栈是操作系统给运行中的程序的分的一块确定大小的内存这个内存大小是在编译的过程中就已经写死了由编译器提交给OS来分配确定大小的内存空间而堆是当我们在程序运行的过程当中想使用一块儿不确定大小内存时动态申请的通常是在堆里面进行分配内存即从虚拟内存中割出来一小块作为堆内存来使用。
我们真正想要申请内存只有两个办法一是通过VirtualAlloc二是通过CreateFileMapping这两个函数我们用的HeapAlloc其实也是一种假申请我们平常用的new(newmalloc构造函数)和malloc他俩也和内存申请没有关系他们名义上是指申请内存实际上根本就不是malloc是从已经申请好的虚拟内存中割出来一小块供自己使用VirtualAlloc则可以看成是向操作系统批发物理页的一个过程在这个过程中的内存申请是真实存在的而malloc是假申请它是在这块虚拟内存本身就已经申请过了归你了的基础上自己从中割出一小块内存拿来使用。堆内存和栈内存都是在进程启动时操作系统就已经替我们申请好了。换句话说就是在你HeapAlloc一小块内存之前这块内存就已经存在了。
待补充。。。有时间会更新
在 Windows 操作系统中多个进程可能会需要访问同一份数据例如共享的 DLL 库、共享内存、共享文件等等。为了节约内存Windows 采用了一种称为“共享页面”的机制尽量保证同一份数据在物理内存中只有一份并分别映射到多个进程中。
当多个进程需要访问同一份数据时Windows 会将这份数据对应的物理内存页面标记为“可共享”的状态这个过程可以通过一些系统调用如CreateFileMapping、MapViewOfFile等来实现。然后为每个进程分配一个虚拟地址空间并将这些虚拟地址空间映射到同一个物理内存页面上。这样每个进程在访问这份数据时实际上是访问同一个物理内存页面而不是各自拥有一份独立的拷贝。
三、虚拟内存
1、内存分页的概念
内存分页是一种将物理内存划分成固定大小的块并将虚拟内存映射到这些物理内存块的技术。在内存分页的实现中每个物理内存块被称为一个页框每个虚拟内存块被称为一个页面。操作系统将虚拟地址空间划分为固定大小的页面并将其映射到物理地址空间中的页面框中。
内存分页的主要目的是实现虚拟内存以提高系统的内存利用率。由于虚拟内存允许将页面换入换出到磁盘上因此在物理内存不足时可以将不常用的页面换出到磁盘上从而释放物理内存并为正在执行的进程提供足够的内存空间。同时内存分页还可以使操作系统更加灵活地管理内存以便实现内存保护和共享等功能。
在内存分页的实现中操作系统通常会将每个页面映射到一个页表项中。页表项包含了虚拟页面的地址、页面的状态信息、页面的访问权限、页面所属的进程等信息。当CPU访问一个虚拟地址时操作系统会根据该地址对应的页表项将虚拟地址映射到物理地址并检查对应的页面是否已经在物理内存中。如果页面不在物理内存中操作系统就会触发一个缺页异常将页面从磁盘上读取到物理内存中并更新页表项。
内存分页是一种将虚拟内存映射到物理内存的技术它实现了虚拟内存和内存保护等功能提高了系统的内存利用率并为操作系统提供了更加灵活的内存管理手段。
2、操作系统分页概述
操作系统如何管理内存
》操作系统管理内存是将内存分成一页一页来管理的每一页的大小是4K也就是0x1000
4G的内存共有1M个页
使用了分页机制之后4G的地址空间被分成了固定大小的页每一页或者被映射到物理内存或者被映射到硬盘上的交换文件中或者没有映射任何东西。对于一般程序来说4G的地址空间只有一小部分映射了物理内存大片大片的部分是没有映射任何东西。CPU用来把虚拟地址转换成物理地址的信息存放在叫做页目录和页表的结构里。 3、虚拟内存状态 状态 空闲FREE 内存页不可用 保留(Reserve) 内存页被预定了但为与物理内存做映射还是不可用 提交(Commit) 内存被分配并且与物理内存进行了映射进程可以使用了
当虚拟内存映射到物理内存时有三种映射方式
4、内存映射方式 映射方式 描述 Private 进程私有内存不被其他进程所共享一般是堆栈 Mapped 从别的内存映射而来 Image 从程序的PE映像映射而来。
虚拟内存管理-内存属性
Windows中内存管理的最小单元是一个内存页 通常是0x10004kb
5、内存分页属性 ReadOnly 只读 READ_WRITE 读写 EXECUTE 执行 EXECUTE_READ_WRITE 可读可写可执行 WRITE_COPY 写时拷贝
6、页交换文件逻辑
程序访问虚拟内存地址操作系统判断数据是否在内存中如果在就从虚拟地址映射到的物理地址如果不在就判断是否在页交换文件当中如果在就查看物理内存是否有闲置空间有的话就将页交换文件载入到物理内存如果没有闲置内存就从物理内存中找到一个可以释放的页然后将页保存到页交换文件中。
7、虚拟内存相关API VirtualAlloc 分配或者预定一块虚拟内存 VirtualAllocEx 可以在其他进程分配或者预定一块内存 VirtualFree 释放内存 VirtualFreeEx 可以释放其他进程内存 VirtualLock 锁定内存不能交换到硬盘 VirtualUnLock 解锁 VirtualProtect 修改内存读写执行属性 VirtualProtectEx 可以修改其他进程内存属性 ReadProcessMemory 读取远程进程内存 WriteProcessMemory 写入数据到远程进程内存 VirtualQuery 查询内存状态 VirtualQueryEx
8、虚拟内存小案例
下面我们利用虚拟内存的特点来修改C当中的常量没错就是那些你以为不可能被改动的const char*那些玩意
事情是这样的浮沉最近经常摆烂他感觉这样不好于是偷偷黑入了我的电脑把我虚拟内存常量区的《浮沉学习记录表》偷偷改了从此浮沉变成了一个爱学习的乖宝宝
以下是完整的代码部分他的代码忘记删了我复制了一份
#include iostream
#includeWindows.h
#includecstring
int main()
{const char* str 浮沉今天在好好摆烂\n;std::cout 字符串常量修改前 str std::endl;// 获取字符串所在内存区域的地址const void* addr static_castconst void*(str);// 修改内存保护属性为可写属性//记得保存一下内存区域的原有访问属性以便后续恢复他的属性避免报错DWORD old_protect;VirtualProtect((char*)addr, strlen(str), PAGE_EXECUTE_READWRITE, old_protect);// 修改字符串内容char* writable_str const_castchar*(str);char* index std::strstr(writable_str, 摆烂);if (index ! nullptr) {std::memcpy(index, 学习\0, 7);}// 输出修改后的字符串内容std::cout 字符串常量修改后 str std::endl;// 恢复内存保护属性为只读属性VirtualProtect((char*)addr, strlen(str), old_protect, old_protect);return 0;
}
四、文件映射
1、文件映射的概念
文件映射File Mapping是Windows系统中一种高效的文件访问方式。它通过将文件数据与进程的虚拟地址空间相关联这样访问虚拟内存就是直接访问文件了让进程可以直接读写文件数据避免了传统的文件I/O操作带来的频繁的磁盘I/O提高了文件访问的效率。
注这里的所谓和虚拟地址建立联系本质就是和物理内存建立联系表面上来看修改虚拟内存就直接修改磁盘上的文件了本质上还是要通过物理内存来连接滴 2、文件映射的原理
1文件映射首先通过系统调用创建一个FileMapping映射对象内核对象
2然后将映射对象关联到一个文件或其他I/O设备关联我们准备操作的文件
3最后将映射对象的内容映射到进程的虚拟地址空间中
通过文件映射的方式使得进程可以通过访问内存的方式直接读写文件或设备的数据修改这个内容之后由操作系统来负责自动同步磁盘上文件内容的更新所以有时候你在内存修改文件的时候磁盘上的源文件可能并没有同步更新这时候我们可以使用Windows提供的另外一个接口FlushViewOfFile来刷新缓冲区实现将映射在内存当中的文件立即写回到磁盘当中。
这个过程中操作系统会将映射对象分页每一页映射到虚拟地址空间的一个页面上并且会对访问的数据进行合适的缓存从而提高了文件访问的效率。
3、下面我来详细解释一下为什么通过文件映射可以提高访问文件的访问效率
首先我们需要明确两点一是在读取大文件的时候通过文件映射才可以提高访问效率其实原因很简单你文件太小的话两种方式都会直接把整个文件全部读到内存当中去启动一次磁盘没什么效率上的区别。二是所谓提高文件访问效率本质就是减少启动磁盘的次数
因此我们不难发现采用传统的方式操作文件属于一种颗粒度比较大的读取而且没有通过操作系统进行统一集中管理比如在操作文件的时候发现文件的内容没有被加载进内存通过传统的方式就是要把相关的块一股脑地全部加载进来但是你很有可能只需要使用其中很少的一部分内容而且长时间不用还有读回磁盘这就又需要额外启动磁盘我们都知道IO设备的访问速度远远慢于内存你直接按照块去加载会启动不止一次磁盘而且每一层加载很多内容速率也慢同时会造成额外的空间开销而使用文件映射之后就相当于是把磁盘当中内存来使用了一样是按页来进行装载一个页大小4KB大小十分轻量就算读取IO也不会浪费太多时间而且文件映射对象是通过内核进行管理的可以十分精确地把没有加载的内容装载进来而不会造成额外的开销
4、文件映射的应用场景 需要频繁地读写大文件或大数据的场景比如视频编辑、图像处理等使用文件映射可以提高访问效率减少磁盘I/O带来的性能瓶颈。 需要多个进程共享同一个数据源的场景比如IPC进程间通信使用文件映射可以让多个进程共享同一个数据源避免了复制数据和进程间通信的开销。 实现内存映射文件Memory-Mapped Files可以将一个文件映射到内存中通过读写内存的方式来读写文件数据。内存映射文件可以实现对文件的共享访问使得多个进程可以共享同一个文件数据。
我们以前操作文件都是将文件从磁盘当中先读入内存当中然后在内存当中对文件的内容进行操作操作完成之后再将文件内容从内存写回磁盘当中这样的操作方式的效率无疑十分低下。
因此Windows当中提供了一种文件映射内存的方式操作文件也就是建立虚拟内存和磁盘文件的映射在这里这个磁盘文件我们就相当于当成是物理内存来用从虚拟内存的角度来看磁盘文件和物理内存本质就是一个东西只不过物理内存的读写速度更快仅此而已。
5、文件映射常用API CreateFileMapping 创建一个Mapping对象 OpenFileMapping 打开一个Mapping对象 MapViewOfFile 将maping对象的文件映射到内存中 UnmapViewOfFile 取消文件映射 FlushViewOfFile 刷新缓存区将映射在内存中的文件写回到硬盘中
6、下面我们来梳理一下
正常操作文件
CreateFile--》文件句柄---》通过文件句柄将文件内容读到虚拟内存修改内容--》重写写入到文件。
文件映射
CreateFile打开文件---》创建文件映射对象---》将文件映射到内存---》对内存的操作直接映射到文件当中。
文件映射还有一个重要的特点可以在不同的进程间共享数据。跨进程
下面我们来重点介绍一下文件映射用于共享内存的功能
7、共享内存小案例
我们可以通过一个小案例来说明一下如何利用共享内存来实现限制程序多开
事情是这样的我有一个朋友叫浮沉他很喜欢摆烂他经常在电脑上使用一款名叫《只因你太美.exe》的舞蹈软件来欣赏哥哥的舞姿现在我要在物理上让他实现科学上网我们都知道”事不过三“这个Old Wisdom当他打开《只因你太美.exe》软件超过三次软件就会打开一个弹窗提醒他停止摆烂赶紧去学习从而达到科学上网的目的。
先说思路我们利用映射内核对象跨进程共享的属性每次打开一个进程就在共享内存的这块区域把计数器1(计数器就存放在共享内存当中)这样新打开的进程就知道在他之前以及打开多少个进程了。注意这个操作要执行在main函数的最前面即在开始我们的业务之前进行执行这样一旦多开数量超过我们设置的阈值后面的业务程序就不会被执行了。
具体步骤就是 1首先通过OpenFileMappingA函数根据共享内存对象的名称打开我们创建的共享内存对象如果返回NULL我们就通过CreateFileMappingA创建一个共享内存对象 2将共享内存对象的句柄传入MapViewOfFile函数调用函数后会返回我们创建的共享内存的首地址 3然后我们通过一个指针来拿到这块内存地址直接对其进行操作就好了对于跨进程的程序如果有必要的话还应考虑同步互斥的问题
以下是完整代码
#include iostream
#includeWindows.h//打开超过3个exe就关闭
//创建一个共享内存每打开一个进程就操作共享缓冲区的计数器给他1
//如果number阈值就return掉即可#define BUF_SIZE 4096
#define ERROR -1int main()
{//修改控制台输出颜色HANDLE hConsole GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN);HANDLE hFileMapping;hFileMapping OpenFileMappingA(FILE_MAP_ALL_ACCESS, NULL, 只因你太美);if (hFileMapping NULL) {hFileMapping CreateFileMappingA(INVALID_HANDLE_VALUE, // 使用页文件创建文件映射NULL, // 默认安全性PAGE_READWRITE, // 可读可写0, // 文件映射的大小高位BUF_SIZE, // 文件映射的大小低位只因你太美 // 共享内存对象名);}if (hFileMapping NULL) {printf(CreateFileMapping failed (error %lu)\n, GetLastError());return ERROR;}LPVOID lpMapView MapViewOfFile(hFileMapping, // 共享内存对象句柄FILE_MAP_WRITE, // 可读可写0, // 文件偏移高位0, // 文件偏移低位BUF_SIZE // 映射视图大小);if (lpMapView NULL) {printf(MapViewOfFile failed (error %lu)\n, GetLastError());CloseHandle(hFileMapping);return ERROR;}//在共享缓冲区写内容int ProcessCnt 0;DWORD* pBuffer (DWORD*)lpMapView;*pBuffer 1;ProcessCnt *pBuffer;if (ProcessCnt 3) {MessageBoxA(0, 浮沉摆烂时间大于3天请立即滚回去学习, 严重警告, MB_OK);*pBuffer - 1;return 0;}printf(*****************浮沉摆烂监控程序已启动*****************\n);printf(应用进程正常加载但是\n);printf(今天是浮沉摆烂的第%d天\n, ProcessCnt);system(pause);*pBuffer - 1;printf(浮沉竟然竟然好好了一天\n);printf(*****************浮沉摆烂监控被干掉一个*****************\n);system(pause);SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);return 0;
}