Windows事件等待学习笔记(四)—— 事件&信号量&互斥体

  • 要点回顾
  • 事件
    • 实验:验证SignalState
      • 第一步:编译并运行以下代码
      • 第二步:观察结果
      • 第三步:修改代码并执行
      • 第四步:观察结果
      • 第五步:修改代码并执行
      • 第六步:观察结果
      • 总结
    • 实验二:验证Type
      • 第一步:编译并运行以下代码
      • 第二步:观察结果
      • 第三步:修改代码并执行
      • 第四步:观察结果
      • 解释说明
    • 分析WaitForSingleObject
  • 信号量
  • 互斥体
    • 创建互斥体
      • 分析 WaitForSingleObject
    • 释放互斥体
    • 解决遗弃问题
    • ApcDisable

要点回顾

  1. 线程在进入临界区之前会调用WaitForSingleObject或者WaitForMultipleObjects
  2. 此时如果有信号,线程会从函数中退出并进入临界区;如果没有信号那么线程将自己挂入等待链表,然后将自己挂入等待网,最后切换线程
  3. 其它线程在适当的时候,调用方法修改被等待对象的SingleState,设置为有信号(不同的等待对象,会调用不同的函数),并将等待该对象的其它线程从等待链表中摘掉,这样,当前线程便会在WaitForSingleObject或者WaitForMultipleObjects恢复执行(在哪切换就在哪开始执行),如果符合唤醒条件,此时会修改SignalState的值,并将自己从等待网上摘下来,此时的线程才是真正的唤醒
  4. 被等待对象不同,主要差异在以下两点:
    1. 不同的被等待对象,修改SingnalState所调用的函数不同
    2. 当前线程一旦被临时唤醒后,会从原来进入等待状态的地方继续执行,不同的等待对象,判断是否符合激活条件修改SignalState的具体操作不同

事件

创建事件对象APICreateEvent(NULL, TRUE, FALSE, NULL);

_DISPATCHER_HEADER+0x000 Type               //CreateEvent的第二个参数决定了当前事件对象的类型//TRUE:通知类型对象 FALSE:事件同步对象+0x001 Absolute+0x002 Size+0x003 Inserted+0x004 SignalState      //CreateEvent的第三个参数决定了这里是否有值+0x008 WaitListHead

实验:验证SignalState

第一步:编译并运行以下代码

#include <stdio.h>
#include <windows.h>HANDLE g_hEvent;DWORD WINAPI ThreadProc(LPVOID lpParameter)
{//当事件变成已通知时WaitForSingleObject(g_hEvent, INFINITE);printf("Thread执行了!\n");return 0;
}int main()
{//创建事件//默认安全属性 对象类型 初始状态 名字g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//设置为有信号//SetEvent(g_hEvent);//创建线程::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);getchar();return 0;
}

第二步:观察结果

无任何输出,g_hEvent句柄处于无信号状态

第三步:修改代码并执行

CreateEvent(NULL, FALSE, TRUE, NULL);    //第三个参数由FALSE改为TRUE

第四步:观察结果

第五步:修改代码并执行

CreateEvent(NULL, FALSE, FALSE, NULL);   //第三个参数由TRUE改为FALSE
SetEvent(g_hEvent);                     //新增一行,设置信号量

第六步:观察结果

总结

  1. CreateEvent函数的第三个参数决定了事件对象一开始是否有信号
  2. CreateEvent函数第三个参数TRUE时,效果等同于在下一行调用了SetEvent();

实验二:验证Type

第一步:编译并运行以下代码

