数学之路-vb.net并行计算(4)

三、栈内存 

1、每个WINDOWS都有一个栈基址和栈限址,二者合在一起表示栈的有效内存范围。栈限址不是固定的,程序需要更多内存空间里,栈限址没有超过保留的栈内存范围,则可以要求更多的内存页 

2、在栈限址外是栈的守护页,当访问到守护页时,会引发STATUS_GUARD_PAGE_VIOLATION异常,当异常发生后,操作系统捕捉后,提交下一页内存,然后将其做为新的守护页,这相当于分配了一个新的页给程序访问,就是栈动态增长。 
具体机制是当访问了带PAGE_GUARD 属性的内存时,操作系统意识到当前栈内存已经用完,因为栈内存的下一页就是守护页,操作系统马上清除守护页的PAGE_GUARD 标志,将其用于补充当前的栈内存,同时申请一个新的内存页做为守护页,随着新守护页的不断申请,越来越多的老守护页加入当前栈空间,这就是动态增长 

3、从MSDN中可以查到 
A guard page provides a one-shot alarm for memory page access. This can be useful for an application that needs to monitor the growth of large dynamic data structures. For example, there are operating systems that use guard pages to implement automatic stack checking. 

To create a guard page, set the PAGE_GUARD page protection modifier for the page. This value can be specified, along with other page protection modifiers, in the VirtualAlloc, VirtualAllocEx, VirtualProtect, and VirtualProtectEx functions. The PAGE_GUARD modifier can be used with any other page protection modifiers, except PAGE_NOACCESS. 

If a program attempts to access an address within a guard page, the system raises a STATUS_GUARD_PAGE_VIOLATION (0x80000001) exception. The system also clears the PAGE_GUARD modifier, removing the memory page‘s guard page status. The system will not stop the next attempt to access the memory page with a STATUS_GUARD_PAGE_VIOLATION exception. 

If a guard page exception occurs during a system service, the service fails and typically returns some failure status indicator. Since the system also removes the relevant memory page‘s guard page status, the next invocation of the same system service won‘t fail due to a STATUS_GUARD_PAGE_VIOLATION exception (unless, of course, someone reestablishes the guard page). 

The following short program illustrates the behavior of guard page protection. 

保护页提供了一个内存页的访问警告,对应用程序监视,操作系统用它来实现栈的自动检查 
在 VirtualAlloc, VirtualAllocEx, VirtualProtect, and VirtualProtectEx 函数中可以定义这个参数 

目前我们使用VB.NET进行多线程编程,使用的是.NET的CLR托管线程,CLR使用了SetThreadStackGuarantee来以更大的增量来递增栈限址,这个API指定了守护区域大小,如果将大小设置为0,则返回当前的守护区域大小。

3、运用stacktrace类实现线程栈回溯跟踪 


本博客所有内容是原创,如果转载请注明来源

http://blog.csdn.net/myhaspl/


技术分享

 代码如下:


Imports System
Imports System.Threading
Imports System.Diagnostics.StackTrace






Module Module1


    Sub Main()
        Dim main_x As Integer
        main_x = 5
        Call sub1(main_x)
    End Sub
    Private Sub sub1(sub1_x As Integer)
        Dim jg As Integer
        jg = sub1_x * sub1_x
        Call sub2(jg)
    End Sub
    Private Sub sub2(sub2_x As Integer)
        Dim jg As Integer
        jg = sub2_x * 2
        jg = jg * jg
        Dim st As New StackTrace(True)
        Console.WriteLine(" 栈跟踪: {0}", _
  st.ToString())
        Dim i As Integer


        For i = 0 To st.FrameCount - 1


            Dim sf As StackFrame = st.GetFrame(i)
            Console.WriteLine()
            Console.WriteLine("回溯调用栈, 方法: {0}", _
                sf.GetMethod())


            Console.WriteLine("回溯调用栈, 行号 : {0}", _
                sf.GetFileLineNumber())
        Next i










    End Sub


End Module

下面这句定义了StackTrace 类的新实例,实现线程栈跟踪

StackTrace 是使用调用方的当前线程创建的,参数含义如下:

如果为 true,则捕获文件名、行号和列号;否则为 false

 Dim st As New StackTrace(True)


本博客所有内容是原创,如果转载请注明来源

http://blog.csdn.net/myhaspl/

3、运用stacktrace类实现线程栈回溯跟踪 

