【UEFI基础】UEFI事件介绍

news2024/11/15 8:13:55

简述

在【UEFI基础】System Table和Architecture Protocols介绍Boot Service时提到有一部分与事件相关的接口,它们创建、触发、等待和关闭事件,来完成某些功能,本文将进一步介绍事件。

需要注意,因为Boot Service需要在DXE阶段才能够使用,所以在此之前的PEI等阶段,是没有事件可用的。事实上,即使是在DXE阶段,也不是可以在任何地方使用,在定时器的实现会进一步说明。

介绍事件之前先回顾一下Boot Service中的相关接口:

函数名类型函数描述
CreateEventBoot创建事件。
CreateEventExBoot作用与CreateEvent大致相同,不过使用方式稍有差别。
CloseEventBoot关闭事件并释放相关资源。
SignalEventBoot触发事件。
WaitForEventBoot等待事件被触发,在事件没有被触发的时候会一直等待。
CheckEventBoot检测事件是否处于被触发状态。
SetTimerBoot给事件一个触发时间。

下面介绍一个简单的例子,首先是创建一个事件:

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  EventFunc,
                  &Count,
                  &Event
                  );

这里创建了一个定时事件,依赖的参数是EVT_TIMER | EVT_NOTIFY_SIGNAL,它表示的就是一个定时事件。第二个参数是TPL_CALLBACK,表示的是代码的运行级别,这个不在这里介绍。第三个参数是回调函数,它在每个时间间隔到来的时候被调用,它的函数原型如下:

/**
  Invoke a notification event

  @param[in]  Event                 Event whose notification function is being invoked.
  @param[in]  Context               The pointer to the notification function's context,
                                    which is implementation-dependent.

**/
typedef
VOID
(EFIAPI *EFI_EVENT_NOTIFY)(
  IN  EFI_EVENT                Event,
  IN  VOID                     *Context
  );

第四个参数是回调函数的入参,这个例子中是一个UINTN类型的整型。最后一个参数就是创建的事件:

EFI_EVENT   Event;

它是函数的返回值,并被后面的代码所使用。

然后是给定时事件设置时间属性:

  if (!EFI_ERROR (Status)) {
    Status = gBS->SetTimer (
                    Event,
                    TimerPeriodic,
                    BENI_EVENT_TIMER_INTERVAL
                    );
  }

当创建事件之后,就会使用该事件作为参数,传入SetTimer(),并通过后续的参数来定义该定时事件的类型(TimerPeriodic,表示周期性调用)和时间间隔。

回调函数的实现:

/**
  Notify the callback function when an event is triggered.

  @param[in]  Event                 Indicates the event that invoke this function.
  @param[in]  Context               Indicates the calling context.

  @retval  NA

**/
VOID
EFIAPI
EventFunc (
  IN  EFI_EVENT                     Event,
  IN  VOID                          *Context
  )
{
  UINTN Count = *(UINTN *)Context;

  Print (L"Count: %d\n", Count);
  Count++;
  *(UINTN *)Context = Count;

  if (Count > 3) {
    gBS->CloseEvent (Event);
  }
}

这里就是自增回调函数的入参,并在4次执行之后关闭事件。

全部的代码可以在BeniPkg\App\EventTestApp\EventTestApp.c找到:

/**
  The main entry of the application.

  @retval  0                        The application exited normally.
  @retval  Other                    An error occurred.

**/
INTN
EFIAPI
ShellAppMain (
  IN  UINTN                         Argc,
  IN  CHAR16                        **Argv
  )
{
  EFI_STATUS  Status = EFI_ABORTED;
  EFI_EVENT   Event = NULL;
  UINTN       Count = 0;

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  EventFunc,
                  &Count,
                  &Event
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->SetTimer (
                    Event,
                    TimerPeriodic,
                    BENI_EVENT_TIMER_INTERVAL
                    );
  }

  //
  // Make sure this application is stilling living before event ends.
  //
  gBS->Stall (1000 * 1000 * 6); // 6s

  return 0;
}

这里将代码放在一个Shell应用中,注意最后的gBS->Stall(),它保证了定时事件在被执行的过程中应用不会被退出,因为如果退出的话,回调函数也不存在了,代码执行就会异常。