#include <stdio.h>
#include <windows.h>HANDLE g_hEvent;DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{//当事件变成已通知时WaitForSingleObject(g_hEvent, INFINITE);printf("Thread1执行了!\n");return 0;
}DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{//当事件变成已通知时WaitForSingleObject(g_hEvent, INFINITE);printf("Thread2执行了!\n");return 0;
}DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{//当事件变成已通知时WaitForSingleObject(g_hEvent, INFINITE);printf("Thread3执行了!\n");return 0;
}int main()
{//创建事件//默认安全属性 对象类型 初始状态 名字g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);   HANDLE hThread[3];//创建3个线程hThread[0] = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);hThread[1] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);hThread[2] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);//设置事件为已通知SetEvent(g_hEvent);//等待线程结束 销毁内核对象WaitForMultipleObjects(3, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);CloseHandle(hThread[2]);CloseHandle(g_hEvent);getchar();return 0;
}

第二步:观察结果

三个线程都得到了执行

第三步:修改代码并执行

g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);   //第三个参数由TRUE改为FALSE

第四步:观察结果

只有Thread1得到了执行

解释说明

  1. CreateEvent第二个参数为TRUE,系统将事件对象的Type设置成0,此时对象为通知类型类型
  2. CreateEvent第二个参数为FALSE,系统将事件对象的Type设置成1,此时对象为事件同步对象
  3. 当SetEvent函数将信号值(SignalState)设置为1时,如果对象Type0,唤醒所有等待该状态的线程;如果对象Type1,从链表头找到第一个并唤醒

分析WaitForSingleObject




总结

  1. 当事件对象的Type为0时,WaitForSingleObject函数在处理时并不修改对象的信号量,原来是多少还是多少,因此所有线程都得以执行
  2. 当事件对象的Type为1时,WaitForSingleObject函数在处理时对象的信号量清零,因此只有一个线程能够得到执行

信号量

描述

  1. 事件中,当一个线程进入临界区时,其它所有事件都无法进入临界区
  2. 信号量允许多个线程进入临界区

优点:举个例子,在生产者与消费者的问题中,若生产者只有三份,那么开五个消费者线程是没有意义的,信号量的存在正是为了解决这种问题

创建信号量API

HANDLE CreateSemaphore(      LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,    LONG lInitialCount,     //发放信号量的数量LONG lMaximumCount,       //信号量发放数量的最大值LPCTSTR lpName);

对应内核结构体

kd> dt _KSEMAPHORE
ntdll!_KSEMAPHORE+0x000 Header           : _DISPATCHER_HEADER+0x010 Limit            : Int4B      //IMaximumCount
kd> dt _DISPATCHER_HEADER
ntdll!_DISPATCHER_HEADER+0x000 Type             : UChar        //信号量类型为5+0x001 Absolute         : UChar+0x002 Size             : UChar+0x003 Inserted         : UChar+0x004 SignalState      : Int4B       //lInitialCount+0x008 WaitListHead     : _LIST_ENTRY

释放信号量API

  1. ReleaseSemaphore
  2. NtReleaseSemaphore
  3. KeReleaseSemaphore
    1. 设置SignalState = SignalState + N(参数)
    2. 通过WaitListHead找到所有线程,并从等待链表中摘除

互斥体

描述

  1. 互斥体(MUTANT)事件(EVENT)信号量(SEMAPHORE) 一样,都可以用来进行线程的同步控制
  2. 但需要注意的是,这几个对象都是内核对象,这就意味着,通过这些对象可以进行跨进程的线程同步控制,比如:A进程中的X线程B进程中的Y线程都可以控制等待对象Z

极端情况

  1. 若B进程的Y线程还没有来得及调用修改SignalState的函数(如SetEvent),那么等待对象Z将被遗弃,这也就意味着A进程的X线程将永远等待下去
  2. 当遇到这种情况时,事件和信号量可能无法解决,但互斥体可以很好地解决


当构造了一个临界区一,等待的对象是A,又在临界区内部构造了一个临界区二,等待对象为A、B、C三个,当临界区一执行完自己的功能后,如果等待的对象为事件或者信号量,那么就必须调用相关API将对象设置为有信号,若进入临界区二前未调用相关API,那么临界区二将永远进入等待状态,这种情况称为死锁
当一个对象需要重复进入临界区时,若A对象为互斥体,就不会出现死锁。

结构体

nt!_KMUTANT+0x000 Header           : _DISPATCHER_HEADER+0x010 MutantListEntry  : _LIST_ENTRY+0x018 OwnerThread      : Ptr32 _KTHREAD+0x01c Abandoned        : UChar+0x01d ApcDisable       : UChar

MutantListEntry:拥有互斥体线程 (KTHREAD+0x010 MutantListHead),是个链表头,圈着当前线程所有的互斥体
OwnerThread:正在拥有互斥体的线程
Abandoned:是否已经被放弃不用
ApcDisable:是否禁用内核APC

创建互斥体

函数

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTE SlpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner,     // 初始化互斥对象的所有者
LPCTSTR lpName          // 指向互斥对象名的指针
);