技术分享

 代码如下:


Imports System
Imports System.Threading
Imports System.Diagnostics.StackTrace






Module Module1


    Sub Main()
        Dim main_x As Integer
        main_x = 5
        Call sub1(main_x)
    End Sub
    Private Sub sub1(sub1_x As Integer)
        Dim jg As Integer
        jg = sub1_x * sub1_x
        Call sub2(jg)
    End Sub
    Private Sub sub2(sub2_x As Integer)
        Dim jg As Integer
        jg = sub2_x * 2
        jg = jg * jg
        Dim st As New StackTrace(True)
        Console.WriteLine(" 栈跟踪: {0}", _
  st.ToString())
        Dim i As Integer


        For i = 0 To st.FrameCount - 1


            Dim sf As StackFrame = st.GetFrame(i)
            Console.WriteLine()
            Console.WriteLine("回溯调用栈, 方法: {0}", _
                sf.GetMethod())


            Console.WriteLine("回溯调用栈, 行号 : {0}", _
                sf.GetFileLineNumber())
        Next i










    End Sub


End Module

下面这句定义了StackTrace 类的新实例,实现线程栈跟踪

StackTrace 是使用调用方的当前线程创建的,参数含义如下:

如果为 true,则捕获文件名、行号和列号;否则为 false

 Dim st As New StackTrace(True)

 4、栈溢出 

1)溢出情况 

a)线程试图提交比保留大小更多的栈内存页 

b)没有物理内存也没有虚拟内存可供提交更多的守护页 

2、栈溢出后还想继续运行程序,必须重置守护页,可以使用CRT的_resetstkoflw。 


不过对于大的数据,可以考虑将某些数据移到堆中。堆栈是有限的,甚至在用户模式下也是如此,如果无法提交堆栈页,会导致堆栈溢出异常。_resetstkoflw 函数可以将系统从堆栈溢出的情况恢复为正常,从而使程序得以继续运行,而不会由于出现异常错误而失败。如果未调用 _resetstkoflw 函数,则在上一个异常后不会显示保护页。当下次发生堆栈溢出时,根本不会显示异常,进程将在没有任何警告的情况下终止。 


System.Runtime.CompilerServices.RuntimeHelpers在线程栈空间的探查方面有很大的作用 

下面这是这个类的常用方法 


EnsureSufficientExecutionStack  确保剩余的堆栈空间足够大,可以执行一般的 .NET Framework 函数。  
   Equals(Object, Object)  确定指定的 Object 实例是否被视为相等。  
   ExecuteCodeWithGuaranteedCleanup  使用一个 Delegate 执行代码,同时使用另一个 Delegate 在异常情况下执行附加代码。  
   GetHashCode(Object)  用作特定类型的哈希函数,适合在哈希算法和数据结构(如哈希表)中使用。  
     GetObjectValue  将值类型装箱。  
     InitializeArray  提供从存储在模块中的数据初始化数组的快速方法。  
   PrepareConstrainedRegions  将代码体指定为受约束的执行区域 (CER)。  
   PrepareConstrainedRegionsNoOP  指定代码体为受约束的执行区域 (CER),而不执行任何探测。  
   PrepareContractedDelegate  提供应用程序用来动态准备 AppDomain 事件委托的方法。  
   PrepareDelegate  指示应准备指定委托以包含在受约束的执行区域 (CER) 中。  
   PrepareMethod(RuntimeMethodHandle)  准备一个要包含在受约束的执行区域 (CER) 中的方法。  
   PrepareMethod(RuntimeMethodHandle, RuntimeTypeHandle())  准备一个要包含在受约束的执行区域 (CER) 中的具有指定实例化的方法。  
   ProbeForSufficientStack  探测某个数量的堆栈空间,以确保不会在后续的代码块内发生堆栈溢出(假设您的代码仅使用有限适中的堆栈空间)。 建议使用受约束的执行区域 (CER),而不使用此方法。  
   RunClassConstructor  运行指定的类构造函数方法。  
   RunModuleConstructor  运行指定的模块构造函数方法。 

 

 

其中比较常用的是

ProbeForSufficientStack  和PrepareConstrainedRegions

 从MSDN中可看出以下细节:

1)PrepareConstrainedRegions
编译器使用此方法来将 catch、finally 和 fault 块标记为受约束的执行区域 (CER)。 标记为受约束的区域的代码必须只调用其他具有高可靠性协定的代码。 它不能分配或虚调用未准备的或不可靠的方法,除非它已准备好处理失败。

