stack 扩展机制

  windows中,每个线程都关联一个stack,stack的默认大小是1M,用于存放临时变量,函数参数,返回地址等。

  但是当一个线程开始运行的时候不是其相关stack的内存就真正被提交,因为如果一个进程有10个线程,那么如果这10个线程的stack的内存都被提交,那么虚拟内存就占用了10M,就需要想对应的页表项等开销,而且这10M到底是否被真的使用还是个未知数,所以系统的策略是只提交几个页面,然后通过一个guard page来实现按需提交。

  先看一下GUARD_PAGE:

        TEB at 7ffdf000
        ExceptionList:        0013fd0c
        StackBase:            00140000
        StackLimit:           0013e000
        0: kd> dt _TEB 7ffdf000
        ntdll!_TEB
        ......
        +0xe0c DeallocationStack : 0x00040000    // 1M stack 范围 StackBase ~ DeallocationStack
 0: kd> .formats(0x140000-0x40000)/0n1024
        Evaluate expression:
        Hex:     00000400
        Decimal: 1024
        Octal:   00000002000
        Binary:  00000000 00000000 00000100 00000000
        Chars:   ....
        Time:    Thu Jan 01 08:17:04 1970
        Float:   low 1.43493e-042 high 0
        Double:  5.05923e-321
                              // stack 大小 1024KB  --- 1M

     StackBase ~ StackLimit:           0013e000 这个是 COMMIT 的页面        StackLimit 下个页面是              MEM_COMMIT | PAGE_READWRITE | PAGE_GUARD        StackLimit 再下一个页面是         MEM_RESERVE

  也就是说TEB,确切说是TIB记录着线程的guard page。

  当一个函数的局部变量过大,例如:char szBuffer[0x10000] = { 0 },那么线程被系统预先提交的页不满足使用了,那么编译器会在该函数的开头插入_chkstk,用以给该函数提交足够大的stack的页面用以装载很大的局部变量。

  _chkstk的核心一个是使ESP减少,另一个就是提交页面,提交页面是个很有趣的过程:

public  _alloca_probe

_chkstk proc

_alloca_probe    =  _chkstk

        push    ecx

; Calculate new TOS.

        lea     ecx, [esp] + 8 - 4      ; TOS before entering function + size for ret value
        sub     ecx, eax                ; new TOS

; Handle allocation size that results in wraparound.
; Wraparound will result in StackOverflow exception.

        sbb     eax, eax                ; 0 if CF==0, ~0 if CF==1
        not     eax                     ; ~0 if TOS did not wrapped around, 0 otherwise
        and     ecx, eax                ; set to 0 if wraparound

        mov     eax, esp                ; current TOS
        and     eax, not ( _PAGESIZE_ - 1) ; Round down to current page boundary

cs10:
        cmp     ecx, eax                ; Is new TOS
        jb      short cs20              ; in probed page?
        mov     eax, ecx                ; yes.
        pop     ecx
        xchg    esp, eax                ; update esp
        mov     eax, dword ptr [eax]    ; get return address
        mov     dword ptr [esp], eax    ; and put it at new TOS
        ret

; Find next lower page and probe
cs20:
        sub     eax, _PAGESIZE_         ; decrease by PAGESIZE
        test    dword ptr [eax],eax     ; probe page.
        jmp     short cs10

_chkstk endp

        end

  其中,test    dword ptr [eax],eax; 可是这行代码仅仅是读了一下eax指向的内存,这里的读操作将触发一个STATUS_GUARD_PAGE异常, 内核通过捕获这个异常,从而知道你的线程已经越过了栈中已提交内存区域的边界, 这时应该增加新的页了。

  操作系统规定栈中的 commit 页时,必须逐页提交,具体的实现是:对已提交的内存区域的最后一个页设置 PAGE_GUARD属性,当这个页发生 STATUS_GUARD_PAGE异常时(这个异常会自动清除其 PAGE_GUARD属性), 再commit下一个页,同时设置其 PAGE_GUARD属 性。

   typedef struct _NT_TIB
    {
        struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
        PVOID StackBase;                                    // 栈的最高地址 , 栈底
        PVOID StackLimit;                                   // 已经commit的栈的内存的最低地址, 栈顶,
        .....
    } NT_TIB;

  栈的内存如此排布:

  StackBase ----> |..............|  <----- 高 -                  |______________|             |
                  |..............|             |
                  |______________|             |
                  |..............|     Protect 00000004 PAGE_READWRITE
                  |______________|     State   00001000 MEM_COMMIT
                  |..............|
                  |______________|             |
                  |..............|             |
                  |______________|             |
                  |..............|             |
  StackLimit ---> |______________| <___________||..............|     Protect 00000104 PAGE_READWRITE | PAGE_GUARD
                  |______________| <___State   00001000 MEM_COMMIT
                  |..............|             |
                  |______________|             |
                  |..............|             |
                  |______________|     State   00002000 MEM_RESERVE  (没有Commit的页谈不上Protect)
                  |..............|             |
                  |______________|             |
                  |..............|  <----------/  

  当一个线程被创建的时候,操作系统会给它的栈reserve一块区域,通常大小为1M,然后立刻在栈顶commit n个pages。
  

  前 n-1 个Page是供线程立刻可以使用,第二个page是守护页面(guard page), 当线程用完第一个页面的时候,需要更多栈内存会访问到守护页面,操作系统会得到通知。系统会再commit一个页面,把下一个页面作为新的守护页面。

