Windows线程

简介: Windows线程
Windows线程
线程基础
  • Windows线程是可以执行的代码的实例。
    系统是以线程为单位调度程序,一个程序中可以有多个线程,实现多任务的处理。主线程只能有一个。
  • Windows线程的特点
  1. 线程都具有1个ID
  2. 每个线程都具有自己的内存栈,其余的共享。
  3. 同一进程中的线程使用同一个地址空间。(除了栈空间)
  • 线程的调度
    操作系统将CPU的执行时间划分为时间片,依次根据时间片执行不同的线程。
    线程轮询: 线程A -> 线程B ->线程A …
创建线程
  • 创建线程
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes , // 安全属性,已废弃, NULL
    SIZE_T dwStackSize, // 线程栈的大小  按1M对齐,最少1M
    LPTHREAD_START_ROUTINE lpStartAddress, // 线程处理函数的函数地址,自己定义由系统调用
    LPVOID lpParameter, // 传递给线程处理函数的参数
    DWORD dwCreationFlags, // 线程的创建方式(立即执行&挂起方式)
    // 0 -> 立即启动
    // CREATE_SUSPENDED -> 挂起
    LPDWORD lpThreadId // 创建成功,返回线程的ID
); // 创建成功,返回线程句柄  ( 线程句柄和线程ID都可以代表线程 )
  • 定义线程处理函数
DWORD WINAPI ThreadProc(
  LPVOID lpParameter // 创建线程时,传递给线程的参数
);

Demo

#include <windows.h>
#include <stdio.h>
DWORD CALLBACK TestProc(LPVOID pParam){
   char * pszText = (char *) pParam;
   printf("%s",pszText);
}
int main(){
    DWORD nID = 0;
    char * pszText = "******";
    HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);
  getchar();
  return 0;
}

子线程正常执行->要保证主线程不结束

销毁线程
  • 挂起
DWORD SuspendThread(
  HANDLE hThread // 线程句柄
);
  • 唤醒
DWORD ResumeThread(
  HANDLE hThread  // 线程句柄
);
  • 结束指定线程
BOOL TerminateThread(
  HANDLE hThread, // 线程句柄
    DWORD dwExitCode // 退出码,一般没用
);
  • 结束函数所在的线程
VOID ExitThread(
  DWORD dwExitCode // 退出码,没有实际意义
);
// 自杀 -> 只能干掉调用的线程
线程相关操作
  • 获取当前线程ID
GetCurrentThreadId();
  • 获取当前线程的句柄
GetCurrentThread();
  • 等候单个句柄有信号
VOID WaitForSingleObject(
  HANDLE handle , // 句柄BUFF的地址
    DWORD dwMilliseconds // 最长等候时间 ms
    // INFINITE 一直等待
);
// 有信号时会立即返回
// 没有信号会阻塞

线程句柄是可等候句柄: 有信号和无信号状态

  • 同时等候多个句柄有信号
DWORD WaitForMultipleObject(
  DWORD nCount, // 句柄数量
    CONST HANDLE *lpHandles, // 句柄BUFF的地址
    BOOL bWaitAll, // 等候方式
    // TRUE : 所有句柄都有信号才会返回
    // FALSE: 只要有一个有信号就返回
    DWORD dwMilliseconds // 等候时间  INFINITE
);

可等候句柄

  • 线程信号
  • 当线程处于执行状态时,线程无信号
  • 当线程结束那刻,线程有信号
线程同步
原子锁
  • 当多个线程对同一个数据进行原子操作,会产生结果丢失,比如算术运算
  • 使用原子锁函数
InterlockedIncrement   // ++ 操作符
InterlockedDecrement   // --
InterlockedCompareExchange   // 三目运算符 
InterlockedExchange    // = 赋值
  • 原子锁的实现,直接对数据所在的内存操作,并且任何一个瞬间只能有一个线程访问。
  • 只能对运算符加锁,效率高,但是麻烦
互斥锁
  • 相关问题
    多线程下代码或者资源的共享使用
  • 互斥的使用
  1. 创建互斥
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全数据(NULL)
    BOOL bInitialOwner, // 初始的拥有者 TRUE/FALSE
    // TRUE: 哪个线程创建哪个线程拥有
    // FALSE: 都不拥有
    LPCTSTR lpName // 命名
); // 创建成功返回互斥句柄
// 互斥句柄,也是可等候句柄
  • 在任何时间点上只有一个线程拥有互斥, 独占性和排他性
  • 当任何线程都不拥有互斥,互斥有信号,任何一个线程拥有互斥,互斥就没有信号
  1. 等候互斥
    WaitFor… 函数
    互斥的等候遵循谁先等候谁先获得

