20170604Windows09_02_进程

来源:互联网 发布:js遍历a标签 编辑:程序博客网 时间:2024/06/07 17:49

CreateProcess:

CreateProcess概述

1:函数非常复杂,它适用于创建进程的API,创建进程,操作系统会帮我们做几件事情:
    1:先创建一个内核对象,这个内核对象并不代表要创建的进程本身,因为进程本身代表的是一块内存空间。创建一个内核对象主要是方便操作系统对进程进行管理。这个内核对象是一个结构体,里面有一个成员称为使用计数。使用计数是windows用来对内核对象进行清理的,创建一个内核对象,使用计数会+1,这个内核对象使用完之后,使用计数会-1,当操作系统发现内核对象里面的使用计数为0的时候,他就会将这个内核对象回收掉。几乎所有的内核对象里面都会有这样一个成员,进程的内核对象里面也有,调用CreateProcess的时候,创建的内核对象,且将里面的使用计数就设置为1。
        平时,鼠标双击,键盘按下的方式来打开进程(程序),实际就相当于CreateProcess了一个进程,只不过用鼠标键盘等方式替代了,但是其机理都是一样的。
        进程内核对象启动完成之后,操作系统就会为新进程创建一个虚拟地址空间(因为windows支持多进程操作,物理内存是有限的,所以使用了虚拟内存技术,我们是不可以直接操作物理内存的,为了安全。),然后将所需的代码和数据加载到虚拟内存里面来。但是光创建一个进程是没有含义的。
    2:CreateProcess里面还会为新进程创建一个线程,这个线程里面也会有一个自己的线程内核对象,线程内核对象里面也会有一个使用计数,也会被设置成1。这样就完成了进程内核对象、进程空间、主线程内核对象的创建。之后,主线程会通过链接器来调用main函数,最终实现程序开始执行起来。
2:CreateProcess有一个返回值,上面四件事情做完之后,他就会返回TRUE,否则会返回FALSE,但是我们创建一个进程并不是仅仅加载这么一点点的东西,还可能加载dll等(他只会管这个执行文件以及必须的dll,程序里面要加载的dll不会管),之后的事情都是被启动这个程序做的事情。
3:如果A进程里面调用CreateProcess创建了B进程,那么这两个进程就产生了一种关系(父子关系),A进程为父进程,B线程为子进程,但是父子关系这个概念是模糊的,因为虚拟地址空间将所有进程封装成了一个一个的小盒子,盒子之间是不能够互相通讯的,父子进程之间是还是不同的进程,两者不能直接通讯。进程之间还有继承关系(以后再讲)。

ApplicationName和CommandLine参数详细讲解

1:pszApplicationName:指定新进程要使用的执行体文件名称:
    执行体文件就是指的exe文件,比如QQ,实际运行的软件中的一个exe执行体文件,在运行过程中并不只是只有执行体文件,还有dll文件等。通过执行体文件来执行操作。这个参数需要传递文件路径(绝对路径:所有路径都是齐全的,相对路径:相对当前目录(exe所在的当前目录,可以理解为所在目录)),如果没找到这个文件,就不会执行。这个参数可以为null,为null的时候,就会执行pszCommandLine这个参数指定的事件,这个不为null,pszCommandLine会作为命令行参数传递进去。
2:pszCommandLine:传递给新进程的命令行字符串:
    经常会用这个参数来传递文件的路径,因为他会按照以下的路径进行搜索:
        所在目录->当前目录->windows系统目录(system32目录)->windows目录->Path环境变量中列出的目录。
    pszCommandLine参数还有一个需要注意的地方,CreateProcess内部会修改这个参数,所以不应该传递一个常量指针。在这个函数返回的时候,他还会将这个参数改回来。可以自己分配一块缓冲区,传递进去。

内核对象继承及设置环境变量:

1:pszProcess:设置进程内核对象安全属性。可设置成NULL
2:psaThread:设置主线程内核对象安全属性。可设置成NULL
3:blnheritHandles:是否被继承。可设置成FALSE
4:fdwCreate:进程创建方式标志。用的较多的是CREATE_NO_WINDOW:标志系统不要为应用程序创建任何控制台窗口。可以使用该标志执行没有用户界面的控制台应用程序。
5:pvEnvironment:指向一个内存快,其中包含了新进程要使用的环境字符串。大多数时候传递NULL,代表子进程继承父进程使用的一组环境变量,另外,还可以使用GetEnvironmentStrings函数。一般传递NULL
6:pszCurDir:参数允许父进程设置子进程的当前驱动器和目录,如果这个参数为NULL,则新进程的工作目录与生成新进程的应用程序一样。
7:psiStartInfo:结构体一定要清零,否则成员间包含主调线程堆栈上的垃圾数据。