最终的执行结果:

在这里插入图片描述

事件的分类

前面创建事件的时候有一个参数EVT_TIMER | EVT_NOTIFY_SIGNAL,从这里入手我们可以了解到事件的大致类型:

//
// These types can be ORed together as needed - for example,
// EVT_TIMER might be Ored with EVT_NOTIFY_WAIT or
// EVT_NOTIFY_SIGNAL.
//
#define EVT_TIMER          0x80000000
#define EVT_RUNTIME        0x40000000
#define EVT_NOTIFY_WAIT    0x00000100
#define EVT_NOTIFY_SIGNAL  0x00000200

#define EVT_SIGNAL_EXIT_BOOT_SERVICES      0x00000201
#define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE  0x60000202

不过从上面的宏并不能得到真正容易理解的事件分类,这里简单将事件分为三种类型:

  • 软件触发的事件:这里指的是通过代码来触发的事件,主要对应到EVT_NOTIFY_SIGNALEVT_SIGNAL_EXIT_BOOT_SERVICESEVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE等。
  • 输入事件:这里指的是因为外部的输入(比如键盘)引起的事件,主要对应EVT_NOTIFY_WAIT
  • 定时事件:因时间而触发的事件,主要对应EVT_TIMER

上面代码中还有一个EVT_RUNTIME,它不能分到上述的类型中,只是说在运行时也可以使用。这个部分也不会在这里介绍。本文的剩余部分会分别介绍前述的三种类型事件。

软件触发的事件

该类型事件是指只与软件有关的,且是通过代码触发的,比如说某些代码需要在启动的某个阶段或者需要满足某个条件才能执行。下面介绍一些示例。

在事件的分类中展示的宏里面有如下的两个:

#define EVT_SIGNAL_EXIT_BOOT_SERVICES      0x00000201
#define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE  0x60000202

它们就是前面说到的在启动的某个阶段的事件触发点,分别对应退出BIOS的时候(开始进入Runtime阶段)和虚拟地址变化的时候。因此可以创建如下的事件:

  Status = gBS->CreateEvent (
                  EVT_SIGNAL_EXIT_BOOT_SERVICES,
                  TPL_NOTIFY,
                  WatchdogExitBootServicesEventHandler,
                  NULL,
                  &mEfiExitBootServicesEvent
                  );

该事件将在退出BIOS的时候触发,并且是代码框架自动执行的,不需要自己写代码来调用回调函数。

事件创建还有一个版本CreateEventEx(),它定义了更多这样的事件触发点,这些触发点对应的事件集合被称为事件组(EventGroup),可以在MdePkg\Include\Guid\EventGroup.h中找到定义:

#ifndef __EVENT_GROUP_GUID__
#define __EVENT_GROUP_GUID__

#define EFI_EVENT_GROUP_EXIT_BOOT_SERVICES \
  { 0x27abf055, 0xb1b8, 0x4c26, { 0x80, 0x48, 0x74, 0x8f, 0x37, 0xba, 0xa2, 0xdf } }

extern EFI_GUID  gEfiEventExitBootServicesGuid;

#define EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE \
  { 0x13fa7698, 0xc831, 0x49c7, { 0x87, 0xea, 0x8f, 0x43, 0xfc, 0xc2, 0x51, 0x96 } }

extern EFI_GUID  gEfiEventVirtualAddressChangeGuid;

#define EFI_EVENT_GROUP_MEMORY_MAP_CHANGE \
  { 0x78bee926, 0x692f, 0x48fd, { 0x9e, 0xdb, 0x1, 0x42, 0x2e, 0xf0, 0xd7, 0xab } }

extern EFI_GUID  gEfiEventMemoryMapChangeGuid;

#define EFI_EVENT_GROUP_READY_TO_BOOT \
  { 0x7ce88fb3, 0x4bd7, 0x4679, { 0x87, 0xa8, 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b } }

extern EFI_GUID  gEfiEventReadyToBootGuid;

