windows下进程与线程剖析

进程与线程的解析

进程:一个正在运行的程序的实例,由两部分组成:

1.一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。 
2.一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。
   进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地 址空间中“同时”执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统 创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。。。如果没有线程要执行进程地 址空间包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。

线程也有两个部分组成:
一个是线程的内核对象,操作系统用它管理线程。系统还用内核对象来存放线程统计信息的地方。 
一个线程栈,线程栈默认大小为1M,用于维护线程执行时所需的所有函数参数和局部变量

内核对象又包括:1.计数器

        2.挂起计数器(初始值为0,每挂起一个进程,该计数器加一,每恢复一个进程,该计数器减一,且它的值只可以是非负整数)

        3.信号

进程从来不执行任何东西,它只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在其进程的地址 空间内执行代码和处理数据。所以,假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同 的数据。此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程。

对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(轮询或轮流)方式,为每个线程都分配时间片(称为“量程”),从而营造出所有线程都在“并发”运行的假象。  

每个线程都有一个上下文,后者保存在线程的内核对象中。这个上下文反映了线程上一次执行时CPU寄存器的状态。大约每隔20ms,Windows都会查看 所有当前存在的线程内核对象。在这些对象中,只有一些被认为是可调度的。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的 值载入CPU寄存器。这一操作被称为上下文切换。线程执行代码,并在进程的地址空间中操作数据。又过了大约20ms,Windows将CPU寄存器存回线 程的上下文,线程不再运行。系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。载入线程上 下文、让线程运行、保存上下文并重复的操作在系统启动的时候就开始,然后这样的操作会不断重复,直至系统关闭。

创建进程是用来占空间的,真正干活的是线程
线程的创建:

用MFC写一个小例子来介绍一下线程的创建:工作线程??

首先我们先让进度条跑一下

1 while(1)
2     {
3         m_process.StepIt();
4         Sleep(100);//为了让进度条更明显,可以睡一会儿,作用是让出时间片
5         //因为cpu是轮换时间片的,代表cpu到该线程时它放弃本次时间片,让cpu先给别人分配任务
6         //注意windows中的sleep的单位是毫秒,而linux中的是秒
7     }

我们会发现在进度条跑的时候窗口是不可以移动的,因为现在进程里干活的只有这一个线程,它在一段时间内只能干一件事,为了让跑进度条的同时也可以移动窗口,这就需要我们再创建一根线程

CreateThread(
  LPSECURITY_ATTRIBUTES lpsa,

DWORD cbStack,

LPTHREAD_START_ROUTINE lpStartAddr,

LPVOID lpvThreadParam,

DWORD fdwCreate,

LPDWORD );

其中参数lpStartAddr 是指定希望新线程执行线程函数的地址。

lpvThreadParam 参数是线程函数的参数。

线程函数可以执行我们希望他执行的任何任务,函数原型类似于:

DWORD WINAPI ThreadFunc(LPVOID pvParam) {

DWORD dwResult;

return (dwResult);

}

调用CreateThread 时,系统会创建一个线程内核对象。这个线程内核对象不是线程本身,而是一个较小的数据结构,操作系统用这个结构来管理线程。

系统将进程地址空间的内存分配给线程堆栈使用。新线程在与负责创建的那个线程相同的进程上下文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所有线程的堆栈。这样一来,同一个进程中的多个进程可以很容易地互相通信。

线程可以通过以下4种方法来终止运行。

1.线程函数返回(这是强烈推荐的)。

2.线程通过调用ExitThread函数“杀死”自己(要避免使用这种方法)。

3.同一个进程或另一个进程中的线程调用TerminateThread函数(要避免使用这种方法)。

4.包含线程的进程终止运行(这种方法避免使用)。

Ps:TerminateThread函数是异步的。也就是说,它告诉系统你想终止线程,但在函数返回时,并不保证线程已经终止了。如果需要确定线程已终止运行,还需要调用WaitForSingleObject或类似的函数,并向其传递线程的句柄。