请注意,除了 NOP 之外,在对 PrepareConstrainedRegions 方法的调用和 try 块之间不允许使用其他任何中间语言操作码。

受约束的执行区域 (CER) 是创作可靠托管代码的机制的一部分。CER 定义一个区域,在该区域中公共语言运行库 (CLR) 会受到约束,不能引发可使区域中的代码无法完全执行的带外异常。在该区域中,用户代码受到约束,不能执行会导致引发带外异常的代码。PrepareConstrainedRegions 方法必须直接位于 try 块之前,并将 catch、finally 和 fault 块标记为受约束的执行区域。标记为受约束的区域后,代码只能调用其他具有强可靠性约定的代码,而且代码不应分配或者对未准备好的或不可靠的方法进行虚调用,除非代码已经准备好处理错误。CLR 为 CER 中正在执行的代码延迟线程中止。

除批注的 try 块外,受约束的执行区域还以其他形式用于 CLR 中


 

CLR 会事先准备 CER 以避免出现内存不足的情况。进行事先准备的目的是为了避免 CLR 在实时编译或类型加载时发生内存不足的情况。

CER 中不允许下面的操作:

显式分配。

获取锁。

装箱。

多维数组访问。

通过反射进行的方法调用。

Enter 或 Lock。

安全检查。不执行命令,仅链接命令。

COM 对象和代理的 Isinst 和 Castclass

获取或设置透明代理上的字段。

序列化。

函数指针和委托。

 

 如果您计划使用适当数量的堆栈空间,则使用 try/finally 或 try/catch 块后跟对 RuntimeHelpers.PrepareConstrainedRegions 方法的调用。 如果您正在调用递归方法或计划使用大量堆栈空间,则必须使用 RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup 方法。

可如下构造代码


       RuntimeHelpers.PrepareConstrainedRegions()
        Try
            分配大量堆栈空间的代码
        Finally
             分配失败后的清理工作或安全分配空间的代码


        End Try


下面是msdn中可靠地设置句柄的操作:

要可靠地将句柄设置为指定的预先存在的句柄,必须确保本机句柄的分配,以及该句柄在SafeHandle 对象中的后续记录是原子操作这些操作之间,如果出现任何故障(如线程中止或内存不足异常)都会导致该本机句柄泄漏。可以使用 PrepareConstrainedRegions 方法确保句柄不会泄漏。

<StructLayout(LayoutKind.Sequential)> _
Structure MyStruct
    Public m_outputHandle As IntPtr
End Structure ‘MyStruct




NotInheritable Class MySafeHandle
    Inherits SafeHandle


    ‘ Called by P/Invoke when returning SafeHandles
    Public Sub New()
        MyBase.New(IntPtr.Zero, True)


    End Sub




    Public Function AllocateHandle() As MySafeHandle
        ‘ Allocate SafeHandle first to avoid failure later.
        Dim sh As New MySafeHandle()


        RuntimeHelpers.PrepareConstrainedRegions()
        Try
        Finally
            Dim myStruct As New MyStruct()
            NativeAllocateHandle(myStruct)
            sh.SetHandle(myStruct.m_outputHandle)
        End Try


        Return sh


    End Function

 2)ProbeForSufficientStack

探测某个数量的堆栈空间,以确保不会在后续的代码块内发生堆栈溢出(假设您的代码仅使用有限适中(12页栈内存)的堆栈空间)。 建议使用受约束的执行区域 (CER),而不使用此方法。

使用方式是

RuntimeHelpers.ProbeForSufficientStack()

分配或使用栈内存的语句

3)此外

RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup 也很重要

Public Shared Sub ExecuteCodeWithGuaranteedCleanup ( _
code As RuntimeHelpers..::..TryCode, _ backoutCode As RuntimeHelpers..::..CleanupCode, _ userData AsObject _
)

从其参数就可以看出它对处理栈溢出的重要性了

 

 

code
类型:System.Runtime.CompilerServices.RuntimeHelpers.TryCode
要尝试的代码的委托。

backoutCode
类型:System.Runtime.CompilerServices.RuntimeHelpers.CleanupCode
在发生异常时要运行的代码的委托。

userData
类型:System.Object
要传递给 code 和 backoutCode 的数据


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。