进程
进程要做任何事情,必须让一个线程在它的上下文运行。该线程负责执行进程地址空间包含的代码。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程(primary thread)。
对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。会采取round-robin的方式。
两部分
一个内核对象
一个地址空间
Windows程序
Windows支持两种类型的应用程序:GUI程序和CUI程序。前者是图形用户界面,后者是控制台用户界面。这两种应用程序的界限是模糊的。
用Microsoft Visual Studio来创建一个应用程序项目的时候,继承开发环境会设置各种链接器开关。
对于CUI程序,这个链接器开关是/SUBSYSTEM:CONSOLE
对于GUI程序,这个链接器开关是/SUBSYSTEM:WINDOWS
操作系统的加载程序会检查可执行文件映像的文件头,并获取这个子系统值。如果此值表明是一个CUI程序,加载程序会自动确保有一个可用的文本台窗口,如果此值表明是一个GUI程序,加载器就不会创建控制台窗口。
Windows程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。
C/C++采用下面两种入口点函数
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++对象都被正确的构造。
所有C/C++运行库启动函数所做的事情基本都是一样的,区别在于它们要处理的是ANSI字符串还是Unicode字符串,在初始化C运行库之后,它们调用的是哪一个入口点函数。Visual C++自带C运行库的源代码。可以在crtree.c文件中找到4个启动函数的源代码。
用途如下:
- 获取指向新进程的完整命令行的一个指针
- 获取指向新进程的环境变量的一个指针
- 初始化C/C++运行库的全局变量。如果包含了StdLib.h,我们的代码就可以访问一些变量
- 初始化C运行库内存分配函数和其他I/O例程使用的堆
- 调用所有全局和静态C++类对象的构造函数
完成这些初始化工作之后,C/C++程序就会调用应用程序的入口点函数。
调用过程如下
GetStartupInfo(&StartupInfo);
int nMainRetVal=&WinMain((HINSTANCE)&_ImageBase,NULL,pszCommandLineUnicode,(StartupInfo.dwFlags&STARTF_USESHOWWINDOW)?StartupInfo.wShowWindow:SW_SHOWDEFAULT);
_Image是操作系统定义的一个伪变量,表明可执行文件被映射到应用程序内存中的什么位置。
进程的命令行
系统在创建一个新进程时,会传一个命令行给它。这个命令行几乎总是非空的
用于创建新进程的可执行文件的名称是命令行上的第一个标记(token)
C运行库的启动代码开始执行一个GUI应用程序时,会调用Windows函数GetCommandLine来获取进程的完整命令行,忽略可执行文件的名称,然后将指向命令行剩余部分的一个指针传给WinMain的pszCmdLine函数
应用程序可以通过自己选择的任何一种方式来分析和解释命令行字符串。