线程的初始化

《Windows核心编程》学习笔记(11)– 线程的创建 - fly - 天嗎荇箜

1.对CreateThread函数的一个调用导致系统创建一个线程内核对象。该对象最初的使用计数为2。

(除非线程终止,而且从CreateThread返回的句柄关闭, 否则线程内核对象不会被销毁。);

2.暂停计数被设为1;

(因为线程的初始化需要时间,我们当然不希望在线程准备好之前就执行它。)

3.退出代码被设为STILL_ACTIVE (0x103);

(线程终止运行的时候,线程退出代码从STILL_ACTIVE (0x103)变成传给ExitThread 或TerminateThread 的代码);

4.对象被设为nonsignaled(未触发)状态。

5.系统分配内存,供线程堆栈使用。然后系统将两个值写入新线程堆栈的最上端。写入线程堆栈的第一个值是传给

CreateThread函数的pvParam参数的值。紧接在它下方的是传给CreateThread函数的pfnStartAddr值。

  栈:windows中栈的大小是固定的,栈底在高地址,数据入栈从高地址开始放。

6. 每个线程都有其自己的一组CPU寄存器,称为线程的上下文(context)。上下文反映了当线程上一 次执行时,线程的

CPU寄存器的状态。线程的CPU寄存器全部保存在一个CONTEXT结构(在 WinNT.h头文件中定义)。CONTEXT结构

本身保存在线程内核对象中。

7.指令指针和栈指针寄存器是线程上下文中最重要的两个寄存器。当线程的内核对象被初始化的时候,CONTEXT结构的堆栈指针寄存器被设为pfnStartAddr(线程执行函数的地址)在线程堆栈中的地址。而指令指针寄存器被设为RtlUserThreadStart函数(线程真正从这里开始执行)的 地址,此函数是NTDLL.dll模块导出的。

8.线程完全初始化好之后,系统将检查CREATE_SUSPENDED标志是否传给CreateThread函数。如果此标记没有传递,系统将线程的暂停计数递增至0;随后,线程就可以调度给一个处理器去执行。然后,系统在实际的CPU寄存器中加载上一次在线程上下文中保存的值。现在,线程可以在其进程的地址空间中执行代码并处理数据了。

伪句柄的转换

HANDLE GetCurrentProcess();

HANDLE GetCurrentThread();

这两个函数都返回到主调线程的进程或线程内核对象的一个伪句柄(pseudohandle )。它们不会在主调进程的句柄表中新建句柄。而且,调用这两个函数,不会影响进程或线程内核对象的使用计数。如果调用CloseHandle,将一个伪句柄作为参数传入,CloseHandle只是简单地忽略此调 用,并返回FALSE。在这种情况下,GetLastError将返回ERROR_INVALID_HANDLE。

将伪句柄转换为真正的句柄

有时或许需要一个真正的线程句柄,而不是一个伪句柄。所谓“真正的句柄”,指的是能明确、无歧义地标识一个线程的句柄。来仔细分析下面的代码:

DWORD WINAPI ParentThread(PVOID pvParam) {

  HANDLE hThreadParent = GetCurrentThread();

  CreateThread(NULL, 0, ChildThread, (PVOID) hThreadParent, 0, NULL);

  // Function continues...

}

DWORD WINAPI ChildThread(PVOID pvParam) {

  HANDLE hThreadParent = (HANDLE) pvParam;

  FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;

  GetThreadTimes(hThreadParent,

  &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);

  // Function continues...

}

能看出这个代码段的问题吗?其意图是让父线程向子线程传递一个可以标识父线程的句柄。但是,父线程传递的是一个伪句柄,

而不是一个真正的句柄。子线程开始执行时,它把这个伪句柄传给GetThreadTimes函数,这将导致子线程得到的是它自己的CPU

计时数据,而不是父线程的。之所以会发生这种情况,是因为线程的伪句柄是一个指向当前线程的句柄;换言之,

