一、摘要:
进程只是线程的容器,存放数据和代码,但不执行代码。
线程才是执行代码的实体。
作业是对一个或多个进程的统一管理,能添加一般无法添加的限制。
二、进程
1.概念
进程只是线程的容器,为线程执行代码提供资源、营造运行环境。
2.进程的构成
- 关键:
一块内存地址空间,用以存放代码和数据;
一个内核对象句柄表,记录使用中的内核对象。
- 更详尽的构成
(来自MSDN http://msdn.microsoft.com/zh-cn/library/ms681917(v=vs.85).aspx):
a virtual address space,
executable code,
open handles to system objects,
a security context,
a unique process identifier,
environment variables,
a priority class,
minimum and maximum working set sizes,
at least one thread of execution.
3.进程的终止
全部线程都结束。即使主线程退出了,如果还有线程存在,该进程仍然不会销毁。
ExitProcess,有可能造成内存泄露,因为C/C++ Runtime Library没有被清空,则全局变量等资源就不会被释放。
TerminateProcess,跟ExitProcess一样是可能造成内存泄露的。另外它是异步的,即只是通知要终止目标进程,返回后并不代表它已结束。
三、作业
1.基本概念
作业是进程的容器,对一个或多个进程附加一定的限制,进行统一管理。
即使作业只包含了一个进程也是有用的,因为这样能做一些普通不能进行的限制
2.主要的API:
API | 功能 |
---|---|
CreateJobObject | 创建作业内核对象 |
OpenJobObject | 根据Handle打开作业内核对象 |
IsProcessInJob | 验证某一个进程是否存在于作业中 |
SetInformationJobObject | 给作业加上各种限制 |
QueryInformationJobObject | 查询作业对象的信息 |
AssignProcessToJobObject | 将进程放入作业 |
TerminateJobObject | 终止作业内所有进程 |
3.用于作业对象的基本用户界面限制的位标志
标志 | 描述 |
---|---|
JOB_OBJECT_UILIMIT_EXITWINDOWS | 用于防止进程通过ExitWindowsEx函数退出、关闭、重新引导或关闭系统电源 |
JOB_OBJECT_UILIMIT_READCLIPBOARD | 防止进程读取剪贴板的内容 |
JOB_OBJECT_UILIMIT_WRITECLIPBOARD | 防止进程删除剪贴板的内容 |
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | 防止进程通过SystemParametersInfor函数来改变系统参数 |
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS | 防止进程通过ChangeDisplaySettings函数来改变显示设置 |
JOB_OBJECT_UILIMIT_GLOBALATOMS | 为作业赋予它自己的基本结构表,使作业中的进程只能访问该作业的表 |
JOB_OBJECT_UILIMIT_DESKTOP | 防止进程使用CreateDesktop或SwitchDesktop函数创建或转换桌面 |
JOB_OBJECT_UILIMIT_HANDLES | 防止作业中的进程使用同一作业外部的进程创建的USER对象(如HWND) |
四、线程
1.基本概念
进程不执行代码的,是线程在进程地址空间内执行代码,并对进程地址空间内的数据做操作。
多个线程共享进程内的地址空间,包括进程的内核对象句柄表,因为句柄表的存在依赖于进程,而不是线程。
2.线程的构成
关键:
一个堆栈、
一些用于保护线程的寄存器、
一个指令寄存器(IP)、
堆栈指针寄存器(SP)更详尽的构成
(来自MSDN http://msdn.microsoft.com/zh-cn/library/ms681917(v=vs.85).aspx):
All threads of a process share its virtual address space and system resources.
In addition, each thread maintains exception handlers, a scheduling priority, thread local storage, a unique thread identifier, and a set of structures the system will use to save the thread context until it is scheduled.
The thread context includes the thread's set of machine registers, the kernel stack, a thread environment block, and a user stack in the address space of the thread's process.
Threads can also have their own security context, which can be used for impersonating clients.
3.线程的启动
- 初始化线程时会把 线程函数的入参(pvParam)、线程函数的指针(pfnStartAddrj) 压栈。
- 每个线程还有一个指令寄存器(IP)和堆栈指针寄存器(SP)。IP初始指向BaseThreadStart函数,它包含在Kernel32.dll中。
它主要是调用线程函数,并把函数返回值传给ExitThread:
VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr,PVOID pvParam)
{
__try
{
ExitThread((pfnStartAddr)(pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation()))
{
ExitProcess(GetExceptionCode());
}
//NOTE: We never get here.
}
- 之所以pfnStartAddr和pvParam压栈,就是因为线程开始运行时,CPU跳到IP指向BaseThreadStart,然后把pfnStartAddr和pvParam出栈,就把它们当做形参传给BaseThreadStart了。
五、其他
1.C/C++ Runtime Library的多线程版本
在C/C++ Runtime Library中,有一些全局变量。它们有可能同时被多个线程访问,使它们的值无法确定。
C/C++ Runtime Library为了适应多线程,出现多线程(MT)版本,改变一些全局变量和函数的特性。
主要思路是为每个线程关联一个数据结构tiddata块,里面都有各全局变量对于这个线程的副本。即每个线程访问的是属于自己的“全局变量”,有属于自己的独立环境。
而相关的函数对这些全局变量的操作也会改为对于tiddata块对于值的操作。
例如:
_beginthreadex就是在调用CreateThread来创建线程的基础上,在线程初始化时创建线程关联的**tiddata块**,并把这些全局变量拷贝到里面。所以_beginthreadex比CreateThread要安全。
_endthreadex则是对应多做了清空关联数据结构的操作。
如果在多线程版本的C/C++ Runtime Library中,用了CreateThread来创建线程,则线程初始化时不会有tiddata块。而当函数要访问tiddata块的时候,开始会访问失败,然后会自动生成一个,并把它与线程关联起来。但在一些情况下调用CreateThread就可能出现错误。
_beginthread比_beginthreadex、以及_endthread比_endthreadex的参数要少,少了对线程安全访问权的控制。
2.伪句柄
用GetCurrentThread和GetCurrentProcess得到句柄是自己句柄的引用,并不会使线程进程的使用计数加1,它们返回的句柄叫伪句柄。
用CloseHandle关闭伪句柄时,会返回FALSE。
3.纤程
UNIX服务器应用程序属于单线程程序(Windows定义),但其内部仿真了多线程工作。为了方便把UNIX服务器应用程序移植到Windows,就推出了纤程。
ConvertThreadToFiber 把线程转换为纤程。
纤程不应该返回,返回会使线程和该线程所有的纤程都撤销。
在单个线程里,每次只能运行一个纤程。可以用SwtichToFiber来切换纤程