【UEFI基础】UEFI事件介绍
简述
在【UEFI基础】System Table和Architecture Protocols介绍Boot Service时提到有一部分与事件相关的接口,它们创建、触发、等待和关闭事件,来完成某些功能,本文将进一步介绍事件。
需要注意,因为Boot Service需要在DXE阶段才能够使用,所以在此之前的PEI等阶段,是没有事件可用的。事实上,即使是在DXE阶段,也不是可以在任何地方使用,在定时器的实现会进一步说明。
介绍事件之前先回顾一下Boot Service中的相关接口:
函数名 | 类型 | 函数描述 |
---|---|---|
CreateEvent | Boot | 创建事件。 |
CreateEventEx | Boot | 作用与CreateEvent大致相同,不过使用方式稍有差别。 |
CloseEvent | Boot | 关闭事件并释放相关资源。 |
SignalEvent | Boot | 触发事件。 |
WaitForEvent | Boot | 等待事件被触发,在事件没有被触发的时候会一直等待。 |
CheckEvent | Boot | 检测事件是否处于被触发状态。 |
SetTimer | Boot | 给事件一个触发时间。 |
下面介绍一个简单的例子,首先是创建一个事件:
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); // 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 (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的一个重要区别,后者依赖于中断,而前者主要依赖于轮巡,注意这里说“主要”,是因为说到底轮巡的底层还是依赖于中断的支持,这个将在后面更进一步说明。
定时事件
前面的内容已经对定时事件有了不少的介绍,这里再统一说明。先介绍定时事件的使用,它分为两个部分:
- 创建事件:
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 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);}}
从这里已经看到了事件相关的代码,通过如下的流程:
事件就被放入了mEfiTimerList
,并被后续调用。
以上就是定时器和事件的底层实现。这里以8254定时器为例,实际上现在还有更精确的HPET定时器,不过流程差不多:
相关文章:

【UEFI基础】UEFI事件介绍
简述 在【UEFI基础】System Table和Architecture Protocols介绍Boot Service时提到有一部分与事件相关的接口,它们创建、触发、等待和关闭事件,来完成某些功能,本文将进一步介绍事件。 需要注意,因为Boot Service需要在DXE阶段才…...
Markdown 语法速查表
Markdown 速查表提供了所有 Markdown 语法元素的基本解释。如果你想了解某些语法元素的更多信息,请参阅更详细的基本语法和拓展语法。 #基本语法 这些是 John Gruber 的原始设计文档中列出的元素。所有 Markdown 应用程序都支持这些元素。 元素Markdown 语法标题…...

【C++】-- 类型转换
目录 前言 C语言中的类型转换 C强制类型转换 static_cast(static静止的) reinterpret_cast(reinterpret重新解释) const_cast(const常量) 总结 dynamic_cast(dynamic动态) …...

汇编基础语法和指令总结+案例(用32位汇编实现插入排序)
目录 前提知识 案例 c的插入排序 32位汇编代码 代码分析 效果展示 前提知识 常用指令add指令 sub指令 mul乘法指令 div除法指令 inc(自增)(即) dec(自减)(即--) cmp…...
C++多线程--线程安全的单例模式
0 引言 由于最近事情比较多,所以很久没有更新相应的专栏了。目前事情基本告一段落,重新恢复相应专栏的更新。 本文主要讲解在C++并发编程中如何实现线程安全的单例模式。本文主要由如下几部分构成 臭名昭著的double-check单例实现四种线程安全的单例模式单例模式使用中所带…...

(Android-RTC-9)PeerConnectionFactory
开篇前瞎扯。很久没发技术文章了,此文一直放着草稿箱没有完成,感觉自己在家庭和工作中找到了拖延的借口,开始慢慢变得懒惰了,那是万万不行的。恰逢2023开年ChatGPT的爆火,更让我这些普通程序员危机感瞬间飙升ÿ…...

