Windows事件等待学习笔记(四)—— 事件信号量互斥体
Windows事件等待学习笔记(四)—— 事件&信号量&互斥体
- 要点回顾
- 事件
- 实验:验证SignalState
- 第一步:编译并运行以下代码
- 第二步:观察结果
- 第三步:修改代码并执行
- 第四步:观察结果
- 第五步:修改代码并执行
- 第六步:观察结果
- 总结
- 实验二:验证Type
- 第一步:编译并运行以下代码
- 第二步:观察结果
- 第三步:修改代码并执行
- 第四步:观察结果
- 解释说明
- 分析WaitForSingleObject
- 信号量
- 互斥体
- 创建互斥体
- 分析 WaitForSingleObject
- 释放互斥体
- 解决遗弃问题
- ApcDisable
要点回顾
- 线程在进入临界区之前会调用WaitForSingleObject或者WaitForMultipleObjects
- 此时如果有信号,线程会从函数中退出并进入临界区;如果没有信号那么线程将自己挂入等待链表,然后将自己挂入等待网,最后切换线程
- 其它线程在适当的时候,调用方法修改被等待对象的SingleState,设置为有信号(不同的等待对象,会调用不同的函数),并将等待该对象的其它线程从等待链表中摘掉,这样,当前线程便会在WaitForSingleObject或者WaitForMultipleObjects恢复执行(在哪切换就在哪开始执行),如果符合唤醒条件,此时会修改SignalState的值,并将自己从等待网上摘下来,此时的线程才是真正的唤醒
- 被等待对象不同,主要差异在以下两点:
- 不同的被等待对象,修改SingnalState所调用的函数不同
- 当前线程一旦被临时唤醒后,会从原来进入等待状态的地方继续执行,不同的等待对象,判断是否符合激活条件和修改SignalState的具体操作不同
事件
创建事件对象API:CreateEvent(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); //新增一行,设置信号量
第六步:观察结果
总结
- CreateEvent函数的第三个参数决定了事件对象一开始是否有信号
- 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得到了执行
解释说明
- 当CreateEvent第二个参数为TRUE,系统将事件对象的Type设置成0,此时对象为通知类型类型
- 当CreateEvent第二个参数为FALSE,系统将事件对象的Type设置成1,此时对象为事件同步对象
- 当SetEvent函数将信号值(SignalState)设置为1时,如果对象Type为0,唤醒所有等待该状态的线程;如果对象Type为1,从链表头找到第一个并唤醒
分析WaitForSingleObject
总结:
- 当事件对象的Type为0时,WaitForSingleObject函数在处理时并不修改对象的信号量,原来是多少还是多少,因此所有线程都得以执行
- 当事件对象的Type为1时,WaitForSingleObject函数在处理时对象的信号量清零,因此只有一个线程能够得到执行
信号量
描述:
- 在事件中,当一个线程进入临界区时,其它所有事件都无法进入临界区
- 信号量允许多个线程进入临界区
优点:举个例子,在生产者与消费者的问题中,若生产者只有三份,那么开五个消费者线程是没有意义的,信号量的存在正是为了解决这种问题
创建信号量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:
ReleaseSemaphore
NtReleaseSemaphore
KeReleaseSemaphore
- 设置
SignalState = SignalState + N(参数)
- 通过
WaitListHead
找到所有线程,并从等待链表中摘除
- 设置
互斥体
描述:
- 互斥体(MUTANT) 与 事件(EVENT) 和 信号量(SEMAPHORE) 一样,都可以用来进行线程的同步控制
- 但需要注意的是,这几个对象都是内核对象,这就意味着,通过这些对象可以进行跨进程的线程同步控制,比如:A进程中的X线程和B进程中的Y线程都可以控制等待对象Z
极端情况:
- 若B进程的Y线程还没有来得及调用修改SignalState的函数(如SetEvent),那么等待对象Z将被遗弃,这也就意味着A进程的X线程将永远等待下去
- 当遇到这种情况时,事件和信号量可能无法解决,但互斥体可以很好地解决
例:
当构造了一个临界区一,等待的对象是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(即退出最外圈临界区后),说明其他进程可以使用了,将该互斥体从线程链表中移除
解决遗弃问题
描述:
- 当一个进程非正常“死亡时”,系统会调用内核函数
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
- 用户层:Mutant
对应内核函数:NtCreateMutant
ApcDisable=0 - 内核层:Mutex
对应内核函数:NtCreateMutex
ApcDisable=1
注意:若在三环创建互斥体(Mutant),内核APC仍然可以使用;若通过零环创建互斥体(Mutex),那么当前内核APC是被禁止的
Windows事件等待学习笔记(四)—— 事件信号量互斥体相关推荐
- Windows事件等待学习笔记(三)—— WaitForSingleObject函数分析
Windows事件等待学习笔记(三)-- WaitForSingleObject函数分析 要点回顾 WaitForSingleObject NtWaitForSingleObject KeWaitFo ...
- Windows事件等待学习笔记(二)—— 线程等待与唤醒
Windows事件等待学习笔记(二)-- 线程等待与唤醒 要点回顾 等待与唤醒机制 可等待对象 可等待对象的差异 线程与等待对象 一个线程等待一个对象 实验 第一步:编译并运行以下代码 第二步:在Wi ...
- Windows事件等待学习笔记(一)—— 临界区自旋锁
Windows事件等待学习笔记(一)-- 临界区&自旋锁 基础知识 演示代码 案例一 案例二 LOCK 单行代码原子操作 多行代码原子操作 临界区 演示代码 手动实现 自旋锁 分析 KeAcq ...
- windows内核开发学习笔记四十四:注册表存储结构-储巢
上一篇文章学习了注册表的逻辑结构,接下来我这篇文章来学习注册表的存储结构.注册表实际存储是由一组储巢构成,每个储巢包含了一个由键和值构成的层次结构.下面表是windows的各个储巢的注册表路径和文件路 ...
- Windows编程课程学习笔记
一. Windows程序内部运行机制--Windows编程课程学习笔记 二. MFC框架程序分析--Windows编程课程学习笔记 三. 简单绘图--Windows编程课程学习笔记 四. 文本编程-- ...
- 【vn.py学习笔记(三)】vn.py事件引擎 学习笔记
[vn.py学习笔记(三)]vn.py事件引擎 学习笔记 1 时间驱动 2 事件驱动 3 事件引擎工作流程 4 事件引擎结构 4.1 事件队列 4.2 事件处理线程 4.3 事件处理函数字典/通用事件 ...
- Windows x64内核学习笔记(四)—— 9-9-9-9-12分页
Windows x64内核学习笔记(四)-- 9-9-9-9-12分页 前言 9-9-9-9-12分页 实验一:线性地址转物理地址 页表基址 定位基址 PTE to PXE 实验二:通过页表基址定位各 ...
- Windows驱动开发学习笔记(四)—— 3环与0环通信(常规方式)
Windows驱动开发学习笔记(四)-- 3环与0环通信(常规方式) 设备对象 创建设备对象 设置数据交互方式 创建符号链接 IRP与派遣函数 IRP的类型 其它类型的IRP 派遣函数 派遣函数注册位 ...
- Windows保护模式学习笔记(十四)—— 阶段测试
Windows保护模式学习笔记(十四)-- 阶段测试 题目一 解题步骤 题目二 解题步骤 题目一 描述:给定一个线性地址,和长度,读取内容 int ReadMemory(OUT BYTE* buffe ...
最新文章
- 算法---------简化路径(Java版本)
- python软件是免费的吗-python属于软件吗
- 在 Linux 系统中安装Load Generator ,并在windows 调用
- 学习《CSS选择器Level-4》不完全版
- 特殊时期,对数据中心运营有哪些影响?
- 20110612 DiscuzNT代码研究(3)
- 商业周刊:Facebook为何价值100亿美元(转)
- 强化学习2——有模型强化学习MDP(搬砖马尔科夫,贝尔曼等式)
- linux下文件以及目录权限修改(摘抄)
- Android与Libgdx环境配置
- 快速分类–三向和双枢轴
- 为C程序员准备的0x10个最佳问题
- linux包之bash之内置命令ulimit
- 使用SPSS进行商业数据分析
- 【虚幻引擎UE】UE5 fbx文件导入gltf文件在线/本地导入和切换(含骨骼动画)
- mysql 登录失败18456_Sqlserver 2005 登录用户提示“sa'登录失败。错误18456“的解决方案...
- 如何从macOS Catalina向iPhone添加自定义铃声
- python3编译成pyc文件
- Halcon形状模板匹配
- JavaSwing_4.5: JMenuBar(菜单栏)
热门文章
- AI:2020年WAIC世界人工智能大会2020年7月9日9:30-12:00开幕式《李彦宏、Elon Musk、马云等大佬演讲》
- DL之DenseNet:DenseNet算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
- 成功解决TypeError: sequence item 0: expected str instance, bytes found
- laravel5.8笔记一:安装与服务器环境配置
- TCP socket编程记录(C语言)
- dede_arctype|栏目表
- 如何adb shell进入ctia模式
- 黑马程序员--学习while、do-while、for循环、try-catch的用法
- 【烙铁使用规范】—— 烙铁使用、温度测量规范
- 访问备份数据寄存器时,需要打开BKP时钟吗?