Win32程序(五)

来源:互联网 发布:在淘宝怎么开店 编辑:程序博客网 时间:2024/05/20 22:37

进程与线程(process and thread)

我们习惯以进程(process)表示一个执行中的程序,并且认为它是CPU调度单位,事实上线程才是调度单位。

核心对象

首先让我解释什么叫做”核心对象“(kernel object)。你可以说核心对象是系统的一种资源,系统对象一旦产生,任何应用程序都可以开启并使用该对象。系统给予核心对象一个计数值(usage count)作为管理之用。核心对象包括下列几种:

核心对象产生方法eventCreateEventmutexCreateMutexsemaphoreCreateSemaphorefileCreateFilefile-mappingCreateFileMappingprocessCreateProcessthreadCreateThread前三者用于线程的同步化:file-mapping对象用于内存映射文件(memory mapping file),process和thread对象则是本节的主角。这些核心对象的产生方式(也就是我们所使用的API)不同,但都会获得一个handle作为识别;每被使用一次,其对应的计数值就加1,核心对象的结束方式相当一致,调用CloseHandle即可。

”process对象"只是一个数据结构,系统用它来管理进程。

一个进程的诞生与死亡

执行一个程序,必然产生一个进程(process)。最直接的程序执行方式就是在shell中以鼠标双击某一个可执行文件图标,执行起来的APP进程其实是shell(如Windows9x的资源管理器)调用CreateProcess激活的。让我们看看整个流程:

  1. shell调用CreateProcess激活App.exe。
  2. 系统产生一个”进程核心对象“,计数值为1。
  3. 系统为此进程建立一个4GB的地址空间。
  4. 加载器为必要的代码加载到上述地址空间中,包括App.exe的程序、数据、以及所需的动态链接数据库(DLLs)。加载器如何知道要加载哪些DLLs呢?它们被记录在可执行文件(PE文件格式)的.idata section中。
  5. 系统为此进程建立一个线程,称为主线程(primary thread)。线程才是CPU时间的分配对象。
  6. 系统调用C runtime函数库的Startup code。
  7. Startup code 调用 App程序的 WinMain函数。
  8. App程序开始运行。
  9. 使用者关闭App主窗口,使WinMain中的消息循环结束掉,于是WinMain结束。
  10. 回到Startup code
  11. 回到系统,系统调用ExitProcess结束进程。
可以说通过这种方式执行起来的所有Windows程序,都是shell的子进程。本来,母进程与子进程之间可以有某种关系存在,但shell在调用CreateProcess时已经把母子之间的脐带关系剪断了,因此它们事实上是独立的个体。

产生子进程

你可以写一个程序,专门用来激活其他的程序。关键就在于你会不会使用CreateProcess。这个API函数有众多参数:

CreateProcess(LPCSTR lpApplicationName,

        LPSTR lpCommandLine,

        LPSECURITY_ATTRIBUTES lpProcessAttributes,

        LPSECURITY_ATTRIBUTES lpThreadAttributes,

        BOOL bInheritHandles,

        DWORD dwCreationFlags,

        LPVOID lpEnvironment,

        LPCSTP lpCurrentDirectory,

        LPSTARTUPINFO lpStartupInfo,

        LPPROCESS_INTORMATION lpProcessInformation

);

第一个参数 lpApplicationName指定可执行文件名,第二个参数lpCommandLine指定欲传给新进程的命令行(command line)参数。如果你指定了lpApplicationName,但没有扩展名,系统并不会主动为你加上.EXE扩展名;如果没有指定完整路径,系统就只在当前工作目录中寻找,但如果你指定lpApplicationName,为NULL的话,系统会以lpCommandLine的第一个”段落“(我的意思其实是术语中所谓的token)作为可执行文件名;如果这个我文件名没有制定的扩展名,就采用默认的”.EXE“扩展名;如果没有指定路径,Windows就依照五个搜寻路径来寻找可执行文件,分别是:

  1. 调用者的可执行文件所在目录
  2. 调用者的当前工作目录
  3. Windows目录
  4. Windows System目录
  5. 环境变量中的path所设定的各目录

