读书笔记_栈的创建过程
来源:互联网 发布:排版软件免费下载 编辑:程序博客网 时间:2024/06/09 18:48
首先看内核栈的创建过程:PspCreateThread函数是windows内核中用于创建线程的一个重要的内部函数,无论是创建系统线程(PsCreateSystemThread)还是用户线程(NtCreateThread),都离不开这个函数,除了创建重要的ETHREAD结构,PspCreateThread函数的另一个重要的任务是创建内核态栈。
对于GUI线程,Windows会为其创建大内核栈,但是在创建线程时,所有的线程都不是GUI线程,而windows是在线程第一次调用windows子系统内核服务(Win32K)时将其转变为GUI线程。因此当创建线程时,PspCreateThread函数总是调用MmCreateKernelStack函数创建一个默认大小的内核态栈,这个栈的大小是固定的,而且不是可增长的。
当一个线程被转化为GUI线程时,系统的PsConvertToGuiThread函数会为该线程重新创建一个栈,然后使用KeSwitchKernelStack切换到新的栈,新的栈是可以改变大小的,被称为大内核栈(Large Kernel Stack)。大内核态栈的最大值记录在名为MmLargeStackSize的全局变量中。
另外两个全局变量MmLargeStacks和MmSmallStacks分别用来记录系统中大型栈和小型栈的总个数。
在一个线程被转变为GUI线程后,其KTHREAD结构的LargeStack字段会被改为1,同时其Win32Thread字段也会由0变为非0(这可以作为判断是否是GUI线程的一个依据)。转换后,新的栈通常不会立即增大,而是当需要时,调用MmGrowKernelStack函数来增长栈,每次增长的幅度至少为一个页面的大小(x86中为4KB)。内核态的代码(如驱动程序)可以调用IoGetStackLimits函数和IoGetRemainingStackSize函数分别得到当前栈的边界和剩余大小。
来看用户态栈的创建过程:Windows线程的用户态栈是由KERNEL32.DLL中的BaseCreateStack函数创建的。进程中的初始线程的调用过程如下:
CreateProcess
NtCreateProcess
BaseCreateStack
NtCreateThread
初始线程之外的其他线程通常是调用CreateThread或CreateRemoteThread函数创建的。实际上,CreateThread函数只是简单地调用CreateRemoteThread函数,CreateRemoteThread函数在调用内核服务NtCreateThread前,会调用BaseCreateStack函数来创建用户态栈。
BaseCreateStack是位于KERNEL32.DLL中一个未公开的函数,其原型大致如下:
NTSTATUS BaseCreateStack(IN HANDLE hProcess,
IN DWORD dwCommitStackSize, IN DWORD dwReservedStackSize,
OUT OINITIAL_TEB pInitialTeb)
简单来说,BaseCreateStack函数就是在hProcess所指定的进程空间中根据dwReserverdStackSize参数保留一段内存区域,并在这个区域中按照dwCommitStackSize参数所指定的大小提交出一部分作为栈的初始空间。BaseCreateStack函数将所保留和提交内存区域的参数保存到pInitialTeb指向的结构中。而后,这些参数会传递给NtCreateThread内核服务,最终被保存到线程的环境块(TEB)结构中。可以使用!teb命令来观察TEB中包含的栈信息。
CreateThread与CreateRemoteThread的原型如下:
BOOL CreateThread(
DWORD dwCreateFlags = 0,
UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);
这两个函数可以通过参数dwStackSize指定栈的保留大小(需要在参数dwCreationFlags中设置STACK_SIZE_PARAM_IS_A_RESERVATION标志)初始提交大小。栈的初始大小是在PE文件的结构IMAGE_OPTIONAL_HEADER中,其中的SizeOfStackReserve和SizeOfStackCommit是用来指定栈的默认保留大小和提交大小。即使当我们调用CreateThread时将dwStackSize设为0,系统也会使用IMAGE_OPTIONAL_HEADER中指定的大小。SizeofStackReserve和SizeOfStackCommit字段的值是连接程序(Linker)在生成EXE文件时根据连接选项写入的。对于VC编译器,可以通过编译选项 STACKSIZE reserve [, commit] 来设置。
对于链接好的EXE文件,还可以通过EDITBIN工具来修改这两个值。(EDITBIN本人没有使用过,WinXP操作系统下没有现成的Editbin命令。需要安装Visual Studio才能获得这个命令。可以在主Visual Studio目录的VC98BIN目录下找到它。Editbin的用法可以参见MSDN:http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/vccore/html/_core_editbin_options.asp)
保留和提交内存都是通过系统的虚拟内存分配函数来完成的,SDK中公开了VirtualAlloc和VirtualAllocEx API,事实上它们都是调用了内核服务NtAllocateVirtualMemory
BaseCreateStack创建用户态栈的过程主要有以下几个步骤:
1. 将提交空间大小取为内存页大小的倍数,将总保留大小取整为内存分配的最小粒度(4字节)
2. 调用内存分配函数(NtAllocateVirtualMemory)保留内存地址空间,内存分配类型为MEM_RESERVE,分配的大小为栈空间保留大小。
3. 调用NtAllocateVirtualMemory在保留空间的高地址端提交初始栈空间,内存分配类型为MEM_COMMIT,分配的大小为初始提交大小
4. 如果保留空间大于初始提交空间,则第3步会多提交一个页面用作栈保护页面。保护的方法是调用虚拟内存保护函数(VirtualProtect)对这个页面设置PAGE_GUARD属性。
- 读书笔记_栈的创建过程
- 子类的创建及初始化过程_Thinking in Java(4)_读书笔记(1)
- 读书笔记_编译过程
- 读书笔记_栈
- 读书笔记_栈
- 数据库_MySQL_简单的存储过程 创建_调用_查找
- android内核剖析 创建窗口过程读书笔记
- 读书笔记_锋利的jQuery
- 读书笔记_学习的艺术
- 搜索引擎的架构_读书笔记
- 《Android内核剖析》读书笔记 第8章 创建窗口的过程
- Mysql创建存储过程示例_设置变量方法_
- 读书笔记_windows内核调试_part 2_内核对话过程
- 读书笔记_C#技术内幕_第十三章(创建结构)
- 读书笔记_Windows的启动过程
- 嵌入式操作系统的基本概念_读书笔记_1
- 算法导论第三章_函数的增长_读书笔记
- 窗体创建的过程
- IOS @property
- arm linux 系统调用实现
- Java_11_02课堂总结
- 自定义标签描述(2)
- WEBSERVICE连接ORACLE
- 读书笔记_栈的创建过程
- Linux下软件安装方法总结
- 由自旋锁引起的思考
- enum 和 typedef
- hadoop学习总结一
- Oct 10 00:00:00 UTC+0800 2010转换成时间 2010-10-10这样的形式
- Windows内核驱动中操作文件
- SQL生成唯一ID号
- 课堂总结