也就是加锁

  1. 释放互斥
BOOL ReleaseMutex(
  HANDLE hMutex // 互斥锁句柄
);
  1. 关闭互斥句柄
CloseHandle

demo

#include <windows.h>
#include <stdio.h>
HANDLE hmutex; // 接收互斥句柄 
DWORD CALLBACK TestProc(LPVOID pParam){
   char * pszText = (char *) pParam;
   int i ;
   while(1){
        WaitForSingleObject(hmutex,INFINITE);
      for(i = 0;i<strlen(pszText);i++){
        printf("%c",pszText[i]);
        Sleep(125);
     }
     printf("\n");
     ReleaseMutex(hmutex); 
   } 
}
int main(){
  hmutex = CreateMutex(NULL,FALSE,NULL); 
    DWORD nID1 = 0;
    DWORD nID2 = 0;
    char * pszText1 = "******";
    char * pszText2 = "------";
    HANDLE hThread1 = CreateThread(NULL,0,TestProc,pszText1,0,&nID1);
    HANDLE hThread2 = CreateThread(NULL,0,TestProc,pszText2,0,&nID2);
  getchar();
  CloseHandle(hmutex);
  return 0;
}
事件
  • 相关问题
    线程之间的通知问题
  • 事件的使用
  1. 创建事件
HANDLE CreateEvent(
  LPSEURITY_ATTRIBUTES lpEventAttributes, // 安全属性
    BOOL bManualReset, // 事件重置(复位)方式 有信号 -> 无信号
    // TRUE  手动
    // FALSE 自动 --> 读取信号一次就会自动复位
    // 触发 无信号 ->有信号
    BOOL bInitialState ,// 事件初始状态,TRUE 有信号
    LPCTSTR lpName // 事件命名,可以为空
);// 创建成功返回事件句柄

可等候句柄

事件的有信号无信号可控制

  1. 等候事件
WaitForSingleObject  / WaitForMultipleObjects
// 自动复位方式,会自动复位
  1. 触发事件( 将事件设置为有信号状态)
BOOL SetEvent(
  HANDLE hEvent //handle to event
);
  1. 复位事件( 将事件设置为无信号状态 )
BOOL ResetEvent(
  HANDLE hEvent
);
  1. 关闭事件
CloseHandle

要小心事件的死锁问题

demo

#include <windows.h>
#include <stdio.h>
HANDLE g_hEvent = 0; // 接收事件句柄
DWORD CALLBACK PrintProc(LPVOID pParam){
  while(1){
    WaitForSingleObject(g_hEvent,INFINITE); // 等待信号 
    ResetEvent(g_hEvent); 
    printf("......\n");
  }
}
DWORD CALLBACK CtrlProc(LPVOID pParam){
  while(1){
    Sleep(1000);
    SetEvent(g_hEvent);
  }
} 
int main(){
  g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
  DWORD nID = 0;
  HANDLE hThread[2] = {0};
  hThread[0] = CreateThread(NULL,0,PrintProc,NULL,0,&nID); 
  hThread[1] = CreateThread(NULL,0,CtrlProc,NULL,0,&nID);
  WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
  CloseHandle(g_hEvent);
  return 0; 
}
信号量
  • 相关的问题
    作用类似于事件,解决通知的相关问题。
    提供一个计数器,可以设置次数
  • 信号量的使用
  1. 创建信号量
HANDLE CreateSemaphore(
  LPSECTRITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性
    LONG lInitialCount, // 初始化信号量数量
    // 信号量计数值为0时,没有信号
    // 信号量计数值不为0,有信号
    LONG lMaximumCount, // 信号量最大个数
    LPCTSTR lpName // 命名
);// 创建成功返回信号量句柄

可等候句柄

  1. 等候信号量
WaitFor ...
// 每等候通过一次,信号量的信号减1,直到为0阻塞
  1. 给信号量指定计数值
BOOL ReleaseSemaphore(
  HANDLE hSemaphore, // 信号量句柄
    LONG lReleaseCount ,// 释放数量
    LPLONG lpPreviousCount 
    // 返回的信息,返回释放前原来信号量的数量,可以为NULL
);
  1. 关闭句柄