建立新进程之前,系统必须做出两个核心对象,也就是“进程对象”和“线程对象”。CreateProcess的第三个参数和第四个参数分别指定这两个核心对象的安全属性。至于第五个参数(TRUE或FALSE)则用来设定这些安全属性是否要被继承。

第六个参数dwCreationFlags可以是许多常数的组合,会影响到进程的建立过程。这些常数中比较常用的是CREATE_SUSPENDED,它使得子进程产生之后,其主线程立刻被暂停执行。

第七个参数lpEnvironment可以指定进程所使用的环境变量区。通常我们会让子进程继承父进程的环境变量,那么这里要指定NULL。

第八个参数lpCurrentDirectory用来设定子进程的工作目录与工作驱动器。如果指定NULl,子进程就会使用父进程的工作目录与工作驱动器。

第九个参数lpStartupInfo是一个指向STARTUPINFO结构的指针。这是一个放大的结构,可以用来设定窗口的标题、位置与大小,详情请看API使用手册。

最后一个参数是一个指向PROCESS_INFORMATION结构的指针:

typedef struct _PROCESS_INFORMATION

{

HANDLE    hProcess;

HANDLE    hThread;

DWORD    dwProcessId;

DWORD    dwThreadId;

} PROCESS_INFORMATION;

当系统为我们产生“进程对象“和”线程对象“时,他会把两个对象的handle填入此结构的相应字段中,应用程序可以从这里获得这些handles。

如果一个进程想结束自己的生命,只要调用:

VOID ExitProcess(UINT fuExitCode);

就可以了。如果一个进程想结束另一个进程的生命,可以使用:

BOOL TerminateProcess(HANDLE hProcess, UINT fuExitcode);

很显然,只要你有某个进程handle,就可以结束他的生命。TerminateProcess并不被建议使用,倒不是因为它权力太大,而是一般结束进程时,系统会通知该进程所开启(所使用)的所有DLLs,但如果你以TerminateProcess结束一个进程,系统不会做这件事,而这恐怕不是你所希望的。

前面我曾说过所谓”“割断脐带”这件事情,这要你把子进程以CloseHandle关闭,就达到了目的。下面就是例子:

PROCESS_INFORMATION ProcInfo;

BOOL fSuccess;

fScuuess=CreateProcess(...,&ProcInfo);

if(fSuccess){

CloseHandle(ProcInfo.hThread);

CloseHandle(ProcInfo.hProcess);

}

一个线程的诞生与死亡

执行程序代码,是线程的工作。当一个线程建立起来后,主线程也产生。所以每一个Windows程序一开始就有了一个线程。我们可以调用CreateThread产生额外的线程,系统会帮我们完成下列事情:

  1. 配置“线程对象”,其handle将成为CreateThread的返回值。
  2. 设定计数值为1.
  3. 配置线程的context。
  4. 保留线程的堆栈。
  5. 将context中的堆栈指针缓存器(SS)和指令指针缓存器(IP)设定妥当。
看看上面的态势,的确可以显示出线程是CPU分配时间的单位。所谓工作切换(context switch)其实就是对线程的context的切换。

程序若欲产生一个新线程,调用CreateThread即可办到:

CreateThread(LPSECURITY_ATTRIBUTES 

        DWORD        dwStackSize,

        LPTHREAD_START_ROUTINE        lpStartAddress,

        LPVOID        lpParameter,

        DWORD        dwCreationFlags,

        LPDWORD        lpThreadId

);

