|
MFC 线程于消息循环的困惑
我当然是查过的,注意是空的对话框程序(你的例子创建了线程,当然不是单线程),难道你的机器查出来不是一个线程? |
|
MFC 线程于消息循环的困惑
1)单线程 2)不一定: 如果在消息处理过程中使用PeekMessage、GetMessage例程,这些例程会检测其它线程发送过来的消息,如果有就立刻处理。 在消息处理过程中还可以使用DispatchMessage处理通过PeekMessage或GetMessage从消息队列中取得的消息。 |
|
[原创]乱侃Windows NT内核之内存管理
系统空间管理(补充) ========================================================== 上面提到过,系统空间使用MMPTE_LIST管理非分页内存,这里管理的页面是没有物理映射的,能够访问前需要映射到物理空间。 非分页内存其实有两种,除了上面讨论的一种外,还有一种是在初始化时就已经分配好的,该区域已经建立了物理映射,被称为非分页池,该区域是可以直接使用的,Windows以一般“池”的方法管理该区域,类似用户空间的heap的管理,及空闲链表法,由于被管理的页面是可以直接使用的,所以空闲链表就建立在这些页面上,总共有四个空闲链表,分别记录1、2、3、4及以上个页面大小的空闲内存块,没有什么亮点,就不多说了。 除了非分页内存外,还有一种称为分页内存的空间,这些页面是可以交换到磁盘上的,所以不能保证这些页面是有物理映射的,所以windows用一个数据结构来描述这些页面的分配状态: typedef struct _MM_PAGED_POOL_INFO { PRTL_BITMAP PagedPoolAllocationMap; PRTL_BITMAP EndOfPagedPoolBitmap; PMMPTE FirstPteForPagedPool; PMMPTE LastPteForPagedPool; PMMPTE NextPdeForPagedPoolExpansion; ULONG PagedPoolHint; SIZE_T PagedPoolCommit; SIZE_T AllocatedPagedPool; } MM_PAGED_POOL_INFO, *PMM_PAGED_POOL_INFO; 从 PagedPoolAllocationMap项可以看出,Windows是使用位图法记录各个页面的分配状态,有点类似fat文件系统,呵呵。 也许你要问了,Windows分别使用MMPTE_LIST、位图、空闲链表记录系统空间的分配状态,在释放一块内存时,如何知道使用哪一个数据结构呢?其实很简单,从要释放的地址就可以确定了,windows使用不同的数据结构管理的可是系统不同的区域,地址属于哪个区域可是一目了然啊,呵呵。 |
|
|
|
[注意]招聘高级软件工程师-北京鼎普科技股份有限公司高薪诚聘
你不会要我,因为我没有“统招本科”学历,我甚至没有上过一天大学。 即使你要我,我也不会去,因为你给的工资上限还没有我现在拿的多,我为什么要去? 呵呵! |
|
[原创]乱侃Windows NT内核之内存管理
物理内存管理: ================================================================ Windows将物理地址空间分成4k(0x1000)字节大小的页,以页为单位分配物理内存 因为windows以物理页的形式分配物理内存,所有要管理这些页,必须建立这些页的描述,显然对于一个确定的页来说,最重要的信息应该是: 1.起始地址 2.大小 3.是否可用等等 我们来看看Windows对物理内存资源的描述: typedef struct _MMPFN { union { PFN_NUMBER Flink; WSLE_NUMBER WsIndex; PKEVENT Event; NTSTATUS ReadStatus; struct _MMPFN *NextStackPfn; } u1; PMMPTE PteAddress; union { PFN_NUMBER Blink; ULONG ShareCount; ULONG SecondaryColorFlink; } u2; union { MMPFNENTRY e1; struct { USHORT ShortFlags; USHORT ReferenceCount; } e2; } u3; #if defined (_WIN64) ULONG UsedPageTableEntries; #endif MMPTE OriginalPte; PFN_NUMBER PteFrame; } MMPFN, *PMMPFN; PMMPFN MmPfnDatabase; 上面是windows对一个物理页的描述,MMPFN用来表示一个物理页,MmFfnDatabase就是一个MMPFN的数组; 你也许奇怪,MMPFN中并没有诸如开始地址、页大小等等信息,其实MMPFN中最重要的数据是: 1) u1.Flink;u2.Blink这两个字段其实就是组成一个LIST_ENTRY,用来将该页链入某个链表 2) PteAddress,用来指向一个页表项。 那么如何知道该MMPFN所代表的那块内存的开始地址和页大小呢,其实很简单。 首先,页大小是固定的,就是4k,没必要每个PFN都记录,而开始地址的计算如下: 设x为一个MMPFN,那么对应的开始页号就是&x-MmPfnDatabae,起始地址就是(&x-MmPfnDatabae)*0x1000. MMPFN数据只存在于数组MmPfnDatabase中,而且按其所代表的页是连续的,即MmPfnDatabase[0]代表0~0xfff,MmPfnDatabase[1]代表0x1000~0x1fff,。。。 而对其使用也就是将其挂入相应的链表,期间除了改变u1.Flink;u2.Blink的指向外,没有数据拷贝,其实这才是其高明之处,它给了我们一个在静态数组之上建立复杂数据结构的范例,这才是我看这部分代码的最大收获(个人认为,使用静态内存就是安心,就是爽,呵呵)。 对于物理内存的管理就是根据不同需求,将存在于MmPfnDatabase数组中的MMPFN数据挂入不同的链表,Windows用于物理内存管理的链表如下,其意义可从其名字上看出,就不多说了: 1) MMPFNLIST MmZeroedPageListHead; 2) MMPFNLIST MmFreePageListHead; 3) MMPFNLIST MmStandbyPageListHead; 4) MMPFNLIST MmModifiedPageListHead; 5) MMPFNLIST MmModifiedNoWritePageListHead; 6) MMPFNLIST MmBadPageListHead; |
|
[原创]乱侃Windows NT内核之内存管理
lkd> dd MmFirstFreeSystemPte l 1 8055f8c0 eb000000 lkd> dd MmSystemPteBase l 1 8055f84c c0000000 奇怪,为何MmSystemPteBase 的值会是c0000000? 如果这样,第一个空闲的pte地址岂不是指向了用户空间? 第一个空闲pte的地址: PointerPte = MmSystemPteBase + MmFirstFreeSystemPte[0] /1000 = c0000000+eb000 = c00eb000 第一个空闲pte在页表中的偏移: c00eb000-c0000000 = eb0000 第一个空闲pte对应的虚拟地址: eb000/4*1000 = 3ac00000 显然有问题: 3ac00000小于0x80000000 MmSystemPteBase 应该是c0200000才合理吧,难道本地内核调试连静态变量都不正确? 谁解释一下? |
|
|
|
[求助]Sleep函数
tickcount是记录在用户和内核共享的一块称为UserSharedData区域,ring3直接读取,内核对该值不是实时更新的。 |
|
|
|
|
|
[原创]乱侃Windows NT内核之内存管理
已经将所有内容整理到1楼了,大家就凑合着看哈 |
|
[原创]乱侃Windows NT内核之内存管理
一块内存在变得可以使用之前,需要经过以下步骤: 1)分配虚拟空间,所有程序只能读写虚拟空间 2)分配物理内存 3)建立虚拟内存和物理之间的映射 下面先说说虚拟空间的管理: 用户空间管理: ===================================================== Windows将所有已经分配或保留的内存区块记录在一个称为avl的二叉树的结构中,该二叉树定义如下: typedef struct _MMADDRESS_NODE { union { LONG_PTR Balance : 2; struct _MMADDRESS_NODE *Parent; } u1; struct _MMADDRESS_NODE *LeftChild; struct _MMADDRESS_NODE *RightChild; ULONG_PTR StartingVpn; ULONG_PTR EndingVpn; } MMADDRESS_NODE, *PMMADDRESS_NODE; 使用这种管理策略管理用户空间是一个很自然的事,如果让我写一个这样的模块,可能实现的方法是一样的,就不多说了呵呵。 系统空间的管理: ======================================================= 系统空间的使用者是内核本身(当然也包含驱动程序),所以内核开发人员对系统空间的内存的需求是很清楚的,所以对系统空间的管理策略应该是针对内核的需求特点进行精心考虑的 看了wrk系统空间管理部分的源代码,不得不佩服windows开发人员的智慧 我们知道页表的元素称为pte,整个页表被映射在系统空间的0xc000000到0xc03ffffff的地方,每一页占有四个字节,其定义如下: typedef struct _MMPTE { union { ULONG_PTR Long; MMPTE_HARDWARE Hard; MMPTE_HARDWARE_LARGEPAGE HardLarge; HARDWARE_PTE Flush; MMPTE_PROTOTYPE Proto; MMPTE_SOFTWARE Soft; MMPTE_TRANSITION Trans; MMPTE_SUBSECTION Subsect; MMPTE_LIST List; } u; } MMPTE; 我们看到windows对mmpte的定义是由多种解释的,硬件对其解释如下: typedef struct _MMPTE_HARDWARE { ULONG Valid : 1; #if defined(NT_UP) ULONG Write : 1; // UP version #else ULONG Writable : 1; // changed for MP version #endif ULONG Owner : 1; ULONG WriteThrough : 1; ULONG CacheDisable : 1; ULONG Accessed : 1; ULONG Dirty : 1; ULONG LargePage : 1; ULONG Global : 1; ULONG CopyOnWrite : 1; // software field ULONG Prototype : 1; // software field #if defined(NT_UP) ULONG reserved : 1; // software field #else ULONG Write : 1; // software field - MP change #endif ULONG PageFrameNumber : 20; } MMPTE_HARDWARE, *PMMPTE_HARDWARE; 其最重要的是最低位的valid位,如果对写的地址的pte的valid等于0将引发缺页硬件异常,该地址会通过cr2传给中断处理程序。 其高20位用来保存物理页框架号,还有其他位现的意义就不说了,以上的定义除了高20位和低8位之间的4位保留可以自由定义外,其它为都是由硬件定义的。 windows对pte还有另外的定义,如MMPTE_LIST List;大家知道其用途么?呵呵,这就是用来管理系统空间的。 windows将整个页表被映射在系统空间的0xc000000到0xc03ffffff的地方后,带给我们的最大的好处是什么? 对了,虚拟地址和该地址在页表中的pte的地址的相互转换变得很容易了,所以windows将系统空间的分配和释放就转换成对pte的分配和释放了,这种想想法真是太高明了,呵呵。 windows将未使用的ptes根据用途分别链入一个链表,实现方法就是pte的另一种解释: typedef struct _MMPTE_LIST { ULONG Valid : 1; ULONG OneEntry : 1; ULONG filler0 : 8; // // Note the Prototype bit must not be used for lists like freed nonpaged // pool because lookaside pops can legitimately reference bogus addresses // (since the pop is unsynchronized) and the fault handler must be able to // distinguish lists from protos so a retry status can be returned (vs a // fatal bugcheck). // ULONG Prototype : 1; // MUST BE ZERO as per above comment. ULONG filler1 : 1; ULONG NextEntry : 20; } MMPTE_LIST; 对应空闲ptes的表头用一个全局变量保存,连续空闲区块的第一个pte的高20位指向下一个空闲连续区块的第一个pte,第二个pte的高20位用来保存该连续空间的大小(页数、pte个数) 也许你会问了,假如一个空闲块只有一个页面,那空闲区块的大小保存在那里呢?嘿嘿,这就是OneEntry 的作用!当OneEntry 等于1时,表示该空闲块只有1页。 windows开发人员聪明吧,他直接将管理空闲空间的链表建立在页表上面,既高效,又节省空间,而且该链表的元素还是静态分配的,高明啊,哈哈。 还有,ptes的分配是从一个空闲pte区的后面开始分配的,知道这一点可以帮助你看懂该部分源代码。 详细的请大家看wrk啦,比如非分页内存的分配例程是MiReserveAlignedSystemPtes。 非分页内存其实有两种,除了上面讨论的一种外,还有一种是在初始化时就已经分配好的,该区域已经建立了物理映射,被称为非分页池,该区域是可以直接使用的,Windows以一般“池”的方法管理该区域,类似用户空间的heap的管理,及空闲链表法,由于被管理的页面是可以直接使用的,所以空闲链表就建立在这些页面上,总共有四个空闲链表,分别记录1、2、3、4及以上个页面大小的空闲内存块,没有什么亮点,就不多说了。 除了非分页内存外,还有一种称为分页内存的空间,这些页面是可以交换到磁盘上的,所以不能保证这些页面是有物理映射的,所以windows用一个数据结构来描述这些页面的分配状态: typedef struct _MM_PAGED_POOL_INFO { PRTL_BITMAP PagedPoolAllocationMap; PRTL_BITMAP EndOfPagedPoolBitmap; PMMPTE FirstPteForPagedPool; PMMPTE LastPteForPagedPool; PMMPTE NextPdeForPagedPoolExpansion; ULONG PagedPoolHint; SIZE_T PagedPoolCommit; SIZE_T AllocatedPagedPool; } MM_PAGED_POOL_INFO, *PMM_PAGED_POOL_INFO; 从 PagedPoolAllocationMap项可以看出,Windows是使用位图法记录各个页面的分配状态,有点类似fat文件系统,呵呵。 也许你要问了,Windows分别使用MMPTE_LIST、位图、空闲链表记录系统空间的分配状态,在释放一块内存时,如何知道使用哪一个数据结构呢?其实很简单,从要释放的地址就可以确定了,windows使用不同的数据结构管理的可是系统不同的区域,地址属于哪个区域可是一目了然啊,呵呵。 |
|
事件对象句柄复制给另一个进程后是否仍然有效?
可以使用带名字的事件对象,在一个进程中创建,其他进程要使用,就是用这个名字打开就行了 关于在不同进程中打开的同一个名字的事件对象的句柄值是否一样,还真不知道,试一下吧。不过这样做也没有实际意义。 |
|
事件对象句柄复制给另一个进程后是否仍然有效?
其实我也不知道 句柄也有两种,第一种是进程私有的,句柄和打开的内核对象指针之间的对应表保存在EPROCESS里,只对该进程有效,父进程也可能将这种对应表项复制给其子进程,在其他进程这一句柄值的解释可能就不一样,甚至没有对应,如打开的文件句柄。 另一种句柄在所有的进程中是一样的,如窗口句柄。 其实这个问题根本就不用问,写个测试程序试一下就知道了。 |
|
[原创]放一段可以随处打印函数调用栈的代码
我不否认有朋友可能看不懂我的帖子,因为我比较懒,好多背景知识懒得说,加上每个朋友的知识结构不一样,术业有专攻嘛。 但是这里高手如云,我想大家不愿意回我所写的帖子的真正原因是我的写的东西没有特别之处,或者说是大家都会了,或者说我是在班门弄斧,大家都不屑跟贴,呵呵。 这是我用于死锁检测的一段代码,还是很有效的,当时写出来只是希望斑竹早点赏我一个邀请注册码。 |
|
[原创]乱侃Windows NT内核之内存管理
[QUOTE=skypismire;795884]多谢楼主,想明白了,PAGETABLE_MAP区域0xc0000000-0xc0400000能够正确的映射到二级层次结构的页目录表的画龙点睛之笔是将0xc0000000-0xc0400000区域对应的PDE表项(下标为 0xc0000000 / 2^22 = 0xc00)即PDE[0xc00]设置为...[/QUOTE] 对头,不用谢,呵呵! |
|
[原创]乱侃Windows NT内核之内存管理
例程MmCreateProcessAddressSpace 包含了windows父进程为子进程构造页表的代码,大家可以在wrk中找到。 |
|
[原创]乱侃Windows NT内核之内存管理
下面简单谈一谈页表的初始化,顺便也就回答了你的问题 =============================================== 页表的初始化: 我们知道,在启动分页以后,CR3指向的物理地址指向页目录 如果我们已经将页目录映射到了一个确定的地址,下面的工作就不是问题了 也许你会问,我们如何将页目录映射到一个确定的地址呢?这似乎是一个先有鸡还是先有蛋的问题 其实很简单,两句话,子进程的页目录映射由父进程代劳,老祖宗进程的页目录的映射工作由实模式代劳。 对于子进程,父进程会在其hyperspace上将子进程的页表映射构造好,然后将页目录对应的物理地址写道子进程的EProcess里就一切OK了。 而对于第一个进程的页表是在启动分页前设置好的。 其实windows的做法是子进程的页表映射数据是直接从父进程中拷贝而来的,上面已经说过,所有进程的系统空间的映射除了页表和hyperspace两块区块外都是相同的,所以所要做的工作就是申请两个页面,映射到父进程的hyperspace,这样父进程就可以读写这两个页面了,其中一个页面用于保存页目录,其高半部分直接从父进程的pde的高半部分拷贝,一页用于保存hyperspace的页表项,并修改保存目录页面中的两个pte的值,一个是指向其自身的pte(pde),一个是指向hyperspace的pte。至于这两个pte在页目录页面中的偏移是容易计算的,而且是固定不变的。 其实让页目录页面中的某一项的pte值指向其自身也就完成了页目录页面本身的映射,比如要想将页目录页面映射到0xc0300000,只需将偏移0xc00除的pte指向其自身即可,计算公式:0xc0300000/1000*4+0xc0000000-0xc0300000=0xc00。 |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值