Windows API(三)线程

Windows API笔记(一)内核对象
Windows API笔记(二)进程和进程间通信、进程边界
Windows API笔记(三)线程和线程同步、线程局部存储
Windows API笔记(三)线程同步
Windows API笔记(四)win32内存结构
Windows API笔记(五)虚拟内存
Windows API笔记(六)内存映射文件
Windows API笔记(七)堆
Windows API笔记(八)文件系统
Windows API笔记(九)窗口消息
Windows API笔记(十)动态链接库
Windows API笔记(十一)设备I/O


文章目录

  • 1. 创建线程
  • 2. 线程函数
    • 2.1 线程的栈
    • 2.2 线程的Context结构
    • 2.3 线程的执行时间
  • 3. 终止线程
    • 3.2 进程终结的过程
  • 4. 识别自己的身份
  • 5. 系统如何调度线程
    • 5.1 Windows API赋优先级
      • 5.1.1 进程优先级
      • 5.1.2 线程优先级
      • 5.1.3 线程相对优先级
    • 5.3 线程和进程挂起与恢复
  • 6. 系统内部情况
  • 7. 进程、线程和C运行时库


何时创建?何时不需要创建?

  • 创建更多的线程可以协助完成进程的工作,其主导思想是占用更多的CPU时间
  • 线程调度有时间开销
  • 线程在解决某些问题的时候会产生新问题(例如:线程同步问题)

1. 创建线程

HANDLE CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,	// 安全描述符,与其他内核对象相同意义
    _In_ SIZE_T dwStackSize,							// 栈内存空间大小,0 时使用默认值(1MB)
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,			// 线程函数地址
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,		// 线程函数参数
    _In_ DWORD dwCreationFlags,							// 控制线程的创建;0 立刻开始执行;CREATE_SUSPENDED 创建后挂起,并不立即执行
    _Out_opt_ LPDWORD lpThreadId						// 返回线程id
    );
  1. 分配一个线程内核对象(线程句柄)来标识和管理新创建的线程
  2. 把该线程的退出码初始化为STILL_ACTIVE,线程挂起计数(使用计数)设置为1
  3. 为新线程分配一个CONTEXT结构(CPU时间)
  4. 为线程分配地址空间(2页)
  5. 将CreateThread的lpStartAddr和lpvThread值被放在栈的顶部,传递给StartOfThread
  6. 把线程的CONTEXT结构中的栈指针寄存器指向第5步中放在栈顶的值;把指令指针寄存器指向内部的StartOfThread函数

2. 线程函数

#include 
#include 

DWORD WINAPI MyThreadFunc(LPVOID lpParm)
{
    Sleep(3 * 1000);

    LPDWORD mainThreadId = (LPDWORD)lpParm;
    DWORD tid = GetCurrentThreadId();
    printf("Main Thread Id : %ld , Current Thread Id : %ld\n", *mainThreadId, tid);
    return 0;
}

void CreateThreadFunc()
{
    DWORD mainThreadId = GetCurrentThreadId();

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = false;      // 不可被继承
    sa.lpSecurityDescriptor = NULL; //安全描述符

    DWORD tid;

    HANDLE thnd = CreateThread(&sa, 0, MyThreadFunc, &mainThreadId, 0, &tid);
    printf("create thread id : %d \n", tid);
    CloseHandle(thnd);

    // 注意线程同步问题,如果当前函数先退出,局部mainThreadId变量将会释放,传递到线程中的mainThreadId地址将会失效
    // Sleep(10*1000);
}

int main()
{
    CreateThreadFunc();

    Sleep(10 * 1000);
    system("pause");
}

2.1 线程的栈

  • 每个线程都从所在的进程的4GB地址空间中分配自己的栈
  • 当使用静态或全局变量时,多个线程同时访问这些变量
  • 局部变量和自动变量是创建在线程的栈上的

2.2 线程的Context结构

Context存储了CPU寄存器的状态。

// 使用此函数前需调用SuspendThread挂起线程,否则将不能获取
BOOL GetThreadContext(
    _In_ HANDLE hThread,
    _Inout_ LPCONTEXT lpContext
    );
//
// Context Frame
//
//  This frame has a several purposes: 1) it is used as an argument to
//  NtContinue, 2) it is used to constuct a call frame for APC delivery,
//  and 3) it is used in the user level thread creation routines.
//
//
// The flags field within this record controls the contents of a CONTEXT
// record.
//
// If the context record is used as an input parameter, then for each
// portion of the context record controlled by a flag whose value is
// set, it is assumed that that portion of the context record contains
// valid context. If the context record is being used to modify a threads
// context, then only that portion of the threads context is modified.
//
// If the context record is used as an output parameter to capture the
// context of a thread, then only those portions of the thread's context
// corresponding to set flags will be returned.
//
// CONTEXT_CONTROL specifies SegSs, Rsp, SegCs, Rip, and EFlags.
//
// CONTEXT_INTEGER specifies Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8-R15.
//
// CONTEXT_SEGMENTS specifies SegDs, SegEs, SegFs, and SegGs.
//
// CONTEXT_FLOATING_POINT specifies Xmm0-Xmm15.
//
// CONTEXT_DEBUG_REGISTERS specifies Dr0-Dr3 and Dr6-Dr7.
//

