定义
-
一般将进程定义成一个正在运行的程序的一个实例
-
一个内核对象,操作系统用它来管理进程。
内核对象也是系统保存进程统计信息的地方。 -
一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。
此外,还包含动态内存分配,比如线程堆栈和堆的分配。
-
-
进程要做什么事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。
-
一个进程可以有多个线程,所有线程都在进程的地址空间中”同时“执行代码。要做到这一点,每个线程都有它自己的CPU寄存器和堆栈。
-
每个进程都至少要有一个线程来执行地址空间中包含的代码。当系统创建一个进程的时候,会自动为进程创建第一个线程,这个线程就是主线程。
-
如果没有线程要执行地址空间中包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。
-
对于所有要运行的线程,操作系统会轮询为每个线程分配一些CPU时间片。
子系统值
- 对于CUI程序,子系统值是/SUBSYSTEM:CONSOLE
- 对于GUI程序,子系统值是/SUBSYSTEM:WINDOWS
过程
- 用户运行应用程序时,操作系统的加载程序会检查可执行文件映像的文件头,并获得这个子系统值。
- 如果值表明是一个CUI程序,加载程序会自动确保有一个可用的文本控制台窗口。另外,如果有必要的时候,会创建一个新窗口。
- 如果值表明是一个GUI程序,加载器就不会创建控制台窗口。它只是加载这个程序,一旦程序开始运行了,操作系统就不再关心应用程序的界面是什么类型的。
- Windows程序必须有一个入口点函数,程序开始运行时,这个函数会被调用。
- C/C++开发人员可以使用两种入口点函数
1 2 3 4 5 6 7 8 9 10 11 12 |
// 第一种 Int WINAPI _tWinMain( HINSTANCE hInstanceExe, HINSTANCE, PTSTR pszCmdLine, int nCmdSHow); // 第二种 int _tmain( int argc, TCHAR* argv[], TCHAR* envp[]); |
- 函数调用的具体的符号,取决于是否使用Unicode字符串。操作系统实际并不调用我们写的入口函数。而是调用由C/C++运行库实现并在链接时使用
-entry:
命令行选项来设置的一个C/C++运行时启动函数。这个函数初始化C/C++运行库,使我们能够调用malloc和free之类的函数。它还确保了在我们的代码开始执行之前,我们声明的任何全局和静态C++对象都能够被正确的构造。
启动函数
应用程序类型 | 入口点函数 | 嵌入可执行文件的启动函数 |
处理ansi字符字符串的GUI程序 | _tWinMain(WinMain) | WinMainCRTStartup |
处理Unicode字符字符串的GUI程序 | _tWinMain(wWinMain) | wWinMainCRTStartup |
处理ansi字符字符串的CUI程序 | _tmain(Main) | mainCRTStartup |
处理Unicode字符字符串的CUI程序 | _tmain(Wmain) | wmainCRTStartup |
- 在链接可执行文件时,链接器将选择正确的C/C++运行库启动函数。如果指定了/SUBSYSTEM:WINDOWS链接器开关,链接器就会寻找WinMain或wWinMain函数。如果没有找到,链接器就会返回一个无法解析的外部符号错误。找到的话,链接器将根据具体情况分别选择WinMainCRTStartup或wWinMainCRTStartup函数。
- 类似,如果指定了/SUBSYSTEM:CONSOLE链接器开关,链接器就会寻找main或wmain函数,并根据情况分别选择mainCRTStartup或wmainCRTStartup函数。同样的,如果main或wmain函数都没有找到,链接器会返回一个无法解析的外部符号的错误。
- 另外,我们其实可以在程序中移除/SUBSYSTEM链接开关。如果这么做了,链接时,链接器会检查代码中包含4个函数中的哪一个(WinMain、wWinMain、main、wmain),并据此推算出可执行文件应该是哪一个子系统,以及应该在可执行文件中嵌入哪一个C/C++启动函数。
- 启动函数的用途:
- 获取指向新进程的完整命令行的一个指针
- 获取指向新进程的环境变量的一个指针
- 初始化C/C++运行库的全局变量。如果包含了StdLib.h,就可以在代码里访问这些变量。
- 初始化C运行库内存分配函数malloc、calloc和其他底层I/O例程使用的堆。
- 调用所有全局和静态C++类对象的构造函数。
全局变量
- 可以访问的C/C++运行库全局变量如下
名称 | 类型 | 描述 |
_osver | unsigned int | 操作系统的构建版本号 |
_winmajor | unsigned int | 十六进制表示的Windows系统的主版本号 |
_winminor | unsigned int | 十六进制表示的Windows系统的次版本号 |
_winver | unsigned int | (_winmajor<<8)+ _winminor。换用GetVersionEx |
__argc | unsigned int | 命令行上传递的参数个数 |
__argv | char | 长度为__argc的一个数组,其中是anisi/unicode字符串的指针 |
__wargv | wchar_t | 如果定义了/_UNICODE,_argv为NULL,没有定义则/_wargv为NULL, 换用GetCommandLine |
_environ | char | 一个指针数组,每一项都指向环境字符串 |
_wenviron | wchar_t | 如果没有定义_UNICODE,_wenviron为NULL,否则_environ为NULL。换用GetEnvironmentStrings或GetEnvironmentVariable。 |
_pgmptr | char | 正在运行的程序的名称机器ANSI/UNICODE完整路径。 |
_wpgmptr | wchar_t | 如果没有定义_UNICODE,_wpgmptr为NULL,否则_pgmptr为NULL。换用GetModuleFileName并传第一个参数为NULL。 |
调用过程
- 完成所有初始化工作后,C/C++启动函数就会调用应用程序的入口点函数。
- 如果是_tWinMain函数,并且定义了_UNICODE,如下
1 2 3 4 5 6 7 8 9 10 |
GetStartupInfo(&StartupInfo); int nMainRetVal = wWinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineUnicode, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT); // 没有定义_UNICODE GetStartupInfo(&StartupInfo); int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow : SW_SHOWDEFAULT); |
- _ImageBase是一个链接器定义的伪变量,表明可执行文件被映射到应用程序内存中的什么位置。
- 如果是_tmain函数,并且定义了_UNICODE,如下
1 2 3 4 |
int nMainRetVal = wmain(argc, argv, envp); // 没有定义_UNICODE int nMainRetVal = main(argc, argv, envp); |
- 入口函数返回后,启动函数将调用C运行库函数exit,向其传递返回值nMainRetVal。由exit开始执行任务。
- exit调用_onexit函数调用所注册的任何一个函数
- 调用所有全局和静态C++类对象的析构函数
- 在DEBUG生成中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄漏报告
- 再调用操作系统的ExitProcess函数,向其传入nMainRetVal。这会使操作系统”杀死“我们的进程,并设置它的退出代码。
注意
- Microsoft是不赞成使用所有这些变量的。因为使用这些变量的代码可能会在C运行库初始化这些变量之前开始执行。所以建议之间调用对应的Windows API函数。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Soui应用 动画一06/24
- ♥ Windows 核心编程 _ 进程五06/30
- ♥ X86_64汇编学习记述三08/08
- ♥ Windows 高级调试 _ 内存破坏03/21
- ♥ Windows核心编程_必备知识04/27
- ♥ Windows 核心编程 _ 内核对象一06/04