Windows Internals 笔记——进程
1.一般将进程定义成一个正在运行的程序的一个实例,由以下两部分构成:
- 一个内核对象,操作系统用它来管理进程,内核对象也是系统保存进程统计信息的地方。
- 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外它还包含动态内存分配,比如线程堆栈和堆的分配。
2.进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。
3.每个线程都有它自己的一组CPU寄存器和它自己的堆栈。对于所有要运行的线程,操作系统会轮流为每一个线程调度一些CPU时间,它会采取轮询的方式为每个线程都分配时间片,从而营造出所有线程都在并发运行的假象。
4.用户运行应用程序时,操作系统的加载程序会检查可执行文件映像的文件头,并获取这个子系统。如果此值表明是一个CUI程序,加载程序会自动确保有一个可用的文本控制台窗口(从命令提示符启动),或者创建一个新窗口(从windows资源管理器启动)。如果此值表明是一个GUI程序,加载器只是加载这个程序。一旦程序开始运行,操作系统就不再关心应用程序的界面是什么类型。
5.Windows应用程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。操作系统实际并不调用我们所写的入口点函数。相反,它会调用由C/C++运行库实现并在链接时使用-entry:命令行选项来设置的一个C/C++运行时启动函数。该函数将初始化C/C++运行库,使我们能调用malloc和free之类的函数。它还确保了在我们的代码开始执行之前,我们声明的任何全局和静态C++对象都被正确地构造。
6.在链接可执行文件时,将根据链接器开关 /SUBSYSTEM:WINDOWS或者 /SUBSYSTEM:CONSOLE以及UNICODE和ANSI来选择正确地C/C++运行库启动函数。

7.一旦链接器开关/SUBSYSTEM被移除,链接器会检查代码中的函数(WinMain,main...)是四个中的哪一个来推算。
8.所有C/C++运行库启动函数所做的事情基本都一样,区别在于它们要处理的是ANSI还是UNICODE字符串。以及在初始化C运行库之后,它们调用的是哪一个入口点函数。这些启动函数的用途简单总结如下。
- 获取指向新进程的完整命令行的一个指针
- 获取指向新进程的环境变量的一个指针
- 初始化C/C++运行库的全局变量。如果包含了StdLib.h,我们的代码就可以访问这些变量。
- 初始化C运行库内存分配函数(malloc和calloc)和其他底层I/O例程使用的堆(heap)。
- 调用所有全局和静态C++类对象的构造函数。

9.完成上述所有的初始化工作之后,C/C++启动函数就会调用应用程序的入口点函数。
// 如果我们写了_tWinMain & 定义了_UNICODE
GetStartupInfo(&StartupInfo);
int nMainRetVal = wWinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineUnicode,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);
// 如果我们写了_tWinMain & 没有定义_UNICODE
GetStartupInfo(&StartupInfo);
int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi,
(StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT);
注意,__ImageBase是一个链接器定义的伪变量,表明可执行文件被映射到应用程序内存中的什么位置。
// 如果我们写了_tmain & 定义了_UNICODE
int nMainRetval = wmain(argc, argv, envp);
// 如果我们写了_tmain & 没有定义_UNICODE
int nMainRetval = main(argc, argv, envp);
注意,用VS向导生成应用程序时,CUI应用程序的入口中没有定义第三个参数(环境变量块),如果需要访问进程的环境变量,只需将上述调用换成下面这一行:
int _tmain(int argc, TCHAR* argv[], TCHAR* env[])
这个env参数指向一个数组,数组中包含所有环境变量及其值,两者用等号(=)分隔。
10.入口点函数返回后,启动函数将调用C运行库函数exit,向其传递返回值(nMainRetVal)。exit函数执行以下任务。
- 调用_onexit函数调用所注册的任何一个函数
- 调用所有全局和静态C++类对象的析构函数。
- 在DEBUG生成中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄漏报告。
- 调用操作系统的ExitProcess函数,向其传入nMainRetVal。这会导致操作系统“杀死”我们的进程,并设置它的退出代码。
11.加载到进程地址空间的每一个可执行文件或DLL文件都被赋予了一个独一无二的实例句柄。可执行文件的实例被当作WinMain函数的第一个参数hInstanceExe传入。
12.事实上,HMOUDLE和HINSTANCE完全是一回事。之所以有两种数据类型,是由于在16位Windows中,HMOUDLE和HINSTANCE表示不同类型的数据。
13.WinMain的hInstanceExe参数的实际值是一个内存基地址,系统将可执行文件的映像加载到进程地址空间中的这个位置。具体加载到哪一个基地址,是由连接器决定的。不同的链接器使用不同的默认基地址。可以使用GetModuleHandle函数来返回一个句柄/基地址。
14.C/C++运行库启动代码总是向WinMain的hPrevInstance参数传递NULL。该参数用于16位Windows系统。VS在向导生成的C++ GUI项目中利用UNREFERENCED_PARAMETER宏来消除这种警告。
15.C运行库的启动代码开始执行一个GUI应用程序的时候,会调用Windows函数GetCommandLine来获取进程的完整命令行,忽略可执行文件的名称,然后将指向命令行剩余部分的一个指针传给WinMain的pszCmdLine参数。
16.GetCommandLine函数返回一个缓冲区指针,缓冲区中包含完整的命令行(包括已执行的文件的完整路径名),而且这个函数返回的总是同一个缓冲区的地址,所以最好不要向其中写入数据。
17.每个进程都有一个与它关联的环境块,这是在进程地址空间内分配的一块内存,其中包含字符串和下面相似:

注意空格是有意义的。
18.用户登录Windows时,系统会创建外壳(shell)进程,并将一组环境字符串与其关联。系统通过检查注册表中的两个注册表项来获得初始的环境字符串。
- 第一个注册表项包含应用于系统的所有环境变量的列表:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
- 第二个注册表项包含应用于当前登录用户的所有环境变量的列表:
HKEY_CURRENT_USER\Environment
19.通常子进程会继承(不共享同一个环境块)一组环境变量,这些环境变量和父进程的环境变量相同,不过,父进程可以控制哪些环境变量允许子进程继承。
20.每个进程都关联了一组标志,作用时让系统知道进程如何响应严重错误,包括磁盘介质错误、未处理的异常、文件查找错误以及数据对齐错误等。进程可以调用SetErrorMode(UINT fuErrorMode)函数来告诉系统如何处理这些错误。

默认情况下,子进程会继承父进程的错误模式标志,例如一个进程已经打开了一个错误标志,并生成了一个子进程,则子进程也会打开这个标志。不过子进程并不知道这一点。父进程可以阻止子进程继承其错误模式。
21.系统跟踪记录着进程的当前驱动器和目录,但没有记录每个驱动器的当前目录,操作系统通过进程的环境字符串来提供多个驱动器的当前目录。例如:
=C:C:\Utility\Bin
=D:=D:\Program Files
如果变量没有找到,系统就假定指定驱动器的当前目录是它的根目录。
Windows Internals 笔记——进程的更多相关文章
- Windows Internals 笔记——进程的权限
1.大多数用户都用一个管理员账户来登录Windows,在Vista之前,这样的登录会创建一个安全令牌.每当有代码试图使用一个受保护的安全资源时,操作系统就会出示这个令牌.从包括Windows资源管理器 ...
- Windows Internals 笔记——终止进程
1.进程可以通过以下四种方式终止: 主线程的入口点函数返回(强烈推荐的方式) 进程中的一个线程调用ExitProcess函数(避免这种方式) 另一个进程中的线程调用TerminateProcess函数 ...
- Windows Internals 笔记——关联性
1.默认情况下,Windows Vista在给线程分配处理器时,使用软关联.意思是如果其他因素都一样,系统将使线程在上一次运行的处理器上运行.让线程始终在同一个处理器上运行有助于重用仍在处理器高速缓存 ...
- Windows Internals 笔记——线程优先级
1.每个线程都被赋予0(最低)~31(最高)的优先级数.当系统确定给哪个线程分配CPU时,它会首先查看优先级为31的线程,并以循环的方式进行调度.如果有优先级为31的线程可供调度,那么系统就会将CPU ...
- Windows Internals 笔记——线程调度
1.线程内核对象中的CONTEXT反应了线程上一次执行时CPU寄存器的状态.大约每隔20ms,Windows都会查看所有当前存在的线程内核对象.Windows在可调度的线程内核对象中选择一个,并将上次 ...
- Windows Internals 笔记——线程
1.进程有两个组成部分,一个进程内核对象和一个地址空间.线程也有两个组成部分: 一个是线程的内核对象,操作系统用它管理线程.系统还用内核对象来存放线程统计信息的地方. 一个线程栈,用于维护线程执行时所 ...
- Windows Internals 笔记——作业
1.Windows提供了一个作业内核对象,它允许我们将进程组合在一起并创建一个“沙箱”来限制进程能够做什么.创建只包含一个进程的作业同样非常有用,因为这样可以对进程施加平时不能施加的限制. 2.如果进 ...
- Windows Internals 笔记——CreateProcess
1.一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始使用计数为1.然后系统为新进程的主线程创建一个线程内核对象(使其计数为1). 2.CreateProcess在进程完全初 ...
- Windows Internals 笔记——内核对象
1.每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问.这个内存块是一个数据结构,其成员维护着与对象相关的信息. 2.调用一个会创建内核对象的函数后,函数会返回一个句柄,它标 ...
随机推荐
- 《Effective C++》继承与面对对象设计:条款32-条款40
条款32:确定你的public继承塑模出is-a关系 public继承意味着is-a.适用于base class身上的每一个函数也一定适用于derived class. 条款33:避免遮掩继承而来的名 ...
- vue.js实战——计算属性
1set和get: 注意: this.lastName=names[names.length-1];//解决连续输入空格后lastName消失的问题 练习代码如下: <!DOCTYPE html ...
- dubbo框架提供Main方法运行容器的几种方式(转)
本文使用的是dubbo提供的主类com.alibaba.dubbo.container.Main启动容器. 主要区别是提供不同插件的的启动方式. 目录 一.项目内容 1.1.目录结构图 1.2 ...
- CodeSmith如何生成实体类 ,完善版
<%-- Name: Database Table Properties Author: Paul Welter Description: Create a list of properties ...
- libavcodev may be vulnerable or is not supported, and should be updated for play video
media.libavcodec.allow-obsolete
- docker之常用命令、自定制镜像、公(私)仓库的上传和下载
一.docker命令 1.参数和命令汇总 1. 参数 Options: --config=~/.docker Location of client config files #客户端配置文件的位置 - ...
- 2.8 hashlib模块
- A.01.12—模块的输出—通讯(CAN&LIN)
AN和LIN相关的内容很多,今天仅对几年前困扰过我的一个疑问进行说明. 以前最常见的通迅方式为CAN和LIN,但现在也有很多其他的通讯方式了,而这两种通讯方式仍使用广泛. 前几年常听人说CAN的成本和 ...
- 读Zepto源码之内部方法
数组方法 定义 var emptyArray = [] concat = emptyArray.concat filter = emptyArray.filter slice = emptyArray ...
- 更换gcc工具链
title: 更换gcc工具链 date: 2019/1/16 19:27:51 toc: true --- 更换gcc工具链 下载后解压到一个临时目录先看看文件结构 mkdir tmp tar xj ...