API调用流

CreateMutex -> NtCreateMutant(内核函数) -> KeInitializeMutant(内核函数)

初始化MUTANT结构体

MUTANT.Header.Type=2;
MUTANT.Header.SignalState=bInitialOwner ? 0 : 1;
MUTANT.OwnerThread=bInitialOwner ? 当前线程 : NULL;
MUTANT.Abandoned=0;
MUTANT.ApcDisable=0;bInitialOwner==TRUE  将当前互斥体挂入到当前线程的互斥体链表
(KTHREAD+0x010 MutantListHead)

分析 WaitForSingleObject


释放互斥体

API

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

API执行流

ReleaseMutex -> NtReleaseMutant -> KeReleaseMutant

正常调用时

MUTANT.Header.SignalState++;

如果SignalState=1(即退出最外圈临界区后),说明其他进程可以使用了,将该互斥体从线程链表中移除

解决遗弃问题

描述

  1. 当一个进程非正常“死亡时”,系统会调用内核函数MmUnloadSystemImage处理后事
    内核函数MmUnloadSystemImage会调用KeReleaseMutant(X, Y, Abandon, Z),第三个参数用来判断该互斥体是否被丢弃,正常释放时值为false,有且只有互斥体有这个待遇
if(Abandon == false) //正常调用
{MUTANT.Header.SignalState++;
}
else
{MUTANT.Header.SignalState == 1;MUTANT.OwnerThread == NULL;
}if(MUTANT.Header.SignalState==1) //意外结束MUTANT.OwnerThread == NULL;  从当前线程互斥体链表中将当前互斥体移除

ApcDisable

  1. 用户层Mutant
    对应内核函数:NtCreateMutant
    ApcDisable=0
  2. 内核层Mutex
    对应内核函数:NtCreateMutex
    ApcDisable=1

注意:若在三环创建互斥体(Mutant),内核APC仍然可以使用;若通过零环创建互斥体(Mutex),那么当前内核APC是被禁止的

