windows核心编程---第四章 进程
CreateProcess函数
接下来进入到本章最重要的知识点:CreateProcess函数。
该函数用以创建一个进程:
- Bool CreateProcess(
- PCTSTR pszApplicationName,
- PTSTR pszCommandLine,
- PSECURTITY_ATTRIBUTES psaProcess,
- PSECUTRITY_ATTRIBUTE psaThread,
- Bool hInheritHandles,
- DWROD fdwCreate,
- PVOID pvEnvironment,
- PCTSTR pszCurDir,
- PSTARTUPINFO psiStartInfo,
- PPROCESS_INFORMATION ppiProcInfo
- );
当此函数被调用时,系统将首先创建一个进程对象,其初始使用计数为1。进程内核对象并不是进程本身,而是操作系统用来管理这个进程的一个数据结构。此后系统为新进程创建一个虚拟地址空间,并将可执行文件的代码及数据加载到进程的地址空间。然后系统会新进程的主线程创建一个线程内核对象,也将其使用计数设为1。和进程内核对象一样它也是操作系统用以管理线程的数据结构。主线程首先会执行C/C++运行时的启动函数,启动函数调用入口函数。进程被创建成功后CreateProcess返回true,函数返回前CreateProcess可能还没有完全初始化好。
psaApplicationName和pszCommandLine分别指定新进程要使用的可执行文件称和要传给新进程的命令行字符串。
注意此处的pszCommandLine是非常量字符串。传入常量字符串将会导致访问违规,因为在内部CreateProcess会修改传入的命令行字符串,返回时再将这个字符串还原。
所以一下代码是错误的:
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
CreateProcess(NULL,TEXT
(“NOTEPAD”),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
因为TEXT("NOTEPAD")是常量字符串,当CreateProcess试图修改字符串会引起访问违规。解决方法是将TEXT("NOTEPAD")放在一个缓冲区内:
- STARTUPINFO si={sizeof(si)};
- PROCESS_INFORMATION pi;
- TCHAR cmdLine[200]=TEXT("NOTEPAD");
- CreateProcess(NULL,cmdLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
这一点要特别注意,很容易出错!!!!这也有个例外,windows vista以及win7的ANSI版本以上是不会发生访问违规的,因为它们会为命令行创建一个临时副本。
在解析命令行时,CreateProcess会检查字符串中第一个标记,假定此标记就是我们想运行的可执行文件名称。如果可执行文件没有扩展名,就会默认是.exe扩展名。CreateProcess会在以下目录下搜索可执行文件:
1:主调进程.exe文件所在目录。
2:主调进程的当前目录。
3:windows系统目录。即System32目录。
4:windows目录。
5:PATH环境变量中列出的目录。
如果为可执行文件制定完整路径,系统就会按指定路径寻找。
以上情况在pszApplicationName为NULL时才发生。当然也可以在pszApplicationName传递可执行文件名称,此时必须指定扩展名,否则进程不会被创建。系统会按照此处指定的路径寻找,如没有指定完整路径,系统会假定文件位于当前目录。如找不到,函数调用失败。
当pszApplicationName指定文件名,pszCommandLine参数中的内容也会作为新进程的命令行传给它。
如:
- STARTUPINFO si={sizeof(si)};
- PROCESS_INFORMATION pi;
- TCHAR cmdLine[200]=TEXT("WORDPAD a.txt");
- CreateProcess(TEXT("C:\\windows\\system32\\NOTEPAD.exe"),
- cmdLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
命令行是“WORDPAD a.txt”,记事本程序将询问:a.txt不存在是否创建a.txt文件。第一个参数WORDPAD应该是作为程序名称传入的。
为了创建一个新的进程,系统必须创建一个进程内核对象和一个线程内核对象,由于这些都是内核对象,所以父进程必须将安全属性关联到这两个对象上。可以通过psaProcess和psaThread为进程和线程安全对象指定默认的安全描述符。可以都为它们指定NULL,使用默认的安全属性,也可以分配并初始化两个SECURITY_ATTRIBUTES结构,以便创建安全权限并将它们分配给进程和线程对象。
fdwCreate参数影响新进程创建的方式。如:指定CREATE_SUSPENDEd标识让系统在创建新进程时挂起其主线程。这样父进程就可以修改子进程地址空间的内存、更改主线程优先级或是将其添加到作业中。修改完后可以调用ResumeThread来允许子进程执行代码。传入0 表示创建进程后立即运行。多个标志位可以组合使用。
pvEnvironment参数指定一块内存,包含新进程要使用的环境字符串。但多数情况下都是传入NULL,表示子进程要继承父进程使用的一组环境字符串。也可以使用GetEnvironmentStrings函数,此函数获取主调进程正在使用的环境字符串地址,当我们为pvEnvirtonment传入NULL时,CreateProcess就是调用这个函数。不使用这块内存时应该使用FreeEnvironmentStrings函数。
pszCurDir允许父进程设置子进程的当前驱动器和目录。如果为NULL,新进程的工作目录与父进程的一样。
psiStartInfo参数指向一个STARTUPINFO结构。该结构包含很多成员。Windows在创建新进程的时候使用它们,但是大多数应用程序仅仅使用它们的默认值。因此我们要做的最起码的工作就是将此结构的所有成员都初始化为0,将cb成员设为此结构的大小,如:
STARTUPINFO si={sizeof(si)};
此时除cb成员外,其他成员均为0。不能仅仅si.cb=sizeof(si);因为此时其他成员的值都是垃圾数据。
ppiProcInfo参数指向PROCESS_INFORMATION结构,CreateProcess在返回时会初始化这个结构。
Typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess;
HANDLE hThread;
HANDLE dwProcessId;
HANDLE dwThreadId;
}PROCESS_INFORMATION;
CreateProcess创建的进程和线程对象将通过它返回。创建时系统会为每个对象指定一个初始的使用计数1,CreateProcess返回时由于PROCESS_INFORMATION结构中再次引用了进程和线程内核对象 此时它们的使用计数都变为2。可以理解为进程和线程实例本身也占有一个计数。当它们结束运行时这个使用计数被递减1。
此时如果系统要释放进程对象,1:进程必须终止,此时使用计数递减1。2:父进程必须调用CloseHandle,使用计数再次减去1。线程类似。
因此为了使不再使用的内核对象能够得到释放,一定要在不使用时调用CloseHandle关闭对句柄的引用。所有对该句柄的引用都被关闭后,当进程或线程终止时它们关联度的内核对象才能够被释放。
CreateProcess还会为进程和线程分配一个ID号。进程和线程分享同一个号码池。这意味着它们不可能相同。一个对象的ID不可能分配到0,因为windows任务管理器将进程ID 0与系统空闲进程关联。该进程代表未被真实使用的cpu使用率。
dwProcessId和dwThreadId成员就是存储进程和线程的ID。使用GetCurrentProcessId可以获得当前进程的ID。GetCurrentThreadId来获得当前正在运行的线程的ID。另外使用GetProcessId和GetThreadId可以获得指定句柄对应的进程和线程的ID。使用GetProcessIDOfThread可以获得某句柄关联的线程所在进程的ID。
由于ID可能会立即重用。也就是说当我们获得某个进程的ID并保存后,此后在使用时有可能出现它已经被释放了。此时此ID就对应着其他进程了。避免这种情况的唯一方法就是:保证进程或线程对象不被销毁。
进程终止
进程可以通过三种方式终止:
1:主线程从入口函数返回。
2:进程中的一个线程调用ExitProcess。
3:另一个进程中的线程调用TerminateProcess。
在以上介绍的三种方式中仅有第一种,当主线程从入口函数返回才保证主线程的所有资源都会被正确清理。
清理操作包括:
1:调用所有在本进程内使用的任何C++对象的析构函数。
2:释放各个线程线程栈使用的内存。
3:进程的退出代码被设为入口函数的返回值。
4:进程内核对象使用计数递减1。
正常情况下入口点函数会返回到启动函数,启动函数将正确清理进程使用的所有C运行时资源,清理之后启动代码显式调用ExitProcess并将入口函数返回值传给它。这也是为什么只需从入口函数返回却可以终止整个进程的原因。
进程的一个线程调用ExitProcess可以终止本进程。其后的别的代码将不会被执行。
与ExitProcess相类似的还有ExitThread,它会导致一个线程终止。在创建线程时常出现这种情况:子线程还没有怎么执行程序就已经结束了,这有可能是在创建完线程后,主线程没有调用WaitForSingleObject之类的函数,主线程创建完其他线程后就返回到启动函数函数返回整个进程被终止。这一点很容易出错。
调用ExitProcess或是ExitThread会导致进程或线程当场终止运行,再也不会返回到启动函数,清理工作(C++对象的析构)当然没法执行。虽然最终随着进程的结束,该进程内所有线程所使用的资源都会被释放,但是应该避免调用这些函数,它们阻止了C++对象析构函数对善后工作的处理。顺便提下,如果在主线程调用ExitThread,虽然主线程当场终止,但是如果进程内还有其他线程,则进程不会终止。
如以下代码:
- #include<windows.h>
- #include<iostream>
- DWORD ThreadProc(PVOID)
- {
- int i=0;
- int j=0;
- while(i<1000000)
- {
- i++;
- while(j<10000)
- j++;
- std::cout<<i<<","<<j<<std::endl;
- }
- return 0;
- }
- int main(int argc,char**argv)
- {
- DWORD id;
- CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,NULL,0,&id);
- ExitThread(0);
- return 0;
- }
ExitProcess,ExitThread只能由本进程的其他线程调用。而TerminateProcess和TerminateThread却可以由任何其他进程的线程调用。它的第一个参数指定要终止进程的句柄。这种情况下应用程序得不到自己要被杀死的通知,也不能阻止自己被杀死,当然也无法为自己准备好后事(得不到清理)。例如已经修改的文件没有刷新到磁盘上。
但要明白进程终止后属于它的任何东西都会被释放。TerminateProcess是异步的,此函数调用后我们并不能保证进程已经被强行终止了。要确定进程是否终止可以调用WaitForSingleObject函数,并传入进程句柄。
进程终止时进行的操作。
一个进程终止时,系统会依次执行以下操作:
1:终止进程中遗留的任何线程。
2:释放进程分配的所有用户对象,关闭所有内核对象。如果它们的使用计数变为0,内核对象将会释放。
3:将进程的退出代码从STILL_ACTIVE变为传给ExitProcess或是TerminateProcess的参数存储在内核对象中。
4:进程内核对象变为一触发状态。这也是为什么其他线程可以挂起他们自己直至另一个进程终止运行。
5:进程内核对象的使用计数递减1。
进程内核对象的生命期至少能像进程本身一样长。当进程终止时如果系统中还有另一个进程打开了这个进程的内核对象的句柄,进程内核对象的使用计数就不会变为0。当父进程忘记关闭子进程的句柄时往往发生这种情况。
进程终止了内核对象还没有被释放,这样做有用吗?当然有用!!即使进程终止了,存储在内核对象的信息也有可能被使用,如我们想知道进程占用了多少Cpu时间,或是想获得它的退出代码。GetExitCodeProcess此函数会查找进程内核对象并从内核对象的数据结构中取出退出代码。任何时候都可以调用此函数。如此时进程正在运行那么将会得到STILL_ALIVE。
WaitForSingleObject将会挂起当前线程,知道它所等待的对象变为已触发状态。进程或线程对象在终止时就会变成已触发状态。
windows核心编程---第四章 进程的更多相关文章
- Windows核心编程 第四章 进程(下)
4.3 终止进程的运行 若要终止进程的运行,可以使用下面四种方法: • 主线程的进入点函数返回(最好使用这个方法) . • 进程中的一个线程调用E x i t P r o c e s s函数(应该避免 ...
- Windows核心编程 第四章 进程(上)
第4章 进 程 本章介绍系统如何管理所有正在运行的应用程序.首先讲述什么是进程,以及系统如何创建进程内核对象,以便管理每个进程.然后将说明如何使用相关的内核对象来对进程进行操作.接着,要介绍进 ...
- Windows核心编程 第四章 进程(中)
4.2 CreateProcess函数 可以用C r e a t e P r o c e s s函数创建一个进程: BOOL CreateProcessW( _In_opt_ LPCWSTR lpAp ...
- windows核心编程 第5章job lab示例程序 解决小技巧
看到windows核心编程 第5章的最后一节,发现job lab例子程序不能在我的系统(win8下)正常运行,总是提示“进程在一个作业里” 用process explorer程序查看 ...
- windows核心编程 第8章201页旋转锁的代码在新版Visual Studio运行问题
// 全局变量,用于指示共享的资源是否在使用 BOOL g_fResourceInUse = FALSE; void Func1() { //等待访问资源 while(InterlockedExcha ...
- [转]Windows Shell 编程 第四章 【来源 http://blog.csdn.net/wangqiulin123456/article/details/7987933】
第四章 文件的本质 以前,所有文件和目录都有一个确定的属性集:时间,日期,尺寸,以及表示‘只读的’,‘隐藏的,‘存档的’,或‘系统的’状态标志.然而,Windos95(及后来的WindowsNT4.0 ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- windows核心编程---第三章 内核对象及句柄本质
本章讨论的是相对抽象的概念,不涉及任何具体的内核对象的细节而是讨论所有内核对象的共有特性. 首先让我们来了解一下什么是内核对象.内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存 ...
- Windows核心编程 第三章 内核对象
第3章内核对象 在介绍Windows API的时候,首先要讲述内核对象以及它们的句柄.本章将要介绍一些比较抽象的概念,在此并不讨论某个特定内核对象的特性,相反只是介绍适用于所有内核对象的特性. 首先介 ...
随机推荐
- 【转】 Tomcat v7.0 Server at localhost was unable to start within 45
转载地址:http://www.jsjtt.com/java/JavaWebkaifa/58.html Starting Tomcat v7.0 Server at localhost' has en ...
- python 学习笔记十二 html基础(进阶篇)
HTML 超级文本标记语言是标准通用标记语言下的一个应用,也是一种规范,一种标准,它通过标记符号来标记要显示的网页中的各个部分.网页文件本身 是一种文本文件,通过在文本文件中添加标记符, 可以告诉浏览 ...
- 9-this
第九课 this 一.this基本概念 this是Javascript语言的一个关键字.在JavaScript中,this是动态绑定,或称为运行期绑定的.在不同的情况下,this指向各不相同.但是有一 ...
- oracle mysql sqlserver数据库中的分页
oracle: select * from (select rownum r,t1.* from tablename t1 where rownum <M+N ) t2 where t2.r&g ...
- java内部类以及匿名类
内部类 一个类内部定义的类称为内部类. 内部类允许把逻辑相关的类组织在一起,并控制内部代码的可视性. 内部类与外部类的结构层次如下. 顶层类:最外层的类 外部类:内部类所在的类 内部类:类内部定义的类 ...
- <a>每次点击都会让浏览器重新打开一个窗口问题
<a> 标签的 target 属性规定在何处打开链接文档.如果在一个 <a> 标签内包含一个 target 属性,浏览器将会载入和显示用这个标签的 href 属性命名的.名称与 ...
- webform简单控件
表单元素: 文本类: text password textarea hidden text,password,textarea实现控件:textbox textmode属性选择password或m ...
- jQuery实现两个按钮的位置互换
页面上有2个按钮A和B.点击按钮A和按钮B互换位置 ,点击按钮B和按钮A互换位置.应该如何实现? html代码如下: <body> <!--页面上有2个按钮A和B. 点击按钮A和按钮 ...
- PHP中的变量详解
php变量通过名只能我们就知道首先变量,是在程序执行期间,可以变化的量. 1.那变量是干嘛的呢,用变量就可以来保存我们值,这就是变量,那么我们接着来看,知道了变量是什么,以及它能干什么,我们再来看一下 ...
- zabbix3.0.4 部署之三 (LNMP > Mysql 安装)
MySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具. 因此,我们首先要在系统中源码编译安装cmake工具. 接下来的安装过程中会遇到错误,我们 ...