厚积薄发
海纳百川,有容乃大
posts - 79,comments - 170,views - 17万
以前写过一篇理解程序内存, 当时主要是针对用户态,下面再稍微深入一点:


我们以32位程序为例(不启用AWE), 总共4G虚拟空间,其中低2G属于用户态, 高2G属于操作系统内核, 每个程序都有自己的低2G用户空间, 高2G内核空间是所有程序共享的。高2G内核空间中, 属于同一Session的程序又共享相同的session空间:


x86系统所有的内存以64K边界粒度, 4K页面大小分配。


用户态的内存空间,按用途分可以分为: image, mapped file, heap, stack, free等
按状态分可以分为:Free, reserved, commit;
Commit的内存,在被访问时又可能以不同的状态存在, 可能是已经提交到物理内存(RAM),也可能是已页文件的形式存在后台, 如果是页文件形式,访问时会触发换页操作。


我们平时以任务管理器或者Process Explorer, 经常会看到一些不同内存术语:
virtual size: reserve和commit的虚拟内存
Private bytes: 已经commit的私有虚拟内存
working set: commit的虚拟内存中已经被加载到物理内存中的部分
WS private / 内存(专用工作集): 不能和其他程序共享的working set


这些内存的大小关系怎么样?
virtual size 肯定是最大的; WS private肯定是最小的;working set和private bytes大小不好定, 因为working set虽然是表示物理内存, 但它包含共享和非共享两部分, 而private bytes虽然是虚拟内存,却只包含私有部分。
另外我们平时看程序的内存泄漏,主要可以看private bytes 和 WS private.


我们程序里使用的虚拟地址, 它在访问时是如何别转成真正的物理地址的?

1. 我们的虚拟地址被分为页目录索引,页表索引,字节偏移三部分
2. 根据CR3寄存器得到当前进程的页目录表地址, 根据页目录索引得到页目录表项目(PDE), 然后就可以得到该页表的地址
3. 根据页表索引,得到页表项目(PTE)的地址, 然后即可定位到该页面, 根据偏移字节即可访问真正的物理内存


操作系统采用按需换页的算法来实现内存的访问, 也就是说系统会在真正访问一个地址的时候才会把该地址转成有效的物理地址, 如果访问失败, 会触发换页异常, 再真正加载该页面换到物理内存。系统用虚拟地址描述符(VAD, virtual address descriptor)组成的平衡二叉树来跟踪所有的虚拟内存,以确定所有虚拟内存的状态(free, reserver, commit)和属性。


下面说下应用层对程序内存的访问, 按照内存的用途就可以大概划分:
Image: 主要是指二进制模块在内存中存在方式, 比如Exe和Dll, 对应的API比如LoadLibrary。
Mapped file: 主要是指内存映射文件, 可以用来快速的加载大文件 ,或者跨进程共享内存, 对应的API比如 CreateFileMapping.
Stack: 每个线程都有自己的堆栈, 包括用户态堆栈和内核堆栈,虽然堆栈内存分配有大小限制, 但是非常高效,函数的局部变量都存在里面,程序的运行过程(函数的调用过程)实际上是不停的压栈和出栈的过程,大小一般默认保留1M(参见线程堆栈是如何增长的)
Heap: 系统有自己的堆管理器, 虽然效率堆内存分配效率低, 但是没有大小限制, 对应的API比如new, malloc, HeapAlloc