Windows事件等待学习笔记(四)—— 事件信号量互斥体相关推荐

  1. Windows事件等待学习笔记(三)—— WaitForSingleObject函数分析

    Windows事件等待学习笔记(三)-- WaitForSingleObject函数分析 要点回顾 WaitForSingleObject NtWaitForSingleObject KeWaitFo ...

  2. Windows事件等待学习笔记(二)—— 线程等待与唤醒

    Windows事件等待学习笔记(二)-- 线程等待与唤醒 要点回顾 等待与唤醒机制 可等待对象 可等待对象的差异 线程与等待对象 一个线程等待一个对象 实验 第一步:编译并运行以下代码 第二步:在Wi ...

  3. Windows事件等待学习笔记(一)—— 临界区自旋锁

    Windows事件等待学习笔记(一)-- 临界区&自旋锁 基础知识 演示代码 案例一 案例二 LOCK 单行代码原子操作 多行代码原子操作 临界区 演示代码 手动实现 自旋锁 分析 KeAcq ...

  4. windows内核开发学习笔记四十四:注册表存储结构-储巢

    上一篇文章学习了注册表的逻辑结构,接下来我这篇文章来学习注册表的存储结构.注册表实际存储是由一组储巢构成,每个储巢包含了一个由键和值构成的层次结构.下面表是windows的各个储巢的注册表路径和文件路 ...

  5. Windows编程课程学习笔记

    一. Windows程序内部运行机制--Windows编程课程学习笔记 二. MFC框架程序分析--Windows编程课程学习笔记 三. 简单绘图--Windows编程课程学习笔记 四. 文本编程-- ...

  6. 【vn.py学习笔记(三)】vn.py事件引擎 学习笔记

    [vn.py学习笔记(三)]vn.py事件引擎 学习笔记 1 时间驱动 2 事件驱动 3 事件引擎工作流程 4 事件引擎结构 4.1 事件队列 4.2 事件处理线程 4.3 事件处理函数字典/通用事件 ...

  7. Windows x64内核学习笔记(四)—— 9-9-9-9-12分页

    Windows x64内核学习笔记(四)-- 9-9-9-9-12分页 前言 9-9-9-9-12分页 实验一:线性地址转物理地址 页表基址 定位基址 PTE to PXE 实验二:通过页表基址定位各 ...

  8. Windows驱动开发学习笔记(四)—— 3环与0环通信(常规方式)

    Windows驱动开发学习笔记(四)-- 3环与0环通信(常规方式) 设备对象 创建设备对象 设置数据交互方式 创建符号链接 IRP与派遣函数 IRP的类型 其它类型的IRP 派遣函数 派遣函数注册位 ...

  9. Windows保护模式学习笔记(十四)—— 阶段测试

    Windows保护模式学习笔记(十四)-- 阶段测试 题目一 解题步骤 题目二 解题步骤 题目一 描述:给定一个线性地址,和长度,读取内容 int ReadMemory(OUT BYTE* buffe ...

最新文章

  1. 算法---------简化路径(Java版本)
  2. python软件是免费的吗-python属于软件吗
  3. 在 Linux 系统中安装Load Generator ,并在windows 调用
  4. 学习《CSS选择器Level-4》不完全版
  5. 特殊时期,对数据中心运营有哪些影响?
  6. 20110612 DiscuzNT代码研究(3)
  7. 商业周刊:Facebook为何价值100亿美元(转)
  8. 强化学习2——有模型强化学习MDP(搬砖马尔科夫,贝尔曼等式)
  9. linux下文件以及目录权限修改(摘抄)
  10. Android与Libgdx环境配置
  11. 快速分类–三向和双枢轴
  12. 为C程序员准备的0x10个最佳问题
  13. linux包之bash之内置命令ulimit
  14. 使用SPSS进行商业数据分析
  15. 【虚幻引擎UE】UE5 fbx文件导入gltf文件在线/本地导入和切换(含骨骼动画)
  16. mysql 登录失败18456_Sqlserver 2005 登录用户提示“sa'登录失败。错误18456“的解决方案...
  17. 如何从macOS Catalina向iPhone添加自定义铃声
  18. python3编译成pyc文件
  19. Halcon形状模板匹配
  20. JavaSwing_4.5: JMenuBar(菜单栏)

热门文章

  1. AI:2020年WAIC世界人工智能大会2020年7月9日9:30-12:00开幕式《李彦宏、Elon Musk、马云等大佬演讲》
  2. DL之DenseNet:DenseNet算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  3. 成功解决TypeError: sequence item 0: expected str instance, bytes found
  4. laravel5.8笔记一:安装与服务器环境配置
  5. TCP socket编程记录(C语言)
  6. dede_arctype|栏目表
  7. 如何adb shell进入ctia模式
  8. 黑马程序员--学习while、do-while、for循环、try-catch的用法
  9. 【烙铁使用规范】—— 烙铁使用、温度测量规范
  10. 访问备份数据寄存器时,需要打开BKP时钟吗?