指向的是发出函数调用的那个线程。

为了修正这段代码,必须将伪句柄转换为一个真正的句柄。DuplicateHandle函数可以执行这个转换:

BOOL DuplicateHandle(

HANDLE hSourceProcess,

HANDLE hSource,

HANDLE hTargetProcess,

PHANDLE phTarget,

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwOptions);

正常情况下,利用这个函数,你可以根据与进程A相关的一个内核对象句柄来创建一个新句柄,并让它同进程B相关。但是,我们可以采取一种特殊的方式来使用它,以纠正前面的那个代码段的错误。纠正过后的代码如下:

DWORD WINAPI ParentThread(PVOID pvParam) {

HANDLE hThreadParent;

DuplicateHandle(

GetCurrentProcess(), // Handle of process that thread pseudohandle is relative to

GetCurrentThread(), // 父伪句柄

GetCurrentProcess(), // Handle of process that the new, real, thread handle is relative to

&hThreadParent, // Will receive the new, real, handle identifying the parent thread

0, // Ignored due to DUPLICATE_SAME_ACCESS

FALSE, // New thread handle is not inheritable

DUPLICATE_SAME_ACCESS); // New thread handle has same access as pseudohandle

CreateThread(NULL, 0, ChildThread, (PVOID) hThreadParent, 0, NULL);

// Function continues...

}

DWORD WINAPI ChildThread(PVOID pvParam) {

HANDLE hThreadParent = (HANDLE) pvParam;

FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;

GetThreadTimes(hThreadParent, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);

CloseHandle(hThreadParent);

// Function continues...

}

现在,当父线程执行时,它会把标识父线程的有歧义的伪句柄转换为一个新的、真正的句柄,后者明确、无歧义地标识了父线程。然后,它将这个真正的句柄传给CreateThread。当子线程开始执行时,其pvParam参数就会包含这个真正的线程句柄。在调用任何函数时,只要传入这个句 柄,影响的就将是父线程,而非子线程。 因为DuplicateHandle递增了指定内核对象的使用计数,所以在用完复制的对象句柄后,有必要 把目标句柄传给CloseHandle,以递减对象的使用计数。前面的代码体现了这一点。调用 GetThreadTimes之后,子线程紧接着调用CloseHandle来递减父线程对象的使用计数。在这段代 码中,我假设子线程不会用这个句柄调用其他任何函数。如果还要在调用其他函数时传入父线程的句柄,那么只有在子线程完全不需要此句柄的时候,才能调用CloseHandle。

还要强调一点,DuplicateHandle函数同样可用于把进程的伪句柄转换为真正的进程句柄,如下所示:

HANDLE hProcess;

DuplicateHandle(

GetCurrentProcess(), // Handle of process that the process pseudohandle is relative to

GetCurrentProcess(), // Process‘ pseudohandle

GetCurrentProcess(),

&hProcess,

0,

FALSE,

DUPLICATE_SAME_ACCESS

)

http://blog.csdn.net/hubinbin595959/article/details/47083019

原文地址:https://www.cnblogs.com/curo0119/p/8343598.html

时间: 2025-01-12 11:55:26

windows下进程与线程剖析的相关文章

[笔记]linux下和windows下的 创建线程函数

linux下和windows下的 创建线程函数 1 #ifdef __GNUC__ 2 //Linux 3 #include <pthread.h> 4 #define CreateThreadEx(tid,threadFun,args) pthread_create(tid, 0, threadFun, args) 5 #define CloseHandle(ph) 6 7 int pthread_create( 8 //指向线程标识符的指针. 9 pthread_t *restrict t

Linux 下进程与线程的基本概念

