内核对象进行线程同步

Posted WhiteLearner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核对象进行线程同步相关的知识,希望对你有一定的参考价值。

前言:

具体的可等待的内核对象有:

  进程,线程,作业,文件以及控制台的标准输入流/输出流/错误流,事件,可等待的计时器,信号量,互斥量。

等待函数:

DWORD WaitForSingleObject(
    HANDLE hObject,//用来标识要等待的内核对象
    DWORD dwMilliseconds);//等待的时间

DWORD WaitForMultipleObjects(
    DWORD dwCount,//函数检查的内核对象的数量(最大为MAXIMUM_WAIT_OBJECTS)
    CONST HANDLE* phObjects,//指向一个内核对象句柄的数组
    BOOL bWaitAll,//是否等待全部对象都触发
    DWORD dwMilliseconds);//等待时间

等待成功引起的副作用:(Windows核心编程)

当等待函数发现对象已经被触发,则称为一个成功的调用,当调用后,对象的状态发生了变化,则称之为等待成功引起的副作用。

 

正文:

1.事件内核对象(Event)

事件包含一个引用计数,一个用来表示事件是自动重置事件还是手动重置事件的布尔值,以及另一个用来表示事件有没有被触发的布尔值。

HANDLE CreateEvent(
    PSECURITY_ATTRIBUTES psa,
    BOOL bManualReset,//TRUE表示手工重置,FALSE表示自动重置
    BOOL bInitialState,//TRUE表示触发状态,FALSE表示未触发状态
    PCTSTR pszName);

HANDLE CreateEventEx(
    PSECURITY_ATTRIBUTES psa,
    PCTSTR pszName,
    DWORD dwFlags,//CREATE_EVENT_INITIAL_SET设置了则初始化为触发状态
            //,CREATE_EVENT_MANUAL_RESET设置了则创建一个手工重置事件
    DWORD dwDesiredAccess);//指定在创建事件时返回的句柄对事件有何种访问权限

HANDLE OpenEvent(
    DWORD dwDesiredAccess,
    BOOL bInherit,
    PCTSTR pszName);

BOOL SetEvent(HANDLE hEvent);//触发事件

BOOL ResetEvent(HANDLE hEvent);//把事件设为未触发状态

BOOL PulseEvent(HANDLE hEvent);//先触发事件后立即恢复到未触发状态

当一个手工重置事件被触发的时候,正在等待该事件的所有线程都将变成可调度状态。当一个自动重置事件被触发的时候,只有一个正在等待该事件的线程会变成可调度状态。

 

2.可等待的计时器内核对象(Waitable Timer)

可以在某个指定的时间触发,或每隔一段时间触发一次。

HANDLE CreateWaitableTimer(
    PSECURITY_ATTRIBUTES psa,
    BOOL bManualReset,
    PCTSTR pszName);

HANDLE OpenWaitableTimer(
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    PCTSTR pszName);

BOOL SetWaitableTimer(
    HANDLE hTimer,
    const LARGE_INTEGER* pDueTime,//表示第一次触发时间在什么时候
    LONG lPeriod,//在第一次触发之后,计时器应该以怎么样的频率触发
    PTIMERAPCROUTINE pfnCompletionRoutine,//APC
    PVOID pvArgToCompletionRoutine,//APC参数
    BOOL bResume);

BOOL CancelWaitableTimer(HANDLE hTimer);//取消计时器

设置可等待计时器时候的pDueTime参数是LARGE_INTEGER*类型的,需要将SYSTEMTIME类型的时间利用SystemTimeToFileTime()转换位FileTime,再将FileTime的成员复制到LARGE_INTEGER结构成员中,然后再将LARGE_INTEGER的地址传给pDueTime。

 

3.信号量内核对象(Semaphore)

包含一个使用计数,还有一个最大资源计数,一个当前资源计数。

信号量的使用规则如下:

1如果当前的资源计数大于0,那么信号量处于触发状态;

2如果当前的资源计数等于0,那么信号量处于未触发状态;

3系统绝对不会让当前资源计数变为负数

4当前资源计数绝对不会大于最大资源计数

HANDLE CreateSemaphore(
    PSECURITY_ATTRIBUTE psa,
    LONG lInitialCount,
    LONG lMaximumCount,
    PCTSTR pszName);

HANDLE CreateSemaphoreEx(
    PSECURITY_ATTRIBUTES psa,
    LONG lInitialCount,
    LONG lMaximumCount,
    PCTSTR pszName,
    DWORD dwFlags,//设为0,系统保留
    DWORD dwDesiredAccess);

