8 事件等待

embedded/2025/1/11 6:56:36/

临界区&自旋锁

这两个章节在”多核同步“篇已经学习过了,需要了解的可以自行查看对应章节。

线程等待与唤醒

我们在之前的课程里面了解了如何自己实现临界区以及什么是Windows自旋锁,这两种同步方案在线程无法进入临界区时都会让当前线程进入等待状态。

一种是通过Sleep函数实现的,一种是通过让当前的CPU”空转“实现的,但这两种等待方式都有局限性:

  1. 通过Sleep函数进行等待,并没有办法确定具体等待的时间,有可能出现等待过长或过短的情况;

  2. 通过让CPU”空转“进行等待,只有在等待事件很短的情况下才有意义,否则空转时间过长,对CPU资源来说就是一种浪费,并且自旋锁的方式只在多核的环境下存在

我们发现如上所说的两种方案,都是由于等待条件的不成熟,所产生的局限。

等待与唤醒机制

在Windows中,一个线程可以通过等待一个或者多个可等待对象,从而进入等待状态,另一个线程可以在某些时刻唤醒等待这些对象的其他线程,这就是Windows的等待与唤醒机制

images/download/attachments/2424889/image2023-5-15_15-45-45.png

可等待对象

所谓可等待对象就是结构体,如下是一些结构体,我们可以在Windbg中查看这些结构体。

images/download/attachments/2424889/image2023-5-15_17-21-41.png

在Windbg中查看结构体,我们会发现这些结构体的第一个成员都是_DISPATCHER_HEADER,也就表示第一个成员为_DISPATCHER_HEADER的结构体就是可等待对象。

images/download/attachments/2424889/image2023-5-15_17-23-29.png

除了这个以外,在Windows中还有一些特殊的结构体,也称之为可等待对象,如_FILE_OBJECT,我们可以看见该结构体的第一个成员就不是_DISPATCHER_HEADER,但是在它的0x5C偏移位成员有一个_KEVENT结构体,这个结构体是一个可等待对象,因此_FILE_OBJECT也是一个可等待对象。

images/download/attachments/2424889/image2023-5-15_17-26-32.png

因此,综上所述我们可以知道只要结构体中成员有_DISPATCHER_HEADER,或包含了_DISPATCHER_HEADER结构体的,我们都可以称之为可等待对象

差异

虽然以上所述的两种类型结构体都称之为可等待对象,但两者之间也是有差异的。差异在等待函数调用过程中体现出来。

当我们使用WaitForSingleObject函数时,进入内核函数NtWaitForSingleObject,这个内核函数会通过3环用户提供的句柄找到等待对象的内核地址;然后判断等待对象的第一个成员是否是_DISPATCHER_HEADER,如果是的话则直接使用;如果不是的话,则去等待对象中找到嵌入的_DISPATCHER_HEADER对象。最后再将找到的对象地址作为参数调用KeWaitForSingleObject函数,该函数核心功能会在后续章节中学习。

images/download/attachments/2424889/image2023-5-15_17-36-59.png

等待块

一个线程可以等待一个或多个对象,线程与等待对象建立联系主要通过等待块,我们可以做个实验来看一下。

一个线程等待一个对象

首先我们可以在XP中编译这样一段代码,来看一下一个线程等待一个对象的情况:

#include <windows.h>

#include <stdio.h>

HANDLE hEvent[2];

DWORD WINAPI ThreadProc(LPVOID lpParameter)

{

::WaitForSingleObject(hEvent[0], -1);

printf("ThreadProc函数执行...\n");

return 0;

}

int main(int argc, char* argv[])

{

hEvent[0] = ::CreateEvent(NULL, TRUE, FALSE, NULL);

::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);

getchar();

return 0;

}

运行程序之后,在Windbg中断一下,然后通过如下图中的指令找到当前进程中正在等待的线程地址:

images/download/attachments/2424889/image2023-5-16_16-33-2.png

接着我们以线程结构体的方式代入线程地址查看,找到0x5C偏移位成员,即等待块:

images/download/attachments/2424889/image2023-5-16_16-36-7.png

等待块是一个_KWAIT_BLOCK结构体,它将线程与被等待对象联系到了一起,我们接着来看一下它的成员含义:

nt!_KWAIT_BLOCK

+0x000 WaitListEntry : _LIST_ENTRY // 稍后了解

+0x008 Thread : Ptr32 _KTHREAD // 当前线程地址

+0x00c Object : Ptr32 Void // 等待对象的地址(当前实验中为_KEVENT)