2019-10-01 关键字:进程.线程.信号量.互斥锁 什么是程序? 程序就是存放在磁盘上的指令和数据的有序集合,就是源代码编译产物. 它是静态的. 什么是进程? 进程就是操作系统为执行某个程序所分配的资源的总称.进程是程序的一次执行过程,因此它与程序不同,它是动态的.它的生命周期包括创建.调度.执行和消亡. 进程的内容主要包括以下三个部分: 1.正文段: 2.用户数据段: 3.系统数据段. 其中正文段与用户数据段两部分是从程序当中来的.而系统数据段则是操作系统分配的用来管理这个进程用的. 系

Windows API——进程和线程函数

CancelWaitableTimer 功能:这个函数用于取消一个可以等待下去的计时器操作.计时器保持它当前的状态,而且除非用SetWaitableTimer函数明确启动,否则它不会重新启动 返回值:非零表示成功,零表示失败.会设置GetLastError 注意:适用Windows NT平台 CallNamedPipe 功能:这个函数由一个希望通过管道通信的一个客户进程调用 返回值:非零表示成功,零表示失败.会设置GetLastError ConnectNamedPipe 功能:指示一台服务器等

Python下进程与线程的原理及区别

对于所有的语言都可能会遇到进程和线程的问题,一般情况下线程是由进程产生的,一个进程产生多个线程来按照一定的规则(Python下根据CPU调度算法和全局进程锁)来利用CPU,我们称之为多线程模式:而一个进程在产生的同时,同时会生成一个主线程,如果程序生成多个进程,那么每个进程都会产生一个线程,多个程序按照一定的规则去利用CPU,我们称之为多进程模式.                 Python下多线程与多进程关系图原理如下所示:

Linux下进程与线程的区别及查询方法

在平时工作中,经常会听到应用程序的进程和线程的概念,那么它们两个之间究竟有什么关系或不同呢?一.深入理解进程和线程的区别 1)两者概念 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是指进程内的一个执行单元,也是进程内的可调度实体. 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源,只拥有一点 在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线

操作系统 - unix和windows下进程异同

在UNIX系统中,仅仅有一个系统调用能够用来创建新进程:fork.这个系统调用会创建一个与调用进程相同的副本. 在调用了fork之后.这两个进程(父进程和子进程)拥有相同的存储映像.相同的环境字符串和相同的打开文件. 这就是所有情形.同城,子进程接着运行execve或一个类似的系统调用,以改动其存储映像并运行一个新的程序.比如,当一个用户在shell中键入命令时,shell就创建一个子进程,然后,这个子进程运行sort. 之所以要安排两步建立进程,是为了在fork之后但在execve之前同意该子

python通过wmi获取windows下进程的信息

linux应该有很多方法可以获取进程的cpu和内存信息,但windows貌似之前接触的是psutil,后来查了一些资料发现wmi也能够获取进程的信息,但貌似效率不太高,应该可以做监控等性能要求不太高的情况 下载wmi,这个网上很多方法和途径,我是用easyinstall来安装,这个不详细说明了 直接附上代码: import wmi from win32com.client import GetObject import win32gui,time mywmi = GetObject("winmg

windows和linux进程与线程的理解

对于windows来说,进程和线程的概念都是有着明确定义的,进程的概念对应于一个程序的运行实例(instance),而线程则是程序代码执行的最小单元.也就是说windows对于进程和线程的定义是与经典OS课程中所教授的进程.线程概念相一致的. 提供API,CreateThread()用于建立一个新的线程,传递线程函数的入口地址和调用参数给新建的线程,然后新线程就开始执行了. windows下,一个典型的线程拥有自己的堆栈.寄存器(包括程序计数器PC,用于指向下一条应该执行的指令在内存中的位置),

Linux下查看进程和线程

在linux中查看线程数的三种方法 1.top -H 手册中说:-H : Threads toggle 加上这个选项启动top,top一行显示一个线程.否则,它一行显示一个进程. 2.ps xH 手册中说:H Show threads as if they were processes 这样可以查看所有存在的线程. 3.ps -mp <PID> 手册中说:m Show threads after processes 这样可以查看一个进程起的线程数. 查看进程 1. top 命令 top命令查看