草庐IT

c++ - 在 Windows 上以编程方式设置堆栈大小

coder 2024-02-16 原文

是否可以在 WinAPI 中为当前线程在运行时设置堆栈大小,如 setrlimit 在 Linux 上吗?
我的意思是增加当前线程的保留堆栈大小,如果它对于当前的要求来说太小了。
这是在一个可能被其他编程语言的线程调用的库中,因此它不是在编译时设置堆栈大小的选项。

如果没有,关于将堆栈指针更改为动态分配的内存块的程序集蹦床之类的解决方案有什么想法吗?

常见问题解答:代理线程是一个万无一失的解决方案(除非调用者线程的堆栈非常小)。然而,线程切换似乎是一个性能杀手。我需要大量堆栈用于递归或 _alloca .这也是为了性能,因为堆分配很慢,特别是如果多个线程从堆并行分配(它们被相同的 libc/CRT 互斥锁阻塞,因此代码变成串行)。

最佳答案

您不能在库代码中的当前线程(分配自我,删除旧)中完全交换堆栈,因为在旧堆栈中 - 返回地址,可能是指向堆栈中变量的指针等。

并且您无法扩展堆栈(已为其分配(保留/提交)且不可扩展的虚拟内存。

但是可能会分配临时堆栈并在调用期间切换到此堆栈。在这种情况下,您必须从 StackBase 中保存旧的 StackLimitNT_TIB(在 winnt.h 中查看此结构),设置新值(您需要为新堆栈分配内存),调用(对于切换堆栈,您需要一些汇编代码 - 您不能这样做仅在 c/c++ 上)并返回原始 StackBaseStackLimit 。在内核模式中存在对此的支持 - KeExpandKernelStackAndCallout

然而在用户模式中存在 Fibers - 这是非常罕见的使用,但看起来完全匹配任务。使用 Fiber,我们可以在 当前线程内创建额外的堆栈/执行上下文

所以一般解决方案是下一个(对于图书馆):

DLL_THREAD_ATTACH 上:

  • 将线程转换为纤程
    ( ConvertThreadToFiber ) (如果它返回 false 也检查GetLastErrorERROR_ALREADY_FIBER - 这也是可以的代码)
  • 并通过调用创建自己的 Fiber CreateFiberEx

  • 我们只做一次。比,每次调用您的过程时,这需要大量堆栈空间:
  • 通过调用记住当前光纤 GetCurrentFiber
  • 光纤的设置任务
  • 通过调用 SwitchToFiber
  • 切换到您的光纤
  • 光纤内部调用程序
  • 返回原始光纤(从调用 GetCurrentFiber 中保存)
    再次由 SwitchToFiber

  • 最后在 DLL_THREAD_DETACH 你需要:
  • 通过 DeleteFiber
  • 删除您的光纤
  • 通过调用 ConvertFiberToThread 将纤程转换为线程,但仅
    如果初始 ConvertThreadToFiber 返回 true(如果是ERROR_ALREADY_FIBER - 让谁先把线程转换成纤程
    它回来 - 在这种情况下,这不是您的任务)

  • 您需要一些(通常很小)与您的光纤/线程相关的数据。这当然必须是每个线程变量。所以你需要使用 __declspec(thread) 来声明这些数据。或直接使用 TLS(或为此存在哪些现代 C++ 功能)

    演示实现是下一个:
    typedef ULONG (WINAPI * MY_EXPAND_STACK_CALLOUT) (PVOID Parameter);
    
    class FIBER_DATA 
    {
    public:
        PVOID _PrevFiber, _MyFiber;
        MY_EXPAND_STACK_CALLOUT _pfn;
        PVOID _Parameter;
        ULONG _dwError;
        BOOL _bConvertToThread;
    
        static VOID CALLBACK _FiberProc( PVOID lpParameter)
        {
            reinterpret_cast<FIBER_DATA*>(lpParameter)->FiberProc();
        }
    
        VOID FiberProc()
        {
            for (;;)
            {
                _dwError = _pfn(_Parameter);
                SwitchToFiber(_PrevFiber);
            }
        }
    
    public:
    
        ~FIBER_DATA()
        {
            if (_MyFiber)
            {
                DeleteFiber(_MyFiber);
            }
    
            if (_bConvertToThread)
            {
                ConvertFiberToThread();
            }
        }
    
        FIBER_DATA()
        {
            _bConvertToThread = FALSE, _MyFiber = 0;
        }
    
        ULONG Create(SIZE_T dwStackCommitSize, SIZE_T dwStackReserveSize);
    
        ULONG DoCallout(MY_EXPAND_STACK_CALLOUT pfn, PVOID Parameter)
        {
            _PrevFiber = GetCurrentFiber();
            _pfn = pfn;
            _Parameter = Parameter;
            SwitchToFiber(_MyFiber);
            return _dwError;
        }
    };
    
    __declspec(thread) FIBER_DATA* g_pData;
    
    ULONG FIBER_DATA::Create(SIZE_T dwStackCommitSize, SIZE_T dwStackReserveSize)
    {
        if (ConvertThreadToFiber(this))
        {
            _bConvertToThread = TRUE;
        }
        else
        {
            ULONG dwError = GetLastError();
    
            if (dwError != ERROR_ALREADY_FIBER)
            {
                return dwError;
            }
        }
    
        return (_MyFiber = CreateFiberEx(dwStackCommitSize, dwStackReserveSize, 0, _FiberProc, this)) ? NOERROR : GetLastError();
    }
    
    void OnDetach()
    {
        if (FIBER_DATA* pData = g_pData)
        {
            delete pData;
        }
    }
    
    ULONG OnAttach()
    {
        if (FIBER_DATA* pData = new FIBER_DATA)
        {
            if (ULONG dwError = pData->Create(2*PAGE_SIZE, 512 * PAGE_SIZE))
            {
                delete pData;
    
                return dwError;
            }
    
            g_pData = pData;
    
            return NOERROR;
        }
    
        return ERROR_NO_SYSTEM_RESOURCES;
    }
    
    ULONG WINAPI TestCallout(PVOID param)
    {
        DbgPrint("TestCallout(%s)\n", param);
    
        return NOERROR;
    }
    
    ULONG DoCallout(MY_EXPAND_STACK_CALLOUT pfn, PVOID Parameter)
    {
        if (FIBER_DATA* pData = g_pData)
        {
            return pData->DoCallout(pfn, Parameter);
        }
    
        return ERROR_GEN_FAILURE;
    }
    
    if (!OnAttach())//DLL_THREAD_ATTACH
    {
        DoCallout(TestCallout, "Demo Task #1");
        DoCallout(TestCallout, "Demo Task #2");
        OnDetach();//DLL_THREAD_DETACH
    }
    

    还要注意,在单线程上下文中执行的所有纤程 - 与线程关联的多个纤程不能并发执行 - 只能按顺序执行,并且您自己控制切换时间。所以不需要任何额外的同步。和 SwitchToFiber - 这是完整的用户模式过程。执行速度非常快,永远不会失败(因为永远不会分配任何资源)

    更新

    尽管使用 __declspec(thread) FIBER_DATA* g_pData; 更简单(代码更少),但更适合直接使用 TlsGetValue/TlsSetValue 并在线程内部首次调用时分配 FIBER_DATA,但不适用于所有线程。还有 __declspec(thread) 在 XP for dll 中不正确工作(根本没有工作)。所以可以进行一些修改

    DLL_PROCESS_ATTACH 分配您的 TLS 插槽 gTlsIndex = TlsAlloc();
    并在 DLL_PROCESS_DETACH 上释放它
    if (gTlsIndex != TLS_OUT_OF_INDEXES) TlsFree(gTlsIndex);
    

    在每个 DLL_THREAD_DETACH 通知调用上
    void OnThreadDetach()
    {
        if (FIBER_DATA* pData = (FIBER_DATA*)TlsGetValue(gTlsIndex))
        {
            delete pData;
        }
    }
    

    DoCallout 需要在下一个方式修改
    ULONG DoCallout(MY_EXPAND_STACK_CALLOUT pfn, PVOID Parameter)
    {
        FIBER_DATA* pData = (FIBER_DATA*)TlsGetValue(gTlsIndex);
    
        if (!pData)
        {
            // this code executed only once on first call
    
            if (!(pData = new FIBER_DATA))
            {
                return ERROR_NO_SYSTEM_RESOURCES;
            }
    
            if (ULONG dwError = pData->Create(512*PAGE_SIZE, 4*PAGE_SIZE))// or what stack size you need
            {
                delete pData;
                return dwError;
            }
    
            TlsSetValue(gTlsIndex, pData);
        }
    
        return pData->DoCallout(pfn, Parameter);
    }
    

    所以改为通过 DLL_THREAD_ATTACHOnAttach() 上的每个新线程分配堆栈更好地仅在真正需要时(在第一次调用时)为线程分配堆栈

    如果其他人也尝试使用纤维,则此代码可能存在纤维问题。在 msdn example 代码中说,如果 ERROR_ALREADY_FIBER 返回 0,则不检查 ConvertThreadToFiber。因此,如果我们在决定创建光纤之前,我们可以等待主应用程序不正确处理这种情况,并且它也尝试在我们之后使用光纤。 ERROR_ALREADY_FIBER 也不适用于 xp(从 vista 开始)。

    所以可能和另一种解决方案 - 自己创建线程堆栈,并临时切换到它做需要大堆栈空间的调用。 main 不仅需要为堆栈和交换 esp(或 rsp)分配空间,而且不要忘记在 StackBase 中正确建立 StackLimitNT_TIB - 这是充分必要条件(否则异常和保护页扩展将不起作用)。

    尽管这个替代解决方案需要更多的代码(手动创建线程堆栈和堆栈切换)它也可以在 xp 上工作,并且在其他人也尝试在线程中使用纤程的情况下没有任何影响
    typedef ULONG (WINAPI * MY_EXPAND_STACK_CALLOUT) (PVOID Parameter);
    
    extern "C" PVOID __fastcall SwitchToStack(PVOID param, PVOID stack);
    
    struct FIBER_DATA
    {
        PVOID _Stack, _StackLimit, _StackPtr, _StackBase;
        MY_EXPAND_STACK_CALLOUT _pfn;
        PVOID _Parameter;
        ULONG _dwError;
    
        static void __fastcall FiberProc(FIBER_DATA* pData, PVOID stack)
        {
            for (;;)
            {
                pData->_dwError = pData->_pfn(pData->_Parameter);
    
                // StackLimit can changed during _pfn call
                pData->_StackLimit = ((PNT_TIB)NtCurrentTeb())->StackLimit;
    
                stack = SwitchToStack(0, stack);
            }
        }
    
        ULONG Create(SIZE_T Reserve, SIZE_T Commit);
    
        ULONG DoCallout(MY_EXPAND_STACK_CALLOUT pfn, PVOID Parameter)
        {
            _pfn = pfn;
            _Parameter = Parameter;
    
            PNT_TIB tib = (PNT_TIB)NtCurrentTeb();
    
            PVOID StackBase = tib->StackBase, StackLimit = tib->StackLimit;
    
            tib->StackBase = _StackBase, tib->StackLimit = _StackLimit;
    
            _StackPtr = SwitchToStack(this, _StackPtr);
    
            tib->StackBase = StackBase, tib->StackLimit = StackLimit;
    
            return _dwError;
        }
    
        ~FIBER_DATA()
        {
            if (_Stack)
            {
                VirtualFree(_Stack, 0, MEM_RELEASE);
            }
        }
    
        FIBER_DATA()
        {
            _Stack = 0;
        }
    };
    
    ULONG FIBER_DATA::Create(SIZE_T Reserve, SIZE_T Commit)
    {
        Reserve = (Reserve + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
        Commit = (Commit + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
    
        if (Reserve < Commit || !Reserve)
        {
            return ERROR_INVALID_PARAMETER;
        }
    
        if (PBYTE newStack = (PBYTE)VirtualAlloc(0, Reserve, MEM_RESERVE, PAGE_NOACCESS))
        {
            union {
                PBYTE newStackBase;
                void** ppvStack;
            };
    
            newStackBase = newStack + Reserve;
    
            PBYTE newStackLimit = newStackBase - Commit;
    
            if (newStackLimit = (PBYTE)VirtualAlloc(newStackLimit, Commit, MEM_COMMIT, PAGE_READWRITE))
            {
                if (Reserve == Commit || VirtualAlloc(newStackLimit - PAGE_SIZE, PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE|PAGE_GUARD))
                {
                    _StackBase = newStackBase, _StackLimit = newStackLimit, _Stack = newStack;
    
    #if defined(_M_IX86) 
                    *--ppvStack = FiberProc;
                    ppvStack -= 4;// ebp,esi,edi,ebx
    #elif defined(_M_AMD64)
                    ppvStack -= 5;// x64 space
                    *--ppvStack = FiberProc;
                    ppvStack -= 8;// r15,r14,r13,r12,rbp,rsi,rdi,rbx
    #else
    #error "not supported"
    #endif
    
                    _StackPtr = ppvStack;
    
                    return NOERROR;
                }
            }
    
            VirtualFree(newStack, 0, MEM_RELEASE);
        }
    
        return GetLastError();
    }
    
    ULONG gTlsIndex;
    
    ULONG DoCallout(MY_EXPAND_STACK_CALLOUT pfn, PVOID Parameter)
    {
        FIBER_DATA* pData = (FIBER_DATA*)TlsGetValue(gTlsIndex);
    
        if (!pData)
        {
            // this code executed only once on first call
    
            if (!(pData = new FIBER_DATA))
            {
                return ERROR_NO_SYSTEM_RESOURCES;
            }
    
            if (ULONG dwError = pData->Create(512*PAGE_SIZE, 4*PAGE_SIZE))
            {
                delete pData;
                return dwError;
            }
    
            TlsSetValue(gTlsIndex, pData);
        }
    
        return pData->DoCallout(pfn, Parameter);
    }
    
    void OnThreadDetach()
    {
        if (FIBER_DATA* pData = (FIBER_DATA*)TlsGetValue(gTlsIndex))
        {
            delete pData;
        }
    }
    

    SwitchToStack 的汇编代码:在 x86 上
    @SwitchToStack@8 proc
        push    ebx
        push    edi
        push    esi
        push    ebp
        xchg    esp,edx
        mov     eax,edx
        pop     ebp
        pop     esi
        pop     edi
        pop     ebx
        ret
    @SwitchToStack@8 endp
    

    对于 x64:
    SwitchToStack proc
        push    rbx
        push    rdi
        push    rsi
        push    rbp
        push    r12
        push    r13
        push    r14
        push    r15
        xchg    rsp,rdx
        mov     rax,rdx
        pop     r15
        pop     r14
        pop     r13
        pop     r12
        pop     rbp
        pop     rsi
        pop     rdi
        pop     rbx
        ret
    SwitchToStack endp
    

    使用/测试可以是下一个:
    gTlsIndex = TlsAlloc();//DLL_PROCESS_ATTACH
    
    if (gTlsIndex != TLS_OUT_OF_INDEXES)
    {
        TestStackMemory();
    
        DoCallout(TestCallout, "test #1");
    
        //play with stack, excepions, guard pages
        PSTR str = (PSTR)alloca(256);
        DoCallout(zTestCallout, str);
        DbgPrint("str=%s\n", str);
    
        DoCallout(TestCallout, "test #2");
    
        OnThreadDetach();//DLL_THREAD_DETACH
    
        TlsFree(gTlsIndex);//DLL_PROCESS_DETACH
    }
    
    void TestMemory(PVOID AllocationBase)
    {
        MEMORY_BASIC_INFORMATION mbi;
        PVOID BaseAddress = AllocationBase;
        while (VirtualQuery(BaseAddress, &mbi, sizeof(mbi)) >= sizeof(mbi) && mbi.AllocationBase == AllocationBase)
        {
            BaseAddress = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
            DbgPrint("[%p, %p) %p %08x %08x\n", mbi.BaseAddress, BaseAddress, (PVOID)(mbi.RegionSize >> PAGE_SHIFT), mbi.State, mbi.Protect);
        }
    }
    
    void TestStackMemory()
    {
        MEMORY_BASIC_INFORMATION mbi;
        if (VirtualQuery(_AddressOfReturnAddress(), &mbi, sizeof(mbi)) >= sizeof(mbi))
        {
            TestMemory(mbi.AllocationBase);
        }
    }
    
    ULONG WINAPI zTestCallout(PVOID Parameter)
    {
        TestStackMemory();
    
        alloca(5*PAGE_SIZE);
    
        TestStackMemory();
    
        __try
        {
            *(int*)0=0;
        } 
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            DbgPrint("exception %x handled\n", GetExceptionCode());
        }
    
        strcpy((PSTR)Parameter, "zTestCallout demo");
    
        return NOERROR;
    }
    
    ULONG WINAPI TestCallout(PVOID param)
    {
        TestStackMemory();
    
        DbgPrint("TestCallout(%s)\n", param);
    
        return NOERROR;
    }
    

    关于c++ - 在 Windows 上以编程方式设置堆栈大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45258017/

    有关c++ - 在 Windows 上以编程方式设置堆栈大小的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

      我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

    3. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

      我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

    4. ruby-openid:执行发现时未设置@socket - 2

      我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

    5. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

      我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

    6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    7. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    8. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    9. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    10. ruby-on-rails - 正确的 Rails 2.1 做事方式 - 2

      question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参

    随机推荐