#define EFI_EVENT_GROUP_DXE_DISPATCH_GUID \
  { 0x7081e22f, 0xcac6, 0x4053, { 0x94, 0x68, 0x67, 0x57, 0x82, 0xcf, 0x88, 0xe5 }}

extern EFI_GUID  gEfiEventDxeDispatchGuid;

#define EFI_END_OF_DXE_EVENT_GROUP_GUID \
  { 0x2ce967a, 0xdd7e, 0x4ffc, { 0x9e, 0xe7, 0x81, 0xc, 0xf0, 0x47, 0x8, 0x80 } }

extern EFI_GUID  gEfiEndOfDxeEventGroupGuid;

#endif

可以看到相比CreateEvent()可以用的事件触发点要扩展了不少。下面是CreateEventEx()使用的一个示例:

  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  ExitBootServicesHandler,
                  NULL,
                  &gEfiEventExitBootServicesGuid,
                  &Event
                  );

CreateEventEx()CreateEvent()函数的入参有一些差别:

  • 第一个参数的可选值不同,不再会用EVT_SIGNAL_EXIT_BOOT_SERVICES这样的入参;
  • 增加了倒数第二个参数,它表示的是事件组的GUID。

关于框架代码中如何触发上述的事件也比较简单,还是以退出BIOS的事件为例,可以在MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c找到相关的实现:

EFI_STATUS
EFIAPI
CoreExitBootServices (
  IN EFI_HANDLE  ImageHandle,
  IN UINTN       MapKey
  )
{
  // 略

  //
  // Notify other drivers that we are exiting boot services.
  //
  CoreNotifySignalList (&gEfiEventExitBootServicesGuid);
  
  // 略
}

CoreExitBootServices()是Boot Service中的一个,而其中就包含了对事件组的触发。关于这里的CoreNotifySignalList()的实现不再进一步介绍,它其实没有直接触发事件的回调函数,只是将它们加入到了另一个全局的队列中以便后续执行,具体的操作可以直接看代码。

当然事件也可以通过手动触发,这需要调用SignalEvent()函数,下面是一个简单的例子:

  EFI_STATUS  Status = EFI_ABORTED;
  EFI_EVENT   Event = NULL;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  EventFunc2,
                  NULL,
                  &Event
                  );

  Print (L"Waiting to signal event ...\n");
  gBS->Stall (1000 * 1000 * 1);
  gBS->SignalEvent (Event);

这里创建一个事件,然后等待一秒钟之后触发,仅此而已。

输入事件

首先查看一个例子(代码来自MdeModulePkg\Bus\Usb\UsbKbDxe\UsbKbDxe.inf):

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  USBKeyboardWaitForKey,
                  UsbKeyboardDevice,
                  &(UsbKeyboardDevice->SimpleInput.WaitForKey)
                  );

这里以EVT_NOTIFY_WAIT为参数创建了一个事件,其回调函数:

VOID
EFIAPI
USBKeyboardWaitForKey (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  // 略

  //
  // WaitforKey doesn't support the partial key.
  // Considering if the partial keystroke is enabled, there maybe a partial
  // keystroke in the queue, so here skip the partial keystroke and get the
  // next key from the queue
  //
  while (!IsQueueEmpty (&UsbKeyboardDevice->EfiKeyQueue)) {
    //
    // If there is pending key, signal the event.
    //
    CopyMem (
      &KeyData,
      UsbKeyboardDevice->EfiKeyQueue.Buffer[UsbKeyboardDevice->EfiKeyQueue.Head],
      sizeof (EFI_KEY_DATA)
      );
    if ((KeyData.Key.ScanCode == SCAN_NULL) && (KeyData.Key.UnicodeChar == CHAR_NULL)) {
      Dequeue (&UsbKeyboardDevice->EfiKeyQueue, &KeyData, sizeof (EFI_KEY_DATA));
      continue;
    }

    gBS->SignalEvent (Event);
    break;
  }

  // 略
}

它判断USB是否有输入,如果有就触发一次事件,这会导致事件的状态改变,而WaitForEvent()CheckEvent()会检测到该状态并执行相关的操作。