第一个参数表示安全属性的设定以及继承,请参考API手册。Windows 9x忽略这一参数。第二个参数设定堆栈的大小。第三个参数设定“线程函数”名称,而该函数的参数则由这里的第四个参数设定。第五个参数如果是0,表示让线程立刻开始执行,如果是CREATE_SUSPENDED,则是要求线程暂停执行(那么我们必须调用ResumeThread才能令其重新开始)。最后一个参数是一个指向DWORD的指针,系统会把线程的ID放在这里。

线程的结束有两种情况,一种是寿终正寝,一种是未得善终。前者是线程函数正常终结退出,那么线程也就自然而然终结了。这时候系统会调用ExitThread做些善后清理工作(其实线程中也可以自行调用此函数以结束自己)。但是若线程函数是一个无穷循环,则有两种方法终结此线程。一是进程结束,二是别的线程强制以TerminateProcess将它终结掉。

线程优先级(priority)

优先级是线程调度的重要依据。优先级高的线程,永远先获得CPU的青睐。操作系统会是视情况调整各个线程的优先级。例如前台线程的优先级应该调高些,后台线程的优先级应该调低些。

线程的优先级范围从0(最低)到31(最高)。当你产生线程时,并不是直接以数值指定其优先级,而是采用两个步骤。第一个步骤是指定“优先级等级(priority Class)”给线程,第二个步骤是指定“相对优先级”给该进程所拥有的线程。下表是对优先级的描述,

等级代码优先级值idleIDLE_PRIORITY_CLASS4normalNORMAL_PRIORITY_CLASS9(前台)或7(后台)highHIGH_PRIORITY_CLASS13realtimeREALTIME_PRIORITY_CLASS24其中的代码在CreateProcess的dwCreationFlags参数中指定。如果你不指定,系统默认给的是NORMAL_PRIORITY_CLASS,除非父进程是IDLE_PRIORITY_CLASS(那么子进程也会是IDLE_PRIORITY_CLASS)。

  • “idle”等级只有在CPU时间将被浪费掉时(就是之前所说的空闲时间)才执行。该等级最适合于系统监视软件,或屏幕保护软件。
  • “normal”是默认等级。系统可以动态改变优先级,但只限于“normal”等级。当进程变成前台时进程优先级提升为9,当进程变成后台时,优先级降低为7。
  • “high”等级是为了满足立即反应的需要,例如使用者按下Ctrl+ESC时立刻把工作管理器(task manager)带出场。
  • “realtime”等级几乎不会被一般的应用程序使用。就连系统中控制鼠标、键盘、驱动器状态重新扫描、Ctrl+Alt+Del等的线程都比“realtime”的优先级还低。这种等级使用在“如果不在某个时间范围内被执行的话,数据就要遗失”的情况。这个等级一定得在正确评估之下使用,如果你把这样的等级指定给一般的(并不会常常被阻塞的)线程,多任务环境恐怕会瘫痪,因为这个线程有如此高的优先级,其他线程再没有机会被执行。
上诉四种等级,每一个等级又映射到某一个范围的优先级值。在每一个等级之中,你可以使用SetThreadPriority设定精确的优先级,并且可以稍高或稍低于该等级的正常值(范围是两个点数)。你可以把SetThreadPriority想象成一种微调操作。

SetThreadPriority的参数微调幅度THREAD_PRIORITY_LOWEST-2THREAD_PRIORITY_BELOW_NORMAL-1THREAD_PRIORITY_NORMAL不变THREAD_PRIORITY_ABOVE_NORMAL+1THREAD_PRIORITY_HIGHEST+2除了以上五种微调,另外还可以指定两种微调常数:

SetThreadPriority的参数面对任何等级的调整结果面对“realtime”等级的调整结果THREAD_PRIORITY_IDLE116THREAD_PRIORITY_TIME_CRITICAL1531这种情况可以一下表作为总结。

优先级等级idlelowestbelow normalnormalabove normalhighesttime criticalidle12345615normal(后台)15678915normal(前台)1789101115high1111213141515realtime16222324252631

0 0
原创粉丝点击