typedef struct DECLSPEC_ALIGN(16) _CONTEXT {

    //
    // Register parameter home addresses.
    //
    // N.B. These fields are for convience - they could be used to extend the
    //      context record in the future.
    //

    DWORD64 P1Home;
    DWORD64 P2Home;
    DWORD64 P3Home;
    DWORD64 P4Home;
    DWORD64 P5Home;
    DWORD64 P6Home;

    //
    // Control flags.
    //

    DWORD ContextFlags;
    DWORD MxCsr;

    //
    // Segment Registers and processor flags.
    //

    WORD   SegCs;
    WORD   SegDs;
    WORD   SegEs;
    WORD   SegFs;
    WORD   SegGs;
    WORD   SegSs;
    DWORD EFlags;

    //
    // Debug registers
    //

    DWORD64 Dr0;
    DWORD64 Dr1;
    DWORD64 Dr2;
    DWORD64 Dr3;
    DWORD64 Dr6;
    DWORD64 Dr7;

    //
    // Integer registers.
    //

    DWORD64 Rax;
    DWORD64 Rcx;
    DWORD64 Rdx;
    DWORD64 Rbx;
    DWORD64 Rsp;
    DWORD64 Rbp;
    DWORD64 Rsi;
    DWORD64 Rdi;
    DWORD64 R8;
    DWORD64 R9;
    DWORD64 R10;
    DWORD64 R11;
    DWORD64 R12;
    DWORD64 R13;
    DWORD64 R14;
    DWORD64 R15;

    //
    // Program counter.
    //

    DWORD64 Rip;

    //
    // Floating point state.
    //

    union {
        XMM_SAVE_AREA32 FltSave;
        struct {
            M128A Header[2];
            M128A Legacy[8];
            M128A Xmm0;
            M128A Xmm1;
            M128A Xmm2;
            M128A Xmm3;
            M128A Xmm4;
            M128A Xmm5;
            M128A Xmm6;
            M128A Xmm7;
            M128A Xmm8;
            M128A Xmm9;
            M128A Xmm10;
            M128A Xmm11;
            M128A Xmm12;
            M128A Xmm13;
            M128A Xmm14;
            M128A Xmm15;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;

    //
    // Vector registers.
    //

    M128A VectorRegister[26];
    DWORD64 VectorControl;

    //
    // Special debug control registers.
    //

    DWORD64 DebugControl;
    DWORD64 LastBranchToRip;
    DWORD64 LastBranchFromRip;
    DWORD64 LastExceptionToRip;
    DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;

2.3 线程的执行时间

BOOL GetThreadTimes(
    _In_ HANDLE hThread,				// 线程句柄
    _Out_ LPFILETIME lpCreationTime,	// 线程的创建时间
    _Out_ LPFILETIME lpExitTime,		// 线程的退出时间,还在运行时无意义
    _Out_ LPFILETIME lpKernelTime,		// 线程用于执行操作系统代码的时间
    _Out_ LPFILETIME lpUserTime			// 线程用于执行应用程序代码的时间
    );

可以用此函数判断一个复杂算法所必须的时间。

相应的还有进程的执行时间:

BOOL GetProcessTimes(
    _In_ HANDLE hProcess,							// 进程句柄
    _Out_ LPFILETIME lpCreationTime,			// 进程创建时间
    _Out_ LPFILETIME lpExitTime,					// 进程的退出时间
    _Out_ LPFILETIME lpKernelTime,				// 进程运行系统代码的时间
    _Out_ LPFILETIME lpUserTime					// 进程运行用户代码的时间
    );

3. 终止线程

有三种方法来终止:

  1. 调用ExitThread
  2. 调用TerminateThread(还可以杀死其他进程中的线程)
  3. 包含该线程的进程终结了(进程终结了,进程内所有的线程都会终结)

3.2 进程终结的过程

  1. 线程拥有的所有用户对象句柄被释放;在Win32中,线程创建的大部分对象是归所属的进程所有,不过线程可以拥有两种用户对象:窗口和钩子(Hook);其他对象只有在进程终结时才被释放
  2. 线程内核对象的状态变为有信号态(WaitForSingleObject/WaitForMultipleObjects将有信号)
  3. 线程的退出码由STILL、ACTIVE变为由ExitThread或TerminateThread传递的值
  4. 如果该线程是进程中最后一个活动线程,进程也终止了
  5. 线程内核对象的使用计数-1

4. 识别自己的身份

/*
获取当前进程的伪句柄,不创建新句柄,也不增加使用计数
返回值传入CloseHandle不做任何操作就返回了
但是可以把伪句柄传递给需要进程句柄的函数调用
*/
HANDLE GetCurrentProcess(VOID)
/*获取当前线程的伪句柄*/
HANDLE GetCurrentThread(VOID)
/*使用伪句柄转换成一个真句柄*/
HANDLE hProcess;
DuplicateHandle(
	GetCurrentProcess(),
	GetCurrentProcess(),
	GetCurrentProcess(),
	&hProcess,
	0,
	FALSE,
	DUPLICATE_SAME_ACCESS);

5. 系统如何调度线程

  • 线程优先级(最低0 - 最高31)
  • 优先级0被赋给了系统中一个叫零页(Zero Page)的特别线程,其他线程不可能有0优先级
  • CPU平等对待同一优先级上的所有线程(如果总是有优先级为31的线程,那么其他优先级低于31的线程就没有机会被分配CPU,就永远不会执行,这种情况被称为饥饿starvation,当一些线程占用了所有的CPU时间,使其他线程永远不能执行时发生饥饿)
  • 线程挂起则不会得到CPU
  • 系统发现一个更高级别的线程准备运行,就会立即挂起低级别的线程(即使它正在一个时间片的中间),把一个完整的CPU分配给高级别的线程,不管低级别的线程正在干什么,高级别的线程总会抢先低级别的线程

5.1 Windows API赋优先级

5.1.1 进程优先级

CreateProcess标志 级别
空闲 IDLE_PRIORITY_CLASS 4
普通(Normal) NORMAL_PRIORITY_CLASS 8
高(High) HIGH_PRIORITY_CLASS 13
实时(Realtime) REALTIME_PRIORITY_CLASS 24

将优先级标志在创建进程CreateProcess时传递给fdwCreate标志。默认普通优先级。

如果不想程序干扰其他程序的运行,可将程序优先级设为空闲,只有在其他程序空闲时才可被执行。屏幕保护程序是一个很好的例子,当用户空闲了某一特定时间段后,屏幕保护程序就激活自己。

/*改变进程优先级*/
BOOL SetPriorityClass(
    _In_ HANDLE hProcess,		// 线程句柄
    _In_ DWORD dwPriorityClass	// 优先级
    );

进程转前台时,将改变时间片大小。

5.1.2 线程优先级

当线程创建时,优先级是所在进程的优先级。在进程内可以通过调用SetThreadPriority函数改变一个线程的相对优先级

// 设置线程相对优先级
BOOL SetThreadPriority(
    _In_ HANDLE hThread,		//线程句柄
    _In_ int nPriority			//相对优先级
    );
    
// 查询线程的相对优先级
int GetThreadPriroty(HANDLE hThread);

5.1.3 线程相对优先级

标识符 含义
THREAD_PRIORITY_LOWEST 所属进程优先级-2
THREAD_PRIORITY_BELOW_NORMAL 所属进程优先级-1
THREAD_PRIORITY_NORMAL 所属进程优先级(默认)
THREAD_PRIORITY_ABOVE_NORMAL 所属进程优先级+1
THREAD_PRIORITY_HIGHEST 所属进程优先级+2
THREAD_PRIORITY_IDLE 设线程优先级为1,进程优先级为实时的话设为16 (进程优先级 - 15)
THREAD_PRIORITY_TIME_CRITICAL 设线程优先级为15,进程优先级为实时的话设为31
线程相对优先级 IDLE_PRIORITY_CLASS(空闲) NORMAL_PRIORITY_CLASS(普通) HIGH_PRIORITY_CLASS(高) REALTIME_PRIORITY_CLASS(实时)
THREAD_PRIORITY_IDLE(空闲) 1 1 1 16
THREAD_PRIORITY_LOWEST(最低) 2 6 11 22
THREAD_PRIORITY_BELOW_NORMAL(低于普通) 3 7 12 23
THREAD_PRIORITY_NORMAL(普通) 4 8 13 24
THREAD_PRIORITY_ABOVE_NORMAL(高于普通) 5 9 14 25
THREAD_PRIORITY_HIGHEST(最高) 6 10 15 26
THREAD_PRIORITY_TIME_CRITICAL(时间关键) 15 15 15 31

5.3 线程和进程挂起与恢复

挂起计数

6. 系统内部情况

查看进程内的线程

7. 进程、线程和C运行时库

Microsoft在VC++中附带了6中C运行时库:

库名 描述
LIBC.LIB 静态链接库的发行版本,用于单个线程的应用
LIBCD.LIB 静态链接库的调试版本,用于单个线程的应用
LIBCMT.LIB 静态链接库的发行版本,用于多线程的应用
LIBCMTD.LIB 静态链接库的调试版本,用于多线程的应用
MSVCRT.LIB 动态链接库MSVCRT.DLL的发行版本的引入库,用于单线程和多线程的应用
MSVCRTD.LIB 动态链接库MSVCRTD.DLL的调试版本的引入库,用于多线程和单线程的应用

创建线程时,应避免使用 _beginthread和_endthread,而应该使用_beginthreadex和_endthreadex。
_beginthread和_endthread设计过时,没有线程优先级的设置。

你可能感兴趣的:(C/C++)