济南网站优化公司哪家好,铁道部建设司网站,一个网站多久能做完,我找客户做网站怎么说简述
在【UEFI基础】System Table和Architecture Protocols介绍Boot Service时提到有一部分与事件相关的接口#xff0c;它们创建、触发、等待和关闭事件#xff0c;来完成某些功能#xff0c;本文将进一步介绍事件。
需要注意#xff0c;因为Boot Service需要在DXE阶段才…简述
在【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 eventparam[in] Event Event whose notification function is being invoked.param[in] Context The pointer to the notification functions 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 (LCount: %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); // 6sreturn 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_SIGNAL、EVT_SIGNAL_EXIT_BOOT_SERVICES、EVT_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 (LWaiting 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 doesnt 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 (LPress 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 (LEsc 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的一个重要区别后者依赖于中断而前者主要依赖于轮巡注意这里说“主要”是因为说到底轮巡的底层还是依赖于中断的支持这个将在后面更进一步说明。
定时事件
前面的内容已经对定时事件有了不少的介绍这里再统一说明。先介绍定时事件的使用它分为两个部分
创建事件 Status gBS-CreateEvent (EVT_TIMER | EVT_NOTIFY_SIGNAL,TPL_CALLBACK,EventFunc1,Count,Event);重点在于EVT_TIMER这个入参当然EVT_NOTIFY_SIGNAL也很重要除非不需要回调函数的执行这种情况也是可以的。
设置定时属性 Status gBS-SetTimer (Event,TimerPeriodic,BENI_EVENT_TIMER_INTERVAL);时间的属性有以下的几种
///
/// Timer delay types
///
typedef enum {////// An events 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 (LWaiting %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);}}从这里已经看到了事件相关的代码通过如下的流程
#mermaid-svg-pnMWYQDN0zqpyHyk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .error-icon{fill:#552222;}#mermaid-svg-pnMWYQDN0zqpyHyk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pnMWYQDN0zqpyHyk .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-pnMWYQDN0zqpyHyk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pnMWYQDN0zqpyHyk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pnMWYQDN0zqpyHyk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pnMWYQDN0zqpyHyk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pnMWYQDN0zqpyHyk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pnMWYQDN0zqpyHyk .marker.cross{stroke:#333333;}#mermaid-svg-pnMWYQDN0zqpyHyk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pnMWYQDN0zqpyHyk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .cluster-label text{fill:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .cluster-label span{color:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .label text,#mermaid-svg-pnMWYQDN0zqpyHyk span{fill:#333;color:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .node rect,#mermaid-svg-pnMWYQDN0zqpyHyk .node circle,#mermaid-svg-pnMWYQDN0zqpyHyk .node ellipse,#mermaid-svg-pnMWYQDN0zqpyHyk .node polygon,#mermaid-svg-pnMWYQDN0zqpyHyk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pnMWYQDN0zqpyHyk .node .label{text-align:center;}#mermaid-svg-pnMWYQDN0zqpyHyk .node.clickable{cursor:pointer;}#mermaid-svg-pnMWYQDN0zqpyHyk .arrowheadPath{fill:#333333;}#mermaid-svg-pnMWYQDN0zqpyHyk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pnMWYQDN0zqpyHyk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pnMWYQDN0zqpyHyk .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-pnMWYQDN0zqpyHyk .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-pnMWYQDN0zqpyHyk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pnMWYQDN0zqpyHyk .cluster text{fill:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk .cluster span{color:#333;}#mermaid-svg-pnMWYQDN0zqpyHyk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pnMWYQDN0zqpyHyk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}CoreSetTimerCoreInsertEventTimerInsertTailList事件就被放入了mEfiTimerList并被后续调用。
以上就是定时器和事件的底层实现。这里以8254定时器为例实际上现在还有更精确的HPET定时器不过流程差不多
#mermaid-svg-ECpCX39Xcl8Bk5nL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .error-icon{fill:#552222;}#mermaid-svg-ECpCX39Xcl8Bk5nL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ECpCX39Xcl8Bk5nL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .marker.cross{stroke:#333333;}#mermaid-svg-ECpCX39Xcl8Bk5nL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .cluster-label text{fill:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .cluster-label span{color:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .label text,#mermaid-svg-ECpCX39Xcl8Bk5nL span{fill:#333;color:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .node rect,#mermaid-svg-ECpCX39Xcl8Bk5nL .node circle,#mermaid-svg-ECpCX39Xcl8Bk5nL .node ellipse,#mermaid-svg-ECpCX39Xcl8Bk5nL .node polygon,#mermaid-svg-ECpCX39Xcl8Bk5nL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .node .label{text-align:center;}#mermaid-svg-ECpCX39Xcl8Bk5nL .node.clickable{cursor:pointer;}#mermaid-svg-ECpCX39Xcl8Bk5nL .arrowheadPath{fill:#333333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-ECpCX39Xcl8Bk5nL .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-ECpCX39Xcl8Bk5nL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ECpCX39Xcl8Bk5nL .cluster text{fill:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL .cluster span{color:#333;}#mermaid-svg-ECpCX39Xcl8Bk5nL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ECpCX39Xcl8Bk5nL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}到时触发调用HPET定时器8254芯片CPU中断中断函数定时事件遍历输入网络USB等等