下面是一个具体的例子:

  EFI_STATUS    Status;
  EFI_INPUT_KEY Key;

  Print (L"Press Esc to quit ...\n");
  do {
    Status = gBS->CheckEvent (gST->ConIn->WaitForKey);
    if (!EFI_ERROR (Status)) {
      Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
      if (Key.ScanCode == SCAN_ESC) {
        break;
      }
    }
  } while (TRUE);
  Print (L"Esc pressed, goodbye ~\n");

这里没有直接调用创建事件的函数,原因是输入与硬件有关,在关于硬件初始化的代码中就已经创建了相关的事件,我们可以直接使用。本代码只是通过CheckEvent()(当然也可以使用WaitForEvent()完成相同的操作)来检测是否有按键,如果有则判断是否是Esc,如果是则退出程序。

以上是关于输入事件的一个简单介绍,但是这里还存在着一个巨大的问题:我们的代码只是检测是否有按键,而检测是否有按键的代码也只是简单的判断,比如这里的IsQueueEmpty()

BOOLEAN
IsQueueEmpty (
  IN  USB_SIMPLE_QUEUE  *Queue
  )
{
  //
  // Meet FIFO empty condition
  //
  return (BOOLEAN)(Queue->Head == Queue->Tail);
}

那么又是谁在操作这里的Queue,使得上述函数在有按键的时候返回TRUE呢?查看代码可以找到:

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  USBKeyboardTimerHandler,
                  UsbKeyboardDevice,
                  &UsbKeyboardDevice->TimerEvent
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->SetTimer (UsbKeyboardDevice->TimerEvent, TimerPeriodic, KEYBOARD_TIMER_INTERVAL);
  }

这里创建了一个定时事件,该事件的回调函数每0.02s执行一次,用来往Queue中填充从USB这个源头获取的数据。

不仅是USB,事实上几乎所有与外部硬件的交互都是定时的,或者说是轮巡的,这也是UEFI BIOS和Legacy BIOS的一个重要区别,后者依赖于中断,而前者主要依赖于轮巡,注意这里说“主要”,是因为说到底轮巡的底层还是依赖于中断的支持,这个将在后面更进一步说明。

定时事件

前面的内容已经对定时事件有了不少的介绍,这里再统一说明。先介绍定时事件的使用,它分为两个部分:

  1. 创建事件:
  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  EventFunc1,
                  &Count,
                  &Event
                  );

重点在于EVT_TIMER这个入参,当然EVT_NOTIFY_SIGNAL也很重要,除非不需要回调函数的执行(这种情况也是可以的)。

  1. 设置定时属性:
    Status = gBS->SetTimer (
                    Event,
                    TimerPeriodic,
                    BENI_EVENT_TIMER_INTERVAL
                    );

时间的属性有以下的几种:

///
/// Timer delay types
///
typedef enum {
  ///
  /// An event's timer settings is to be cancelled and not trigger time is to be set/
  ///
  TimerCancel,
  ///
  /// An event is to be signaled periodically at a specified interval from the current time.
  ///
  TimerPeriodic,
  ///
  /// An event is to be signaled once at a specified interval from the current time.
  ///
  TimerRelative
} EFI_TIMER_DELAY;

主要的还是周期时间相对时间两种。周期事件在每个时间间隔之后都会执行,直到被显式地取消;相对事件会在设置时间间隔之后开始计时,时间到了之后执行一遍,然后结束。周期事件已经在前面介绍过,这里介绍一个相对事件的例子:

  EFI_STATUS  Status = EFI_ABORTED;
  EFI_EVENT   WaitEvt = NULL;
  UINTN       Count = 0;

  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &WaitEvt
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->SetTimer (
                    WaitEvt,
                    TimerRelative,
                    EFI_TIMER_PERIOD_SECONDS (5)
                    );
  }
  if (EFI_ERROR (Status)) {
    return;
  }

  while (EFI_ERROR (gBS->CheckEvent (WaitEvt))) {
    Print (L"Waiting %d sencond ...\n", ++Count);
    gBS->Stall (1000 * 1000 * 1);
  }

  gBS->SetTimer (WaitEvt, TimerCancel, 0);
  gBS->CloseEvent (WaitEvt);

这里创建了一个没有回调函数的事件,作用就是一个5秒的超时操作,时间到了之后就退出。