BOOL ReleaseSemaphore(
  HANDLE hSemaphore,
  LONG lReleaseCount,
  PLONG plPreviousCount);//递增信号量的当前资源计数

信号量以原子方式来执行测试和设置操作,当我们向信号量请求一个资源的时候,操作系统会检查资源是否可用,并将可用资源的数量递减,整个过程不会被别的线程打断。

 

4.互斥量内核对象(Mutex)

 互斥量对象包含一个使用计数、线程ID以及一个递归计数。线程ID用来标识当前占用这个互斥量的是系统中哪个线程,递归计数表示这个线程占用该互斥量的次数。

互斥量的使用规则:

1如果线程ID为0(无效),那么该互斥量不为任何线程所占用,他处于触发状态。

2如果线程ID为非0值,那么有一个线程已经占用了该互斥量,他处于未触发状态

3与所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许它们违反一些常规的规则(下面说)

HANDLE CreateMutex(
    PSECURITY_ATTRIBUTES pas,
    BOOL bInitialOwner,//控制互斥量的初始状态,如果传FALSE,
              //那么互斥量对象的线程ID和递归计数都将被设为0(触发状态),
              //传TRUE,线程ID为调用线程的线程ID,递归计数将被设为1(未触发状态)
PCTSTR pszName); HANDLE CreateMutexEx( PSECURITY_ATTRIBUTES psa, PCTSTR pszName, DWORD dwFlags, DWORD dwDesiredAccess); HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName); BOOL ReleaseMutex(HANDLE hMutex);//释放互斥量,递归计数减1

使用方法:

为了获得对被保护资源的访问权,线程要调用一个等待函数并传入互斥量的句柄。在内部,等待函数会检查线程ID是否为0.如果为0,那么函数会把线程ID设为调用线程的线程ID,把递归计数设为1,然后让调用线程继续运行。(相当于关键段的EnterCriticalSection())。

如果等待函数检测到线程ID不为0,那么调用线程将进入等待状态。当另一个线程将互斥量的线程ID设为0的时候,系统会记得有一个线程正在等待,于是它把线程ID设为正在等待的那个线程的线程ID,把递归计数设为1,使正在等待的线程变为可调度状态。

上面所说的互斥量的一切都与其他的内核对象一样,但是互斥量有一些自己的特性:

上面说的违反一些常规规则便是:如果等待函数检测到线程ID不为0但是和目前的调用线程的线程ID相等,那么系统会让线程保持可调度状态——即使该互斥量尚未触发,然后递归计数加1,使递归计数大于1的唯一途径是利用这个例外,让线程多次等待同一个互斥量。

当释放互斥量时ReleaseMutex也会检查调用线程的线程ID与互斥量内部的线程ID是否一致,如果一致,递归计数会递减;如果不一致,那么将返回FALSE,调用GetLastError会返回ERROR_NOT_OWNER。

 

更多的等待函数:

 

//等待hProcess标识的进程,直到创建应用程序第一个窗口的线程没有待处理的输入为止。
DWORD WaitForInputIdle(
    HANDLE hProcess,
    DWORD dwMilliseconds);

//不仅内核对象被触发的是时候调用线程会变成可调度状态,而且当窗口信息需要被派送到一个
//由调用线程创建的窗口时,他们也会变成可调度状态。
DWORD MsgWaitForMultipleObjects(
    DWORD dwCount,
    PHANDLE phObjects,
    BOOL bWaitAll,
    DWORD dwMilliseconds,
    DWORD dwWakeMask);

DWORD MsgWaitForMultipleObjectsEx(
    DWORD dwCount,
    PHANDLE phObjects,
    DWORD dwMilliseconds,
    DWORD dwWakeMask,
    DWORD dwFlags);

BOOL WaitForDebugEvent(
    PDEBUG_EVENT pde,
    DWORD dwMilliseconds);

//原子操作来触发一个内核对象并等待另一个内核对象
DWORD SignalObjectAndWait(
    HANDLE hObjectToSignal,//只能是互斥量,事件,信号量内核对象
    HANDLE hObjectToWaitOn,//可等待的内核对象即可
    DWORD dwMilliseconds,
    BOOL bAlertable);

 

以上是关于内核对象进行线程同步的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ _beginthreadex 多线程操作 - 线程同步

第三章--Win32程序的执行单元(部分概念及代码讲解)(中-线程同步

零基础逆向工程37_Win32_10_事件_线程同步

9内核同步介绍

如果线程在内核中实现,可以使用内核信号量对同一个进程中的两个线程进行同步吗?

线程同步-使用ReaderWriterLockSlim类