时间: 2025-01-13 12:49:15

stack 扩展机制的相关文章

atitit.编程语言&#160;类与对象的&#160;扩展机制.doc

atitit.编程语言 类与对象的 扩展机制.doc 1.1. Java 下一代: 没有继承性的扩展1 1.2. 继承1 1.3. 使用cglib动态为Java类添加方法1 1.4. 工具类 1 1.5. Wrap 包装类  装饰器模式2 1.6. 扩展方法 (其实就是工具类的语法糖)2 1.7. Scala 的隐式转换2 1.8. 类别类.ExpandoMetaClass2 1.1. Java 下一代: 没有继承性的扩展 Groovy.Scala 和 Clojure 提供了许多扩展机制,但继承

OpenGL版本与OpenGL扩展机制

OpenGL版本与OpenGL扩展机制 1 opengl的版本区别(在opengl官方文档中有详细说明)    针对Opengl不同版本的升级是主要是扩展指令集.    现在版本是4.0啦1.1 opengl1.11995年,SGI推出了更为完善的OpenGL 1.1版本.OpenGL 1.1的性能比1.0版提高甚多.其中包括改进打印机支持,在增强元文件中包含OpenGL的调用,顶点数组的新特性,提高顶点位置.法线.颜色.色彩指数.纹理坐标.多边形边缘标识的传输速度,引入了新的纹理特性等等.1.

从ExtensionLoader理解Dubbo扩展机制

Dubbo的扩展机制是怎么实现的?最简单的回答就是@SPI. Dubbo的插件化思路来源于Java SPI. JAVA SPI 机制 SPI的全名为Service Provider Interface. 大多数人可能不了解,因为它是JDK针对插件或者厂商的.java spi机制的思想就是: 我们的系统的抽象模块(接口),往往有很多不同方案的实现.如日志模块,jdbc模块等.而在面向对象的设计里,我们一般都要做模块解耦,面向接口编程.但如果要切换接口的不同实现,就可能需要改动代码.为了实现模块的自

Dubbo源码分析系列-扩展机制的实现

Spring可扩展Schema 像标签dubbo:monitor.dubbo:service.dubbo:provider等怎么读的,读完之后怎么又是怎么解析的呢? 以上标签都是基于Spring可扩展Schema提供的自定义配置 下面举个例子 1)创建JavaBean 首先设计好配置项,通过JavaBean来建模,如类People public class People { private String name; private Integer age; } 2)编写XSD文件 XSD文件是X

Dubbo中SPI扩展机制解析

dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name,可以通过name找到具体的实现. 每个扩展点都有一个@Adaptive实例,用来注入到依赖这个扩展点的某些类中,运行时通过url参数去动态判断最终选择哪个Extension实例用. dubbo的SPI扩展机制增加了对扩展点自动装配(类似IOC)和自动包装(类似AOP)的支持. 标注了@Activat

AppCan Widget插件扩展机制

AppCan Widget插件扩展机制,通过AppCan平台生成的应用,可以理解为一个Widget包(即在IDE创建项目是看到的'phone'文件夹),和一个AppCan平台中间件组成的.通常的情况下,一个应用是由一个Widget+AppCan构成,那么,有没有可能说'n个Widget+AppCan'的机制呢,答应是肯定的,这就是Widget 插件机制,是针对主widget以及普通widget的一种增强性的扩展机制,可以将具有特定功能的widget封装成一个单独的widget包存放到plugin

聊聊Dubbo - Dubbo可扩展机制实战

摘要: 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架.今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性. 1. Dubbo的扩展机制 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架.今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性. 如同罗马不是一天建成的,任何系统都一定是从小系统不断发展成为大系统的,想要从一开始就把系统设计的足够完善是不可能的,相反的,我们应该关注当下的需求,然后再不断地对系统进行迭代.在代码层面,要求我们适当的对

聊聊Dubbo - Dubbo可扩展机制源码解析

摘要: 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的实现,并自己实现了一个LoadBalance.是不是觉得Dubbo的扩展机制很不错呀,接下来,我们就深入Dubbo的源码,一睹庐山真面目. 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的实现,并自己实现了一个LoadBalance.是不是觉得Dubbo的扩展机制很不错呀,接下来,我们就深入Dubbo的源码,一睹庐

Spring.factories扩展机制

和Java SPI的扩展机制类似,Spring Boot采用了spring.factories的扩展机制,在很多spring的starter 包中都可以找到,通过在 META-INF/spring.factories文件中指定自动配置类入口,从而让框架加载该类实现jar的动态加载. 这种为某个接口寻找服务实现的机制,有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要. 我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块.jdb