定时器的实现

在前面的介绍中提到过下面的几点:

  • 即使是在DXE阶段,也不是所有的事件都可在任何地方使用;
  • 说到底轮巡本身依赖于中断的支持。

这里将进一步说明。为此需要看关注如下的Protocol:

///
/// This protocol provides the services to initialize a periodic timer
/// interrupt, and to register a handler that is called each time the timer
/// interrupt fires.  It may also provide a service to adjust the rate of the
/// periodic timer interrupt.  When a timer interrupt occurs, the handler is
/// passed the amount of time that has passed since the previous timer
/// interrupt.
///
struct _EFI_TIMER_ARCH_PROTOCOL {
  EFI_TIMER_REGISTER_HANDLER           RegisterHandler;
  EFI_TIMER_SET_TIMER_PERIOD           SetTimerPeriod;
  EFI_TIMER_GET_TIMER_PERIOD           GetTimerPeriod;
  EFI_TIMER_GENERATE_SOFT_INTERRUPT    GenerateSoftInterrupt;
};

extern EFI_GUID  gEfiTimerArchProtocolGuid;

注意这是一个[Architectural Protocol](#Architectural Protocol),也就是说UEFI BIOS必须要实现这个Protocol,它会对应到一个硬件层的定时器初始化,比如OvmfPkg\8254TimerDxe\8254Timer.inf,用于初始化8254芯片,并实现和安装EFI_TIMER_ARCH_PROTOCOL。关于8254芯片的初始化代码不会在这里介绍,只是列举其中跟定时器有关的部分:

  //
  // Find the CPU architectural protocol.
  //
  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
  ASSERT_EFI_ERROR (Status);

  //
  // Find the Legacy8259 protocol.
  //
  Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **)&mLegacy8259);
  ASSERT_EFI_ERROR (Status);

  //
  // Force the timer to be disabled
  //
  Status = TimerDriverSetTimerPeriod (&mTimer, 0);
  ASSERT_EFI_ERROR (Status);

  //
  // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
  //
  TimerVector = 0;
  Status      = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *)&TimerVector);
  ASSERT_EFI_ERROR (Status);

  //
  // Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
  //
  Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, TimerInterruptHandler);
  ASSERT_EFI_ERROR (Status);

  //
  // Force the timer to be enabled at its default period
  //
  Status = TimerDriverSetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);
  ASSERT_EFI_ERROR (Status);

这里就注册了一个中断,该中断就是定时器的基础,在DEFAULT_TIMER_TICK_DURATION的间隔会执行一次中断函数TimerInterruptHandler()。到这里也能够解释前面提到的两点了。

进一步查看TimerInterruptHandler()的代码,其中的主要部分:

  if (mTimerNotifyFunction != NULL) {
    //
    // @bug : This does not handle missed timer interrupts
    //
    mTimerNotifyFunction (mTimerPeriod);
  }

也就是说每隔DEFAULT_TIMER_TICK_DURATION都会执行mTimerNotifyFunction对应的函数。该函数通过EFI_TIMER_ARCH_PROTOCOL中的RegisterHandler()注册,调用的代码在MdeModulePkg\Core\Dxe\DxeMain\DxeProtocolNotify.c:

  //
  // Do special operations for Architectural Protocols
  //

  if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
    //
    // Register the Core timer tick handler with the Timer AP
    //
    gTimer->RegisterHandler (gTimer, CoreTimerTick);
  }

这样的话,相当于CoreTimerTick()函数会被定时调用,其实现:

  //
  // If the head of the list is expired, fire the timer event
  // to process it
  //
  if (!IsListEmpty (&mEfiTimerList)) {
    Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);

    if (Event->Timer.TriggerTime <= mEfiSystemTime) {
      CoreSignalEvent (mEfiCheckTimerEvent);
    }
  }

从这里已经看到了事件相关的代码,通过如下的流程:

CoreSetTimer
CoreInsertEventTimer
InsertTailList

事件就被放入了mEfiTimerList,并被后续调用。

以上就是定时器和事件的底层实现。这里以8254定时器为例,实际上现在还有更精确的HPET定时器,不过流程差不多:

