码迷,mamicode.com
首页 > 编程语言 > 详细

第16章 Windows线程栈

时间:2015-10-17 20:31:21      阅读:211      评论:0      收藏:0      [点我收藏+]

标签:

16.1 线程栈及工作原理

(1)线程栈简介

  ①系统在创建线程时,会为线程预订一块地址空间(即每个线程私有的栈空间),并调拨一些物理存储器。默认情况下,预订1MB的地址空间并调拨两个页面的存储器

  ②调整线程栈的默认大小可以使用编译选项或#pragma指令,具体用法视编译器不同,VC下可以使用 /Fnewsize 编译选项设置默认栈大小,其中newsize是以字节为单位,也可以使用/STACK:reserve[,commit]连接选项,使用#pragma指令的样式如下:#pragma comment(linker, "/STACK:reserve,commit") ,其中的reserve和commit均以字节为单位。这些信息会被写入.exe或.dll文件的PE文件头中。

  ③也可以在调用CreateThread或_beginthreadex函数时,给dwStackSize参数指定一个值来改变栈的大小,如果该参数为0时,表示PE文件头指定的大小。

(2)线程栈的工作原理

技术分享 

【初始状态】

  ①设页面大小为4KB,栈大小为1MB。图中线程栈的基地址为0x80000000,所有己调拨的页面都具有PAGE_READWRITE保护属性

  ②初始化时,栈顶指针ESP如上图所示(接过0x8100 0000),这个页面是线程开始使用栈的地方。往下看,第2个页面为“防护页面(guard page)”

  ③随着线程调用越来越多的函数,调用树也越来越深,线程所需的栈空间也越来越多。

【栈即将用尽状态】

  ①当线程试图访问“防护页面”的内存时,系统会得到通知,这时系统会先给“防护页面”下面的那个页面调拨物理存储器,接着去除当前“防护页面”的PAGE_GUARD保护标志,然后给刚调拨的存储页指定PAGE_GUARD保护属性。

  ②该项技术使用系统能够在线程需要的时候才增大栈存储器的大小。如果线程的调用树不断加深,那么栈的地址空间区域将很快被占满。

【栈满时的状态】

  ①如果线程的调用树非常深,CPU的ESP指针指向了0x0800 3004。此时,当线程调用另一个函数时,就必须调拨更多的物理存储器。但是当给0x0800 1000页面调拨物理存储器时。它的做法和给区域其他部分调拨物理存储器有所不同。

  ②首先会去除0x0800 2000页面的PAGE_GUARD标志,然后给0x0800 1000页面调拨。但区别在于,此时不会给0x0800 1000指定防护属性。这意味着栈的地址空间区域己经放满所能容纳的所有物理存储器。

  ③当系统给0x0800 1000页面调拨物理存储器时,会执行一个额外操作——抛出EXCEPTION_STACK_OVERFLOW异常,以通知应用程序,从而使程序能够得体地从这异常情况下恢复。(这里提供一种机制让线程栈溢出时,有补救的措施)

  ④但是,如果线程在引发栈溢出异常后继续使用栈,那它会用尽0800 1000页面,并试图访问地址0x800 0000页面的内存。但这个页面被设计为“不可调拨的页面”,所以会抛出访问违规异常。此时系统会收回控制权并弹出错误,从而结束整个进程(而不仅仅是线程!)。如避免这种情况,应用程序可以调用SetThreadStackGuarante函数,以确保Windows在终止进程之前,地址空间中还有指定数量的内存,使应用程序抛出EXCEPTION_STACK_OVERFLOW异常以便让用户自行决定如何处理和恢复。

(3)线程栈溢出时的恢复

  ①当线程访问最后一个防护页面时,系统会抛出EXCEPTION_STACK_OVERFLOW异常。如果线程捕获了该异常并继续执行,那么系统将不会在同一个线程中再次抛出异常,因为后面再也没有防护页面了。

  ②如果希望在同一线程中继续收到EXCEPTION_STACK_OVERFLOW异常,那么应用程序必须重置防护页面。只需调用运行库的_resetstkoflw函数(在malloc.h中定义)

【StackOverflow程序】——演示栈溢出及如何恢复

技术分享

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <locale.h>
#include <malloc.h> //调用_resetstkoflow函数

//递归
void recursive(int recurse){
    int iArray[2000] = {}; //分配栈空间
    if (recurse){
        recursive(recurse);
    }
}

//下标越界错误
void ArrayErr()
{
    int iArray[] = { 3, 4 };
    iArray[10] = 1; //下标越界,无法恢复
}

int stack_overflow_exception_filter(int exception_code){
    if (exception_code == EXCEPTION_STACK_OVERFLOW){
        //执行__except后{}中的代码, 即执行异常处理代码, 不返回到__try中
        return EXCEPTION_EXECUTE_HANDLER;

        //EXCEPTION_CONTINUE_EXECUTION,返回__try块中的异常代码处继续执行,即异常已被正常处理
    } else{
        //继续查找,即本__except块不能处理此异常
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));

    int recurse = 1, iRet = 0;
    for (int i = 0; i < 10;i++){
        _tprintf(_T("第%d次循环\n"), i + 1);
        __try{
            //模拟栈溢出
            //ArrayErr(); //下标越界,无法检测出来,所以不会抛出异常。
            recursive(recurse);

        }__except(stack_overflow_exception_filter(GetExceptionCode())){
            _tprintf(_T("恢复栈溢出....\n"));
            iRet = _resetstkoflw();
        }

        if (!iRet){
            _tprintf(_T("恢复失败\n"));
            break;    
        } else{
            _tprintf(_T("恢复成功\n"));
        }
    }
    _tsystem(_T("PAUSE"));
    return 0;
}

 

第16章 Windows线程栈

标签:

原文地址:http://www.cnblogs.com/5iedu/p/4888094.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!