• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2021-06-07 14:21 Aet 隐藏边栏 |   抢沙发  8 
文章评分 3 次,平均分 5.0

定义

  1. 一般将进程定义成一个正在运行的程序的一个实例

    1. 一个内核对象,操作系统用它来管理进程。
      内核对象也是系统保存进程统计信息的地方。

    2. 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。

      此外,还包含动态内存分配,比如线程堆栈和堆的分配。

  2. 进程要做什么事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。

  3. 一个进程可以有多个线程,所有线程都在进程的地址空间中”同时“执行代码。要做到这一点,每个线程都有它自己的CPU寄存器和堆栈。

  4. 每个进程都至少要有一个线程来执行地址空间中包含的代码。当系统创建一个进程的时候,会自动为进程创建第一个线程,这个线程就是主线程。

  5. 如果没有线程要执行地址空间中包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。

  6. 对于所有要运行的线程,操作系统会轮询为每个线程分配一些CPU时间片。

子系统值

  1. 对于CUI程序,子系统值是/SUBSYSTEM:CONSOLE
  2. 对于GUI程序,子系统值是/SUBSYSTEM:WINDOWS

过程

  1. 用户运行应用程序时,操作系统的加载程序会检查可执行文件映像的文件头,并获得这个子系统值。
  2. 如果值表明是一个CUI程序,加载程序会自动确保有一个可用的文本控制台窗口。另外,如果有必要的时候,会创建一个新窗口。
  3. 如果值表明是一个GUI程序,加载器就不会创建控制台窗口。它只是加载这个程序,一旦程序开始运行了,操作系统就不再关心应用程序的界面是什么类型的。
  4. Windows程序必须有一个入口点函数,程序开始运行时,这个函数会被调用。
  5. C/C++开发人员可以使用两种入口点函数

  1. 函数调用的具体的符号,取决于是否使用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
  1. 在链接可执行文件时,链接器将选择正确的C/C++运行库启动函数。如果指定了/SUBSYSTEM:WINDOWS链接器开关,链接器就会寻找WinMain或wWinMain函数。如果没有找到,链接器就会返回一个无法解析的外部符号错误。找到的话,链接器将根据具体情况分别选择WinMainCRTStartup或wWinMainCRTStartup函数
  2. 类似,如果指定了/SUBSYSTEM:CONSOLE链接器开关,链接器就会寻找main或wmain函数,并根据情况分别选择mainCRTStartup或wmainCRTStartup函数。同样的,如果main或wmain函数都没有找到,链接器会返回一个无法解析的外部符号的错误。
  3. 另外,我们其实可以在程序中移除/SUBSYSTEM链接开关。如果这么做了,链接时,链接器会检查代码中包含4个函数中的哪一个(WinMain、wWinMain、main、wmain),并据此推算出可执行文件应该是哪一个子系统,以及应该在可执行文件中嵌入哪一个C/C++启动函数。
  4. 启动函数的用途:
    1. 获取指向新进程的完整命令行的一个指针
    2. 获取指向新进程的环境变量的一个指针
    3. 初始化C/C++运行库的全局变量。如果包含了StdLib.h,就可以在代码里访问这些变量。
    4. 初始化C运行库内存分配函数malloc、calloc和其他底层I/O例程使用的堆。
    5. 调用所有全局和静态C++类对象的构造函数。

全局变量

  1. 可以访问的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。

调用过程

  1. 完成所有初始化工作后,C/C++启动函数就会调用应用程序的入口点函数。
  2. 如果是_tWinMain函数,并且定义了_UNICODE,如下

  1. _ImageBase是一个链接器定义的伪变量,表明可执行文件被映射到应用程序内存中的什么位置。
  2. 如果是_tmain函数,并且定义了_UNICODE,如下

  1. 入口函数返回后,启动函数将调用C运行库函数exit,向其传递返回值nMainRetVal。由exit开始执行任务。
  2. exit调用_onexit函数调用所注册的任何一个函数
  3. 调用所有全局和静态C++类对象的析构函数
  4. 在DEBUG生成中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄漏报告
  5. 再调用操作系统的ExitProcess函数,向其传入nMainRetVal。这会使操作系统”杀死“我们的进程,并设置它的退出代码。

注意

  1. Microsoft是不赞成使用所有这些变量的。因为使用这些变量的代码可能会在C运行库初始化这些变量之前开始执行。所以建议之间调用对应的Windows API函数。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2021-11-20
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享