到时
触发
调用
HPET
定时器
8254芯片
CPU
中断
中断函数
定时事件遍历
输入
网络
USB
等等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用数组名作函数参数的详解,以及形参实参采用数组名,形参实参采用指针变量的几种情况解析

关于地址&#xff0c;指针&#xff0c;指针变量可以参考我的这篇文章&#xff1a; 地址&#xff0c;指针&#xff0c;指针变量是什么&#xff1f;他们的区别&#xff1f;符号&#xff08;*&#xff09;在不同位置的解释&#xff1f;_juechen333的博客-CSDN博客https://blog.csd…

Kali的安装与配置

虚拟机安装kali Kali下载 官网下载地址 注&#xff1a;下载VMware版本 百度网盘 提取码&#xff1a;Chen 创建虚拟机 将下载的压缩包放到合适的位置解压 双击运行虚拟机 登录 默认的账号密码都为kali 基本配置 修改root账户密码 打开命令行输入 sudo su root 输入kali 输…

【机器学习】验证集loss震荡(loss的其他问题)

训练过程中发现&#xff0c;train loss一直下降&#xff0c;train acc一直上升&#xff1b;但是val loss、val acc却一直震荡。loss一会上一会下&#xff0c;但是总体趋势是向下的。 “loss震荡但验证集准确率总体下降” 如何解决&#xff1f; 测试集准确率这样震荡是正常的吗…

python2.7/3.8版本安装教程

Wiondos-Python环境安装 Python2.7 下载地址 官网 速度比较慢 百度网盘 提取码:Chen 安装Python2.7 直接next 选择安装目录 注意这一步将最后一项勾选 安装完成 cmd中输入python 检查pip是否安装 cmd中输入pip --version Python3.8 下载地址 官网 速度比较慢 百度网…

蓝桥杯C/C++程序设计 往届真题汇总(进阶篇)

文章目录1. 最短路2. 数字三角形3. 递增序列4. 杨辉三角形5. 跳跃6. 路径7. 迷宫8. 装饰珠9. 明码10. 字串分值11. 作物杂交12. 承压计算13. 全球变暖14. 直线15. 平面切分1. 最短路 题目描述&#xff1a; 如下图所示&#xff0c;G是一个无向图&#xff0c;其中蓝色边的长度是…

线程池执行父子任务,导致线程死锁

前言&#xff0c; 一次线程池的不当使用&#xff0c;导致了现场出现了线程死锁&#xff0c;接口一直不返回。而且由于这是一个公共的线程池&#xff0c;其他使用了次线程池的业务也一直阻塞&#xff0c;系统出现了OOM&#xff0c;不过是幸好是线程同事测试出来的&#xff0c;没…

RPC通信原理解析

一、什么是RPC框架&#xff1f; RPC&#xff0c;全称为Remote Procedure Call&#xff0c;即远程过程调用&#xff0c;是一种计算机通信协议。 比如现在有两台机器&#xff1a;A机器和B机器&#xff0c;并且分别部署了应用A和应用B。假设此时位于A机器上的A应用想要调用位于B机…

第十一届蓝桥杯大赛青少组国赛Python真题2

第十一届蓝桥杯大赛青少组Python 真题 第二题 提示信息&#xff1a; 杨辉三角形&#xff0c;是二项式系数在三角形中的一种几何排列。中国南宋数学家杨辉在 1261 年所著的《详 解九章算法》一书有明确记载。欧洲数学家帕斯卡在 1654 年发现这一规律&#xff0c;所以又叫做帕斯卡…

Rabbit快速入门

入门案例 需求&#xff1a;使用简单模式完成消息传递 步骤&#xff1a; 创建工程&#xff08;生成者、消费者&#xff09; 分别添加依赖 编写生产者发送消息 编写消费者接收消息 3.1.2. 添加依赖 往heima-rabbitmq的pom.xml文件中添加如下依赖&#xff1a; <dependenc…

RabbitMQ的安装和配置

注意: 请使用资料里提供的CentOS-7-x86_64-DVD-1810.iso 安装虚拟机. 1. 安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel tk tc xz2. 安装Erlang 上…