+0x010 NextWaitBlock : Ptr32 _KWAIT_BLOCK // 下一个等待块地址,这是一个单向循环链表,存储的是与当前线程关联的多个等待块结构体地址,如果只有一个等待块则该地址指向当前等待快地址

+0x014 WaitKey : Uint2B // 等待块的索引,当前为第一个等待块,因此该值为0

+0x016 WaitType : Uint2B // 等待类型,若当前只要有一个等待对象符合条件就可以使得线程被唤醒,那么该值就是1;如果你等待多个对象必须全部符合条件才可以使得线程被唤醒,那么该值0

images/download/attachments/2424889/image2023-5-16_16-37-23.png

综上所述,我们可以使用如下图来表示一个线程等待一个对象的情况:

images/download/attachments/2424889/image2023-5-16_16-43-26.png

一个线程等待多个对象

一个线程等待多个对象的情况,我们需要将代码进行修改,如下代码所示,我们添加两个可等待对象,然后将WaitForSingleObject替换为WaitForMultipleObjects,这样就可以使得一个线程等待多个对象。

值得注意的是,WaitForMultipleObjects函数多出了两个参数,分别是第一个参数nCount(即等待对象的数量)第二个参数bWaitAll


http://www.ppmy.cn/embedded/152938.html

相关文章

Pytest安装Allure生成自动化测试报告

Date: 2025.01.09 16:33:01 author: lijianzhan Allure 是一个强大的测试报告框架&#xff0c;能够生成美观且详细的测试报告。它与 pytest 结合使用&#xff0c;可以帮助你更好地展示测试结果、分析测试数据&#xff0c;并提高测试的可读性和可维护性。以下是关于如何在 Pytho…

在 Vue 3 集成 e签宝电子合同签署功能

实现 Vue 3 e签宝电子合同签署功能&#xff0c;需要使用 e签宝提供的实际 SDK 或 API。 e签宝通常提供针对不同平台&#xff08;如 Web、Android、iOS&#xff09;的 SDK&#xff0c;而 Web 端一般通过 WebView 或直接使用嵌入式 iframe 来加载合同签署页面。 下面举个 &…

JavaSE(十五)——认识进程与多线程入门

文章目录 认识进程进程的概念进程的特性进程的状态进程的优先级进程的描述进程的调度 并发与并行认识线程线程的概念Java中线程的表示认识Thread类线程的创建 线程的控制和管理线程启动-start()线程休眠-sleep()线程中断-interrupt()线程插队-join()线程让步-yield() 线程的状态…

DARPA 着眼于新的量子传感技术研究

前言 DARPA 正专注于推进量子传感器的研究&#xff0c;以应对定位、导航和授时&#xff08;PNT&#xff09;以及军事应用中的情报、监视和侦察&#xff08;ISR&#xff09;方面的挑战。最新一项名为“鲁棒量子传感器”&#xff08;RoQS&#xff09;的新计划旨在提高能够探测地…

代码随想录day03

203 链表基础操作 class Solution { public:ListNode* removeElements(ListNode* head, int val) {while (head!NULL&&head->valval){ListNode* temphead;headhead->next;delete temp;}ListNode* curhead;while (cur!NULL&&cur->next!NULL){if(cur-…

Python基于YOLOv8和OpenCV实现车道线和车辆检测

使用YOLOv8&#xff08;You Only Look Once&#xff09;和OpenCV实现车道线和车辆检测&#xff0c;目标是创建一个可以检测道路上的车道并识别车辆的系统&#xff0c;并估计它们与摄像头的距离。该项目结合了计算机视觉技术和深度学习物体检测。 1、系统主要功能 车道检测&am…

Ubuntu 20.04 安装Cuda 12.2版本踩坑记录

Ubuntu 20.04 安装Cuda 12.2版本踩坑记录 文章目录 Ubuntu 20.04 安装Cuda 12.2版本踩坑记录查看Ubuntu版本不成功的方式&#xff1a;使用deb安装卸载现有的 NVIDIA 驱动&#xff1a;安装符合要求的驱动版本&#xff1a; 成功的安装方式&#xff1a;使用runfile安装user账户nvc…

Golang学习笔记_23——error补充

Golang学习笔记_20——error Golang学习笔记_21——Reader Golang学习笔记_22——Reader示例 文章目录 error补充1. 基本错误处理2. 自定义错误3. 错误类型判断3.1 类型断言3.2 类型选择 4. panic && recover 源码 error补充 1. 基本错误处理 在Go中&#xff0c;函数…