谈谈windows线程栈

上一篇 / 下一篇  2012-07-03 08:36:44 / 个人分类:杂谈

,KP3K"a [2NO;XA0  当系统创建线程时会为线程预订一块地址空间区域,注意仅仅是预订。默认情况下预定的这块区域的大小是1MB,虽然预订这么多,但是系统并不会给 全部区域调拨物理存储器。默认情况下,仅仅为两个页面挑拨。x86系统下每个页面是4KB.其他页面会在访问的时候由系统调拨。这仅仅是在创建线程时,程 序员指定CreateThread的第二个参数StackSize为0时才会发挥作用。如果程序员传入的是非零值,那么调拨的物理存储器的数量就是这个非 零值。51Testing软件测试网 w&iE*\e6Y'VL

51Testing软件测试网^X_*uTZ+nC,m*`

  这两个默认的页面是从哪里来的呢?原来是在链接的时候,系统会将当前编译器中指定的大小写入PE文件中。(PE文件即为exe可 执行文件),如果StackSize被指定为0,系统就将PE文件中读出的值作为预订和调拨的页面数。在编译器将预定和挑拨的数量写入PE文件之前,我们 可以用两种方法,改变编译器写入的大小。一种是使用/F选项,另一种是使用/STACK选项。51Testing软件测试网*bfpdXp y

g~&bs1s0  预定空间后,系统会为线程栈的最上方的两 个页面调拨物理存储器,esp指向第一个挑拨页面的起始位置。第二个页面也被调拨了页面,它被称为防护页面,具有PAGE_GUARD属性,当线程试图访 问防护页面时,系统会得到通知,会为防护页面下方的页面调拨物理存储器,同时,将原来防护页面的PAGE_GUARD的属性去掉,为他下方刚刚调拨的页面 指定PAGE_GUARD属性,他就变成了防护页面,此操作,会使防护页面不断下移.不断为页面调拨物理存储器。

3G6Y Xn | @051Testing软件测试网*c|\p j9bE5`)?"K

  由于栈空间默认是1M,(即使再大他也是有限的)当调用函数时,或是局部变量增多时,被调拨的页面越来越多,51Testing软件测试网*G R(k${R O"}:C-N/I

51Testing软件测试网|7e4e?`:p;y c

  使防护页面不断下移,最终他会到达这样一个状态:(3000,2000,1000仅仅起标记作用并不代表实际情况)51Testing软件测试网9L[~ QX3i N p

Uflj U'KV/m0

  此时,线程访问地址为3000的页面,由于它具有保护属性,因此系统会为地址为2000的页面调拨物理存储器,系统会去除页面地址为3000的PAGE_GUARD属性,然后给地址为2000的页面调拨物理存储器。但此时会与原来不同。

V&ZGl8?~.Z0

   原来为页面调拨物理存储器后,会将他上方的页面的保护属性去掉,而把他设为保护属性,现在区别在于,新调拨的页面并没有被设为保护属性。与此同时,系统 会抛出EXCEPTION_STACK_OVERFLOW异常,此异常会被系统捕捉,进而通知我们的程序,至于如何响应这个通知,则有程序员自己定义。另 外由于系统是在线程访问具有保护属性的页面时,才会为他下方的页面调拨物理存储器,地址2000的页面没有保护属性,所以地址为1000的页面永远也不会 被调拨物理存储器。51Testing软件测试网0Z/wx$CW4q:}8e

  之所以不为地址为1000的页面,(即栈底)调拨物理存储器,是为了保护进程内的其他数 据。因为当栈增长到超过预定的区域后,他会覆盖进程地址空间的其他数据。如果线程在引发堆栈溢出异常后继续使用栈,即访问地址为1000的页面,由于此页 面永远不会被调拨物理存储区,而访问未被调拨的页面会引发违规访问异常,此时进程将会被终止。

]u/~@+w0

  系统故意不为栈底调拨物理存储器,用来检测程序溢出的情况。但这样仍然不能完全阻止这种情况。如:

Rv)x6f%D'q0DWORD WINAPI threadpro(PVOID)
j0[%|'AN3j tEr0{51Testing软件测试网7rUqI5]1DZJ
      int array[10];
/s E#xN\rgl0      array[2000]=100;    //假设此时array+2000的地址在栈外
ys | z*zdwR-L;pH0     return 0;51Testing软件测试网%bU^irbZ+I8o;`f
}
e$C@.u6H051Testing软件测试网.Fju$Qla%]-N r

(C7P3ruP$Y9e0  请问此时会发生访问违规,导致进程被终止吗?假设此时array+2000的地址在栈外。

/O _sv,_9~m0

$A)u @F5T*YR+M0  答案是不确定。首先得明确什么情况下会导致访问违规。51Testing软件测试网 yL(|$s4v

*kR6plS0  前面我们提到,当访问没有被调拨物理存储器的页面时会发生访问违规。此处,array[2000]所处的页面有没有被调拨物理页面我们是不清楚 的。当它所处的位置已经被调拨物理存储器,线程会用100覆盖此处的值,导致的结果不确定。如果没有被调拨,就会引发访问违规,导致进程被结束。 通过这个例子可以知道,即使操作系统采取了不为栈底挑拨物理存储器的方法,但仍不能解决此类问题。51Testing软件测试网F_{3X(}!CF oL

e;h6xvE0  再看一个例子:51Testing软件测试网7Vbg(Q!V)V hD(~

51Testing软件测试网 ik\_x0q2C

,]%pc-^ Khf8h0
DWORD WINAPI threadpro(PVOID)51Testing软件测试网g9zu1gg Sp H
{
b"_#i;o` T+aI0      int array[1000];
$j;p4gK+Yfu\0      array[900]=100;    //假设array+900位于防护页面之下。
'Fmyd Gb DFRx0      return 0;
-d5Z A5V e;a*}b5X0}

:ja.r,zvQ%J5X/NN#u Z0  此例中,线程需要1000*4个字节的栈空间,假设此时没有发生栈溢出的情况,也就是说array+900仍然指向栈内,此时会发生什么情况 呢??由于需要大量的栈空间,在开始运行时,系统会为1000*4个字节预订空间,不会为他们全部调拨物理存储器。只有当程序真正访问时才会为他调拨物理 存储器。此时为存在一个问题。如果此时程序要访问位于防护页面之下的地址会发生什么情况?

y5YL q%ZyeL051Testing软件测试网 M:z2[lKj

  由于位于防护页面之下的栈空间都没有被调拨物理存储器,如果仍然去访问的话,将会造成访问违规。

/?ak;L h3r0

Gbk8TD.O%clo r3`0  如何解决这个问题呢?原来是编译器编译程序的时候,编译器会获得系统的页面大小。当它算出线程执行中需要的栈空间大于系统的页面大小时他会自动 插入代码来调用检查函数,检查函数的作用就是:为程序要访问的栈空间之前的所有页面调拨物理存储器,确保程序去访问时发生违规访问的情况。如此例,当程序 试图访问array+900的栈地址时,系统会为他和他之前的所有栈空间调拨物理存储器。

ch]{Z]G0

)LuH6h:WC.`jc+qm0  检查函数一般由编译器厂商用汇编来实现。

T*[7x(F'gZ;i0
b gAJlC}0

TAG:

 

评分:0

我来说两句

Open Toolbar