【完整版】国内网络编译,Ambari 2.7.6 全部模块源码编译笔记

本次编译 ambari 2.7.6 没有使用科学上网的工具,使用的普通网络,可以编译成功,过程比 ambari 2.7.5 编译时要顺畅。 以下是笔记完整版。如果想单独查看本篇编译笔记,可参考:《Ambari 2.7.6 全部模块源码编译笔记》 该版本相对 2.7.5 版本以来,共有 26 个 contributors …

使用labelImg标注自己的VOC数据集

文章目录1.下载labelImg2.准备文件夹3.打开软件4.软件使用1.下载labelImg 步骤&#xff1a;WindowsR打开运行界面→输入cmd打开命令行窗口→输入pip install labelImg命令&#xff08;前提是python版本在3.0以上并安装anaconda&#xff0c;如果没有安装anaconda&#xff0c;输…

cmd窗口中java命令报错。错误:找不到或无法加载主类 java的jdk安装过程中踩过的坑

错误: 找不到或无法加载主类 HelloWorld 遇到这个问题时&#xff0c;我尝试过网上其他人的做法。有试过添加classpath&#xff0c;也有试过删除classpath。但是依然报错&#xff0c;这里javac可以编译通过&#xff0c;说明代码应该是没有问题的。只是在运行是出现了错误。我安装…

卷积神经网络的原理及实现

专栏&#xff1a;神经网络复现目录 卷积神经网络 本章介绍的卷积神经网络&#xff08;convolutional neural network&#xff0c;CNN&#xff09;是一类强大的、为处理图像数据而设计的神经网络。 基于卷积神经网络架构的模型在计算机视觉领域中已经占主导地位&#xff0c;当今…

【C3】进程休眠,时间和延时,延缓,/proc文件系统,内存分配,数据类型,/内核中断,通过IO内存访问外设

9.实现进程休眠&#xff1a;条件不够歇一歇&#xff0c;把CPU让给其他进程 有时候进程在读设备时&#xff0c;发现设备数据还没准备好&#xff0c;没办法正常读取设备。或在写设备时&#xff0c;发现设备缓冲区满&#xff0c;没办法正常写设备。在遇到这些情况时&#xff0c;进…

SpringCloud之 Eureka注册中心

文章目录Eureka注册中心一、服务注册与发现1.1 依赖导入①父工程 SpringCloud 版本管理②Eureka 服务端依赖③Eureka 客户端依赖1.2 服务注册①创建 Eureka 服务端的主类②设置 Eureka 服务端的配置文件③设置 Eureka 客户端的配置文件④关闭自我保护机制1.3 服务发现①远程调用…

计算机视觉废钢堆提取问题

计算机视觉废钢堆提取问题 背景介绍 在钢铁炼制中&#xff0c;废钢是非常重要的原料&#xff0c;不同等级废钢对于钢成品影响很大&#xff0c;因此需要对废钢进行正确分类。某废钢料场中&#xff0c;卸料区域布置了多个摄像头&#xff0c;用于拍摄卸料场中废钢堆&#xff0c;…

python 连接数据库

文章目录同步操作同步连Mysql同步连redis同步连mongodb异步操作异步连mysql异步连redis异步连mongodb同步操作 同步连Mysql python 连接mysql可以使用pymysql、mysqlclient等。 安装&#xff1a; # win pip install pymysql 连接mysql: # __author__ "laufing"…

Java各种锁

目录 一、读写锁(ReentrantReadWriteLock) 二、非公平锁(synchronized/ReentrantLock) 三、可重入锁/递归锁(synchronized/ReentrantLock) 四、自旋锁(spinlock) 五、乐观锁/悲观锁 六、死锁 1、死锁代码 2、死锁的检测(jps -l 与 jstack 进程号) 本文通过学习&#xff…

Spring——Spring介绍和IOC相关概念

Spring是以Spring Framework为核心&#xff0c;其余的例如Spring MVC&#xff0c; Spring Cloud&#xff0c;Spring Data&#xff0c;Spring Security SpringBoot的基础都是Spring Framework。 Spring Boot可以在简化开发的基础上加速开发。 Spring Cloud分布式开发 Spring有…