psiStartInfo及ProcessInformation

1:StrartInfo是来设置我们新启动进程的结构体,他必须在CreateProcess之前进行初始化,这个结构体在新建之前一定要清零,否则保存的堆栈数据可能使偶尔创建成功,偶尔创建失败。
2:ProcessInformation是作为返回值,CreateProcess的时候会建立进程内核对象和线程内核对象,创建成功后,他将会把进程内核对象和线程内核对象写在这个结构体里面。每个进程都有自己的ID,称为PID,每个线程也有ID,称为TID,TID和PID也是除开句柄之外的标识符,也会写在这个结构体里面。

ExitProcess:

进程终止有四种情况会被终止:

    1:入口函数(main,WinMain)返回的时候。
    2:在本进程中的任意一个线程(主线程或其他线程)执行了ExitProcess函数,这个进程会被强制终止。
    3:其他进程拥有当前进程的权限,它可以调用远程终止函数(TerminateProcess)来终止这个进程。
    4:进程中的所有线程都消亡了,进程会自动关闭。
1:入口函数返回结束进程。
        只有入口函数返回的方式是进程终止的最正确的方式,入口函数返回时,可以确保一下几件事已经完成:
    1:主线程所创建的任何对象都已经被正确销毁。
    2:操作系统会正确的释放线程的堆栈
    3:将进程的退出代码设置成入口函数的返回值
    4:递减内核对象的使用计数,减为0操作系统就会回收内核对象。
上面这些都是C++的机制来做的。
2:任意线程调用ExitProcess结束进程:
    1:ExitProcess(UINT uExitCode);这个函数会将退出代码设置为uExitCode。
    2:这种方式风险非常高,例如,进程中有一个全局对象,这个函数会导致,全局变量没有办法正确释放的。
    3:好处在于可以直接结束进程,操作系统会讲里面的资源全部结束,线程也会结束,但是线程里面的资源也无法被正确释放。操作系统会将进程里面的空间全部释放,但如果内核对象里面的使用计数不为0,操作系统就无法对内核对象进行回收,长久就会导致系统内核所能够使用的内存不足,导致系统崩溃。因此,并不建议这样关闭进程。
3:TerminateProcess()关闭进程:
    1:TerminateProcess(HANDLE hProcess, DWORD uExitCode);,不因该使用该函数结束进程,此函数能够结束其他进程。
    2:这个函数与ExitProcess的区别在于,他会发送一个请求给操作系统,告诉操作系统这个进程要被结束了,操作系统会准备退出这个进程,这个函数是异步的,不会直接退出,存在滞后,但结果是一样的,一个是直接退出本进程,一个是可以退出其他进程。
4:进程中所有的线程都消亡,进程自动退出:
    1:很少碰到这种情况,理论上会有。
    2:同一时间,将进程中的所有线程都执行了ExitThread,所有的进程都没了,进程就会自动消亡。

内核对象泄漏:

    CreateProcess的时候,事实上,他的内核计数2,进程内核对象设置成1,线程内核对象使用计数会被设置成1,CreateProcess的时候,会使用到进程句柄(返回了进程句柄到CreateProcess函数),使用一次,进程内核对象就会被+1,所以,CreateProcess执行完成之后,进程的使用计数实际上已经设置成2了。
    当子进程入口函数返回的时候,进程的使用计数实际上还为1,并设置退出代码,此时,可以在父进程中使用GetExitCodeProcess()函数和hProcess句柄来获取子进程退出的退出代码。
    如果想正常将子进程内核对象关闭,那就需要将使用计数-1,操作系统才可以回收子进程的内核对象。可以在父进程使用CloseHandle将hProcess关闭掉,才可以使子进程内核对象使用计数-1。所以,子进程的句柄如果对父进程用处不大,就应该在CreateProcess之后关闭(CloseHandle)掉。
    事实上CloseHandle并不是真正关闭了这个handle,而是将其适用对象-1,这样子进程就可以在合适的实时机被关闭掉。否则这个内核对象会一直留存在操作系统中,导致内核对象的泄漏。
    内核对象的泄漏比内存泄漏更麻烦,内存泄漏可以通过关闭进程来解决,但内核对象的泄漏,只能通过重启操作系统来解决。