Vector - CAPL - 定时器函数和使用
定时器在C语言中的使用我想学习过C编程的都不会陌生,它能够提供延时,完成等待一定的时间;它也可以实现多线程的操作,并行实行某些软件功能。那在CAPL中,定时器又能做哪些工作呢?又是怎么使用的呢࿱…...
【嵌入式C】常见问题
1、goto的使用场景有哪些?并讨论其局限? (1)常用来跳出死循坏; (2)在linux开发中,常用于打印错误; (3)goto在某些使用场合会破坏程序的栈逻辑&…...
[神经网络]Transfomer架构
一、概述 Transfomer架构与传统CNN和RNN最大的区别在于其仅依赖自注意力机制,而没有卷积/循环操作。其相较于RNN,不需要进行时序运算,可以更好的进行并行;相较于CNN,其一次可以关注全图而不局限于感受野尺寸。 二、模…...

C++之多态 虚函数表
多态 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。 需要区分一下:1、菱形虚拟继承,是在继承方式前面加上virtual; class Person {}; class Student : virtual public Person {}; class Teacher…...

AI_Papers周刊:第四期
2023.02.28—2023.03.05 Top Papers Subjects: cs.CL 1.Language Is Not All You Need: Aligning Perception with Language Models 标题:KOSMOS-1:语言不是你所需要的全部:将感知与语言模型相结合 作者:Shaohan Huang, Li …...

A Simple Framework for Contrastive Learning of Visual Representations阅读笔记
论文地址:https://arxiv.org/pdf/2002.05709.pdf 目前流行的无监督学范式。通过训练,使模型拥有比较的能力。即,模型能够区别两个数据(instance)是否是相同的。这在 深度聚类 领域受到广泛的关注。(在有监…...

mac安装开发工具:clipy、iterm2、go、brew、mysql、redis、wget等
wget brew install wget clipy Releases Clipy/Clipy GitHub 环境变量 ~下有三个文件 .zshrc .zprofile .bash_profile > cat .zshrc export PATH$PATH:/usr/local/mysql/bin> cat .zprofile eval "$(/opt/homebrew/bin/brew shellenv)"> cat .bas…...

DJ1-1 计算机网络和因特网
目录 一、计算机网络 二、Interent 1. Internet 的介绍 2. Internet 的具体构成 3. Internet 提供的服务 4. Internet 的通信控制 一、计算机网络 定义:是指两台以上具有独立操作系统的计算机通过某些介质连接成的相互共享软硬件资源的集合体。 计算机网络向…...

[1.3.3]计算机系统概述——系统调用
文章目录第一章 计算机系统概述系统调用(一)什么是系统调用,有何作用(二)系统调用与库函数的区别(三)小例子:为什么系统调用是必须的(四)什么功能要用到系统调…...

【Java开发】JUC进阶 03:读写锁、阻塞队列、同步队列
1 读写锁(ReadWriteLock)📌 要点实现类:ReentrantReadWirteLock通过读写锁实现更细粒度的控制,当然通过Synchronized和Lock锁也能达到目的,不过他们会在写入和读取操作都给加锁,影响性能&#x…...
Fragment中获取Activity的一点点建议
平时的Android开发中,我们经常要在Fragment中去获取当前的Activity实例,刚开始的时候可能使用使用Fragment提供的getActivity方法来获取,但是这个方法可能返回null,为了让程序可以正常运行,项目中就出现大量下面这样的…...
Java Math类
Java Math 类是 Java 标准库中提供的一个数学计算类,它提供了很多数学函数,如三角函数、指数函数、对数函数等。在实际工作中,Java Math 类常常被用于处理数学计算问题,例如计算复杂的数学公式、实现数学算法等。本文将详细介绍 J…...
Javascript -- 加载时间线 正则表达式
js加载时间线 1、创建Document对象,开始解析web页面,解析html元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段的document.readyState ‘loading’ 2、遇到link外部css,创建线程加载,并继续解析文档 3、遇到…...

gdb/git的基本使用
热爱编程的你,一定经常徘徊在写bug和改bug之间,调试器也一定是你随影而行的伙伴,离开了它你应该会寝食难安吧! 目录 gdb的使用 断点操作 运行调试 观察数据 Git的使用 仓库的创建和拉取 .gitignore “三板斧” 常用指令 gd…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...