操作系统为我们访问内存提供了各种渠道,我们可以根据需要自己选择, 由下往上可以分为:
虚拟内存: 对应的API如VirtualAlloc(Ex), VirtualFree(Ex), VirtualLock, VirtualProtect, 通过这些API,我们可以直接分配(reserver, commit)大块内存( 4K页面大小), 同时定义修改页面属性, 这是最高效的大内存分配方式。
Win32 堆内存: 对应的API如HeapCreate, HeapAlloc, 堆内存建立在虚拟内存之上,很多时候我们不需要虚拟内存的大块内存,只需要小块内存,操作系统通过堆管理器帮我们解决了这个问题。每个进程启动时系统都会创建一个默认堆,同时我们也可以创建自己的私有堆, 不同模块之间是否共享同一个CRT堆取决于模块的编译选项,(参见基于WinDbg的内存泄漏分析
CRT 堆内存:C/C++代码中我们最常用的内存分配方式是malloc和new, 通常情况下malloc只负责内存分配, 而new在调用malloc分配内存的同时还有在分配的内存上构造对象的功能。至于malloc的实现方式, 不同的编译器厂商会有不同的实现, 有些可能是通过Win32堆实现,也可能是通过虚拟内存API直接实现。


思考为什么有了虚拟内存API和Win32堆API,还要有CRT堆API?
软件工程里一条比较经典的话是: 任何问题都可以加一个间接层加以解决。操作系统提供的API都是平台相关的, 通过CRT这个间接层实现了平台无关, 同时我们可以在这个间接层上做很多事情, 比如内存泄漏跟踪, 实现自己的内存池等。


如果我们直接调用虚拟内存API分配内存, 这种内存属于那种类型?
实际上按照VMMap的说法, 内存类型还有更多: Image, Mapped File, Shareable, Heap, Managed Heap, Stack, Private Data, Page Table, Unusable, Free.
直接通过VirtualAlloc分配的内存不属于Heap, 应该属于Private Data.
posted @ 2016-04-07 21:54 Richard Wei 阅读(1540) 评论(0) 推荐(2) 编辑
摘要: 总之,无论学什么,先深入一个平台, 从C++编译器到CRT运行库, 再到操作系统, 从用户态API到内核和驱动,越深越好,然后再跳出这个平台,接触其平台,会发现各个平台基本都是大同小异。 阅读全文
posted @ 2015-09-18 00:59 Richard Wei 阅读(675) 评论(0) 推荐(0) 编辑
摘要: 谁知道Windows为什么不用UTF8,非要搞得和其他平台不一样? 阅读全文
posted @ 2015-07-25 01:22 Richard Wei 阅读(2679) 评论(1) 推荐(0) 编辑
摘要: 尽管我N次吐槽基于GDI的DirectUI界面库会随着XP的淡出而逐渐失去市场, 但是实际工作中还是要经常和GDI打交道,外面招聘单位还是有不少Windows客户端的开发岗位。 在这"移动互联和"Web前端"横行的"大数据"时代,很多同事开始向移动App和大数据转型, 尽管这几年PC客户端的开发人员是只出不进, 但是只要Windows存在一天,我们的工作就还是有价值的.. 阅读全文
posted @ 2014-11-15 00:05 Richard Wei 阅读(2456) 评论(1) 推荐(1) 编辑
摘要: Window上我们常见的资源泄露包括内存和对象句柄泄露, 下面讨论下对各类泄露的检测方法。 阅读全文
posted @ 2014-08-27 22:40 Richard Wei 阅读(1422) 评论(0) 推荐(0) 编辑
摘要: 一个基本的图形引擎要包括几个方面的支持:位图绘制,文字绘制,矢量绘制(如矩形,线条)。 可惜GDI这个20多年前发明的老古董,对这几项的绘制, 除了位图绘制可以通过AlphaBlend支持alpha通道,其他绘制都不支持alpha通道。 阅读全文
posted @ 2014-08-26 13:51 Richard Wei 阅读(850) 评论(0) 推荐(0) 编辑
摘要: 工作中有个需求是关于抓取扬声器的声音, 为什么会有这个需求? 试想我们在共享远程桌面时,如果能够把本地桌面应用程序的声音也一起发给对方,然后播放出来, 用户体验该是多么棒。 阅读全文
posted @ 2014-08-22 07:11 Richard Wei 阅读(3626) 评论(3) 推荐(5) 编辑
摘要: 最近工作中有个需求是将Icon转成带Alpha通道的Bitmap, 虽然网上有不少这方面的文章,但很多都是错的, 这里记录下,或许对后来人有用。 阅读全文
posted @ 2014-08-21 23:40 Richard Wei 阅读(3024) 评论(0) 推荐(3) 编辑
摘要: 对于客户端架构设计,个人觉得最大的原则就分层设计, 每层都封装一个概念并保持独立, 同时根据依赖倒置的原则, 站在上层客户的角度提供接口。软件工程里面的一条黄金定律:“任何问题都可以通过增加一个间接层来解决。 阅读全文
posted @ 2014-07-26 23:59 Richard Wei 阅读(3913) 评论(4) 推荐(5) 编辑
摘要: 计算机的好处是它永远不会欺骗你, 它只会按部就班的执行, 所以很多看似奇怪(甚至看似不可思议的问题), 只要你理解了程序背后的机制原理,都是可以找出根本原因的。 阅读全文
posted @ 2014-05-30 23:21 Richard Wei 阅读(420) 评论(0) 推荐(0) 编辑
摘要: 简单总结下,我们从C++的内置数组讲到标准库提供的vector, 最后谈到C++11新增的array, 数组这个最基本的数据结构在C++中终于有了完整的支持。 阅读全文
posted @ 2014-05-12 22:12 Richard Wei 阅读(849) 评论(0) 推荐(1) 编辑
摘要: 简单总结下,操作系统通过一层层的封装,隐藏了太多的东西, 很多看似简单的行为, 实际上背后都有很复杂层层调用。理解这些原理,可以让你的知识达到一定的深度,帮助你更好的解决问题。 阅读全文
posted @ 2014-04-05 01:01 Richard Wei 阅读(2535) 评论(1) 推荐(9) 编辑
摘要: 如果说以前XP时代我们还有理由不关注高DPI, 那么在移动设备时代和大显示器的高分辨率时代, 我们就没有理由不关注高DPI了, 比如Surface Pro的分辨率是1920x1080, 这种情况下如果系统我们不设置高DPI, 基本上就没法触摸和操作了,所以现在普通程序对高DPI的支持已经成为趋势了。 阅读全文
posted @ 2014-02-18 23:17 Richard Wei 阅读(12350) 评论(3) 推荐(1) 编辑
摘要: 在XP时代我们的程序没有响应后只能通过任务管理器强制杀掉,但是Vista之后情况变了, 我们仍然可以拖动失去响应的窗口,甚至可以尝试最小化和关闭窗口, 我们把这个特性叫住Window Ghosting。 阅读全文
posted @ 2014-01-09 19:20 Richard Wei 阅读(1045) 评论(0) 推荐(0) 编辑
摘要: 最后简单总结下 , 我们可以看到Windows系统上基本没有一种通用的抓屏技术可以高效的抓取所有的系统(XP/Win7/Win8), 很大一部原因是操作系统的显示驱动模型在从XPDM向WDDM转变, 应用层的API也在从GDI向D3D转变 。 相对于Linux的稳定, Window的不断发展和进步, 对开发人员究竟是喜是悲? 阅读全文
posted @ 2013-12-01 22:13 Richard Wei 阅读(3158) 评论(1) 推荐(1) 编辑
摘要: 我们可以看到数组new[]和delete[]的关键是, C++编译器在数组起始地址之前的4个字节保存了对象的数量N,后面会根据这个数量值进行N次的构造和析构 。 我们可以看到C++ 编译器在背后干了很多事情,可能会内联我们的函数, 也可以修改和产生其他一些函数, 而这是很多C开发者受不了的事情, 所以在内核级别, 很多人宁愿用C来减少编译器背后的干扰。 阅读全文
posted @ 2013-11-17 21:25 Richard Wei 阅读(699) 评论(0) 推荐(0) 编辑
摘要: 探讨Windows上各种Hook技术:SetWindowsHookEx, SetWinEventHook, API Hook, COM Hook 阅读全文
posted @ 2013-10-30 11:12 Richard Wei 阅读(11703) 评论(5) 推荐(6) 编辑
摘要: 我们知道Windows的窗口消息处理函数是C方式, 面向过程的, 所以窗口框架的基本任务就是将它转成面向对象的方式, 确切的说如何将消息处理函数第一参数HWND转成对象指针。 阅读全文
posted @ 2013-09-08 14:49 Richard Wei 阅读(855) 评论(0) 推荐(0) 编辑
摘要: 最近工作 中有个需求是抓取桌面截图, 这里的桌面是指点了“显示桌面”之后看到的桌面, 截图内容包括桌面背景和图标以及任务栏 阅读全文
posted @ 2013-08-30 20:39 Richard Wei 阅读(4252) 评论(12) 推荐(3) 编辑
摘要: 记录一些Windows 安全相关的概念, 分享自己对Windows安全机制的一些理解。 阅读全文
posted @ 2013-08-25 08:09 Richard Wei 阅读(2563) 评论(2) 推荐(2) 编辑
摘要: 简单总结下, 语言不在多,在精。经常使用你觉得有价值的语言。深入掌握一门脚本。 阅读全文
posted @ 2013-08-17 19:35 Richard Wei 阅读(4421) 评论(22) 推荐(6) 编辑
摘要: 很多人说COM过时了, 也许”纯正的标准COM“确实是使用的人越来越少了, 但是COM的思想却一直在后续的软件开发中被使用和发扬, 可以说COM技术是微软技术框架的“根”(之一)。 阅读全文
posted @ 2013-07-20 17:16 Richard Wei 阅读(2340) 评论(10) 推荐(6) 编辑
摘要: 很多人说跟着微软跑真累,确实如此。 但是回头来想想, 那是因为你一直落后, 你一直在追赶, 你一直是被赶着走的, 所以你会觉得累。如果你一直是与微软起头并进, 甚至领先与微软(微软新加的东西是你意料到的), 你就不会有累的感觉,有新东西出来, 研究下原理, 写些测试代码,就大概知道怎么回事了。 阅读全文
posted @ 2013-07-18 06:40 Richard Wei 阅读(5716) 评论(20) 推荐(15) 编辑
摘要: 越抽象的东西离底层机器就越遥远, C++隔着复杂的编译器, Java/C#隔着虚拟机, 脚本语言隔着解释器, 这就是高级语言的代价。 阅读全文
posted @ 2013-06-20 22:44 Richard Wei 阅读(864) 评论(0) 推荐(0) 编辑
摘要: 最近有机会看号称是公司最核心的代码, 因为这个代码以前一直是美国那边保密的, 这么重要的代码会是啥样子? 真正拿到手大致看了一下后却挺失望的,因为该代码风格基本上是我刚毕业时的C++风格----带类的C,单从代码上看写的挺滥,里面没啥设计模式, 也没有用模板, 代码里面甚至一个函数可以写上近千行。 这么重要的代码, 竟然是这种风格, 由此思考好的C++程序应该是什么风格? 阅读全文
posted @ 2013-04-27 23:40 Richard Wei 阅读(2027) 评论(6) 推荐(3) 编辑
摘要: Windows的API封装了太多细节, 尽管大部分时候我们只要知道如何使用它们,而不用关心它们的内部如何实现。 但是当你写一些相对底层的东西,比如开发自己的DirectUI界面库时, 还是需要真正理解某些API的内部实现原理,才能继续深入下去。 阅读全文
posted @ 2013-04-07 22:24 Richard Wei 阅读(2002) 评论(1) 推荐(1) 编辑
摘要: 个人尝试山寨了下STL, 对STL的6大组件(containers, algorithms, iterators, functors, adaptors, allocators)都有涉及。 当然山寨STL不是为了重复造轮子,而是为了更好的理解和扩展STL。 阅读全文
posted @ 2013-04-03 16:59 Richard Wei 阅读(674) 评论(0) 推荐(2) 编辑
摘要: 通过WinDbg结合AppVerifier, 我们可以详细的跟踪堆中new出来的每一块内存。 很多时候在没有源代码的Release版本中,在程序运行一段时间后,如果我们发现有大内存或是大量同样大小的小内存一直没有释放, 我们就可以用上面的方法进行分析和快速的定位问题。 阅读全文
posted @ 2013-02-27 14:50 Richard Wei 阅读(9135) 评论(0) 推荐(2) 编辑
摘要: 尽管这个概念已经让人说滥了 ,还是想简单记录一下, 以备以后查询。 阅读全文
posted @ 2013-02-25 22:33 Richard Wei 阅读(1933) 评论(1) 推荐(3) 编辑
摘要: 从编译时到运行时,从面向对象到普通泛型编程再到模板元编程,C++复杂得让人无语, 也强大得让人无语, 而且C++语言本身是在不断发展的(C++11), 同一问题在C++中往往有多种解决方案,这些解决方案有的简单,有的复杂,有的高效, 也有的低效, 而我们的目标就是利用C++这把利器寻找简单而高效的解决方案。 阅读全文
posted @ 2013-02-14 20:49 Richard Wei 阅读(8045) 评论(7) 推荐(4) 编辑
摘要: 以前在设计DirectUI界面库(该界面库现已开源, 可到 这里 下载)架构时,遇到一个接口继承相关的问题,当时没有太好的解决方案,却一直个耿耿于怀, 现在重新思考整理下。 最后,总结下上面三种方法: 第一种实现和接口混合继承的方法最简单,也最容易理解, 缺点是没法完全基于接口编程; 第二种基于模板的方法比较难理解,实现上也比较简单, 缺点是代码膨胀; 第三种多重继承的方法也比较容易理解, 缺点是我们要多做一些工作。 阅读全文
posted @ 2013-02-08 20:01 Richard Wei 阅读(1625) 评论(2) 推荐(0) 编辑
摘要: 在上文 在C++中实现事件(委托) 中我们实现的C#里委托方式的事件处理, 虽然使用很方便,但是感觉似乎少了一点C#的味道, 下面我们尝试把它改成真正的C#版。 其实要改成真正的C#版,我们主要要做2件事, 一是吧CEventHandler放到外面,可以让外部直接构造, 二是实现operator +=和operator -= 阅读全文
posted @ 2013-01-31 17:46 Richard Wei 阅读(2990) 评论(5) 推荐(2) 编辑
摘要: 在C++中实现回调机制的几种方式一文中,我们提到了实现回调的三种方式(C风格的回调函数, Sink方式和Delegate方式)。在面向对象开发中,delegate的方式是最灵活和方便的,因此很早就有人用复杂的模板去模拟, 实现起来很复杂。但是现在借助C++11的function和bind, 我们可以很方便的去实现。 阅读全文
posted @ 2013-01-31 14:23 Richard Wei 阅读(2523) 评论(2) 推荐(2) 编辑
摘要: 总之, 一开始我们会觉得WinRT很好奇, 但是后来我们逐步发现它其实并没有那么神秘, 它是很多微软现有技术的合成体。 微软的技术更新很多时候让人眼花潦兰,但是底层本质的东西(比如COM,D3D,Win32)其实一直很少改变。 阅读全文
posted @ 2013-01-13 16:40 Richard Wei 阅读(1873) 评论(4) 推荐(0) 编辑
摘要: 最后, 总结一下, 微软在Intel处理器上开发Windows操作系统, 我们在Windows操作系统上开发应用程序,无非是一层层的封装, 其实具体到细节, 每层都没有太多神秘的东西。我们当然不可能掌握每层的细节, 只能理解每层的概念, 以帮助我们在应用层更好的开发。 阅读全文
posted @ 2013-01-13 13:39 Richard Wei 阅读(2758) 评论(1) 推荐(1) 编辑
摘要: windows程序运行分为内核模式和用户模式,内核模式可以访问所有的内存地址空间, 并且可以访问所有的CPU指令。一般程序运行在用户模式, 通过系统调用切换到内核模式执行系统功能,Windows系统通过这种方式来确保系统的安全和稳定。 阅读全文
posted @ 2013-01-12 22:08 Richard Wei 阅读(4975) 评论(0) 推荐(2) 编辑
摘要: 有些人说GP的抽象能力高于OO,这个观点我并不认同,我感觉只是他们的抽象方式不一样,OO是基于接口, 而GP是基于concept。OO的基于接口的抽象,在源代码和最终运行时都能体现,源代码中是接口,运行时是虚表,所以他们是一致的, 符合普通人的思维习惯。GP基于concept的抽象, 主要体现在源代码中 ,只是你用来告诉编译器你的思维方式, 在运行时他可能是一个完全不同的世界,所以比较难理解。 阅读全文
posted @ 2012-11-10 14:45 Richard Wei 阅读(1527) 评论(5) 推荐(1) 编辑
摘要: 通过上面的分析 ,相信我们知道了为什么ATL/WTL大量使用模板,但是生成的exe还是这么小的原因 : 不是模板不会使代码膨胀,而是ATL/WTL在设计时就关注了这个问题 ,它避免了在可能生成很多模板实例的模板类中编写大量代码(有些拗口,不知道你有没有读懂^_^) 总结下 ,如果你想用模板,但是又不想 让自己最终的可执行文件变的很大, 有2种方式: (1)你的模板类不会生成很多模板实例,这样写成模板类还有意义吗? (2)你的模板类的代码量或是函数个数很少,你可以仿照ATL的方式把模板无关的东西逐层剥离。 阅读全文
posted @ 2012-11-08 22:56 Richard Wei 阅读(3439) 评论(6) 推荐(2) 编辑
摘要: 以我个人的经验,一些东西刚开始看不太懂就放一放,先去看一些基本的东西,比如不懂COM,先去学下C++ 中的虚函数;不懂C++模板,先去学下STL;不懂Thunk,先去看一下汇编,等有了一定的积累,回头再看,一切就觉得没这么难了。 阅读全文
posted @ 2012-10-23 00:31 Richard Wei 阅读(2022) 评论(1) 推荐(1) 编辑
摘要: 经过上面的比较, 我们可以得出一些结论: 消息方式的强项是耦合性和扩展性,以及监控的方便性,个人感觉比较适合于Server端的规模应用。 接口方式的强项是性能高效以及开发的方便性, 比较适用于同一进程内客户端的小规模应用。 但是大部分时候, 对于架构师或是公司领导,他们会更关注可耦合性和可扩展性,所以他们会倾向于选择消息方式,尽管有时可能不是那么适用。 阅读全文
posted @ 2012-10-12 23:17 Richard Wei 阅读(5548) 评论(14) 推荐(11) 编辑
< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

点击右上角即可分享
微信分享提示