CloseHandle

demo

#include <windows.h>
#include <stdio.h>
HANDLE g_hSema = 0;// 保存信号量句柄
DWORD CALLBACK TestProc(LPVOID pParam){
  while(1){
    WaitForSingleObject(g_hSema,INFINITE);
    printf("*****\n");
  }
}
 
int main(){
  g_hSema = CreateSemaphore(NULL,3,10,NULL);
  DWORD nID = 0;
  HANDLE hThread = CreateThread(NULL,0,TestProc,NULL,0,&nID);
  getchar();
  ReleaseSemaphore(g_hSema,5,NULL);
  WaitForSingleObject(hThread,INFINITE);
  CloseHandle(g_hSema); 
  return 0;
}
相关文章
|
5月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
55 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调
|
5月前
|
网络协议 安全 API
9.9 Windows驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章`《内核RIP劫持实现DLL注入》`介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过`NtCreateThreadEx`这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,`NtCreateThreadEx`函数最终会调用`ZwCreateThread`,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
86 0
9.9 Windows驱动开发:内核远程线程实现DLL注入
|
5月前
|
监控 安全 API
7.1 Windows驱动开发:内核监控进程与线程回调
在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。
64 0
7.1 Windows驱动开发:内核监控进程与线程回调
|
5月前
|
监控 Windows
4.4 Windows驱动开发:内核监控进程与线程创建
当你需要在Windows操作系统中监控进程的启动和退出时,可以使用`PsSetCreateProcessNotifyRoutineEx`函数来创建一个`MyCreateProcessNotifyEx`回调函数,该回调函数将在每个进程的创建和退出时被调用。PsSetCreateProcessNotifyRoutineEx 用于在系统启动后向内核注册一个回调函数,以监视新进程的创建和退出,
42 0
4.4 Windows驱动开发:内核监控进程与线程创建
|
5月前
|
监控 安全 Windows
4.3 Windows驱动开发:监控进程与线程对象操作
在内核中,可以使用`ObRegisterCallbacks`这个内核回调函数来实现监控进程和线程对象操作。通过注册一个`OB_CALLBACK_REGISTRATION`回调结构体,可以指定所需的回调函数和回调的监控类型。这个回调结构体包含了回调函数和监控的对象类型,还有一个`Altitude`字段,用于指定回调函数的优先级。优先级越高的回调函数会先被调用,如果某个回调函数返回了一个非NULL值,后续的回调函数就不会被调用。当有进程或线程对象创建、删除、复制或重命名时,内核会调用注册的回调函数。回调函数可以访问被监控对象的信息,如句柄、进程ID等,并可以采取相应的操作,如打印日志、记录信息等。
37 0
4.3 Windows驱动开发:监控进程与线程对象操作
|
6月前
|
存储 安全 调度
4.2 Windows驱动开发:内核中进程线程与模块
内核进程线程和模块是操作系统内核中非常重要的概念。它们是操作系统的核心部分,用于管理系统资源和处理系统请求。在驱动安全开发中,理解内核进程线程和模块的概念对于编写安全的内核驱动程序至关重要。内核进程是在操作系统内核中运行的程序。每个进程都有一个唯一的进程标识符(PID),它用于在系统中唯一地标识该进程。在内核中,进程被表示为一个进程控制块(PCB),它包含有关进程的信息,如进程状态、优先级、内存使用情况等。枚举进程可以让我们获取当前系统中所有正在运行的进程的PID和其他有用的信息,以便我们可以监视和管理系统中的进程。
76 0
4.2 Windows驱动开发:内核中进程线程与模块
|
7月前
|
编译器 索引 Windows
[笔记]Windows核心编程《二十一》线程本地存储器TLS
[笔记]Windows核心编程《二十一》线程本地存储器TLS
|
7月前
|
存储 编译器 C++
[笔记]Windows核心编程《十六》线程栈
[笔记]Windows核心编程《十六》线程栈
|
7月前
|
存储 API 调度
[笔记]Windows核心编程《八》用内核对象进行线程同步
[笔记]Windows核心编程《八》用内核对象进行线程同步
|
7月前
|
算法 安全 调度
[笔记]Windows核心编程《六》线程调度、优先级和关联性
[笔记]Windows核心编程《六》线程调度、优先级和关联性