【UEFI】PEI阶段从概念到代码
总述
UEFI开发过程中,BIOS工程师主要关注点和工作都在于PEI和DXE阶段。
DXE阶段是我们的主战场,可以进行丰富且大量的功能驱动开发。
一阵见血。
我们换句话说,PEI阶段是进入DXE阶段前的一个不得已而为之的妥协,或是一个过渡的阶段,我们的目标是进入DXE阶段,能够放开拳脚。
下面介绍一下PEI(Pre-EFI Initialization,EFI前初始化),本人初学者,一家之言,如有错误请留言指正。
为什么有PEI阶段
在PEI阶段在SEC阶段之后,尽管进行了SEC的相关工作,但仍然相对初始。
尤其是内存仍然尚未初始化,而想要利用C语言来做一些丰富的功能开发,尽快进入DXE阶段,最关键的是能够大量地使用“栈
”。
因此在这个阶段,我们希望可以尽快能够初始化Memory
,在一些资料中也被称为“永久内存Permanent Memory”
。
此处的永久内存仍然是指Ram
,即断电易失的存储器
,永久是相对于SEC阶段中的Cache As Ram (CAR)
来说的。
在这个阶段仅利用 CPU 上的资源,如将 CPU 的缓存 Cache
作为栈,来调度PEIM(PEI Module)
,目的是最快进入DXE阶段。这些 PEIM
负责以下工作:
UEFI PI Spec 1.8中这样描述的:
Initializing some permanent memory complement
初始化一些 永久性内存 作为补充
Describing the memory in Hand-Off Blocks (HOBs)
描述 传递块(HOBs)中的内存
Describing the firmware volume locations in HOBs
描述 HOBs 中的固件卷位置
Passing control into the Driver Execution Environment (DXE) phase
将控制权传递到 驱动执行环境(DXE)阶段
Philosophically, the PEI phase is intended to be the thinnest amount of code to achieve the ends listed above. As such, any more sophisticated algorithms or processing should be deferred to the DXE phase of execution.
从哲学上讲,PEI 阶段应该以最少的代码量实现上述目标。因此,任何更复杂的算法或处理都应该推迟到执行 DXE 阶段。
...............
名词很多,而且真的很抽象。
那首先,PEIM
是什么?
PEIM
PEIM
,PEI阶段对系统的初始化主要由PEIM完成。
在具体地认知上,可以认为是一个个的 *.efi
二进制文件。
可以认为,这些个efi
文件就是在UEFI下的可执行文件,类似于我们在单片机中烧写的二进制.bin
文件。
- 资料中说,
.efi
文件格式是基于PE32+
的文件格式而来,具体这个PE32+
格式是个啥,我们先不细究,反正也细究不明白。
更具象地,在编译后的Build
文件夹中,例如在 \edk2\Build\OvmfX64\DEBUG_VS2019\X64\
这个文件夹下,可以找到大量的 .efi
文件,其中有一部分形如 XxxxxxPei.efi
的文件,例如 S3Resume2Pei.efi
文件,使用WinHex等软件可以打开,查看其格式。
流程是:.inf 文件 + .c 文件 + .h 文件 -> build -> .efi
知道了什么是PEIM了,那PEIM这些功能模块是怎么怎么在代码中跑起来的呢?下面我们来看下。
一些概念
PEI 内核(在UEFI Spec中叫
PEI Foundation
,在EDK2代码中其实就是PeiCore
):负责PEI阶段的基础服务和流程,可以认为是PEI阶段的内核,在EDK2代码中,具体可以找到MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
中的函数PeiCore
PEIM Dispatcher(调度器):具体地是在PeiCore中PeiDispatcher函数,Dispatcher会找出系统中的所有PEIM,并根据PEIM之间的依赖关系,按顺序执行PEIM。
PEI Foundation,即PeiCore,会建立一个 UEFI规范里叫 PEI Services Table 的变量,实际在代码里如下图中的gPs,该表对所有系统中的 PEIM 可见。通过PEI Services,PEIM 可以调用 PEI 阶段提供的一些系统功能,例如
Install PPI、Locate PPI
以及Notify PPI
等。
(另外说一嘴,在EDK2中,如果是全局变量就用gVariable的小驼峰形式来标注,如果是仅仅在Module中使用的变量,则mVariable来命名)通过调用这些服务,PEIM可以访问PEI内核。PEIM之间的的通信通过PPI(PEIM-to-PEIM Interfaces)完成。
啥又是Interface?
PPI(PEIM-to-PEIM Interfaces)
在EDK2中,Interface接口的概念使用非常多,然而这里的接口并不是类似于Java或者Web的前后端通信的接口。具体在代码的表现上,其实就是一个结构体,这个结构体描述了某一个函数功能的信息,相当于把一个功能函数封装起来。
在MdePkg\Include\Pi\PiPeiCis.h
中可以看到
PPI
是用 EFI_PEI_PPI_DESCRIPTOR
来封装描述的,里面有个成员是 VOID *Ppi
。
这个成员是个指针,一旦初始化这个描述符,也就是说我们绑定了 某个 Guid 和 某个 Ppi 上,并且通过Flags来指定这个Ppi的一些属性。不要忘了,PPI本质上是希望给其他PEIM调用的功能,所以具体的功能函数就应该存放在这个VOID *Ppi
里。
前面我们也说了,接口本身是一个结构体,这个VOID *Ppi
所以也应该是一个结构体。不信?我们看EDK2中的代码,看看大佬的写法:
可以从上图中看到,首先定义了一个Const EFI_XXX_XXX_PPI类型
的 mXxxxPpi
,因此,可以说,PPI是一个结构体。这个例子中,结构体中只有一个成员WaitForNotify,这个成员是一个函数。
在实际开发中,Const EFI_XXX_XXX_PPI类型
应当是由我们自己定义的, 为啥呢?
想想开发PEIM的流程,我们应当预先写好相关的函数功能,例如Func1、Func2、Func3,再将这些Func1、Func2、Func3统统包含到一个结构体里,那如何把函数包含到结构体里?当然是自己定义结构体原型了。例如:
// 函数原型,注意这里的函数是没有函数体的
typedef
EFI_STATUS
(EFIAPI *EFI_PEI_FUNC_1)();
typedef
EFI_STATUS
(EFIAPI *EFI_PEI_FUNC_2)();
typedef
EFI_STATUS
(EFIAPI *EFI_PEI_FUNC_3)();
// PPI结构体原型定义
typedef struct _EFI_PEI_FUNC1_FUNC2_FUNC3_PPI
{
EFI_PEI_FUNC_1 func1;
EFI_PEI_FUNC_2 func2;
EFI_PEI_FUNC_3 func3;
} EFI_PEI_FUNC1_FUNC2_FUNC3_PPI;
// 函数功能实现
EFI_STATUS
EFIAPI
Func1(){
.......
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
Func2(){
.......
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
Func3(){
.......
return EFI_SUCCESS;
}
// 重点来了,实例化Ppi结构体
EFI_PEI_FUNC1_FUNC2_FUNC3_PPI mFunc1Func2Func3Ppi = {
Func1,
Func2,
Func3
};
紧接着,又利用 EFI_PEI_PPI_DESCRIPTOR
这个描述符封装这个结构体,并指定其Flags属性和绑定Guid
,这样以后我们就可以通过Guid来找到这个PPI,从而调用到PPI里的功能了,是不是很麻烦聪明?
EFI_PEI_PPI_DESCRIPTOR mFunc1Func2Func3PpiList = {
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiFunc1Func2Func3PpiGuid, // 这个GUID在开头自己定义好,或者使用一些UEFI中的,可以实现一些功能
&mFunc1Func2Func3Ppi
};
现在我们知道了怎么定义一个PPI,那该如何完整的开发一个PPI或使用一个PPI呢?
Install 一个自己的 PPI
这里就涉及到了如何编写一个PEIM模块了,实际上上面的定义一个PPI内容都是某一个xxxPEIM.c的内容。
新建一个文件夹(就是PEIM),路径为edk2\OvmfPkg\MyHelloWorldInstallPpi\
,创建两个文件,分别叫做MyHelloWorldInstallPpi.c
、 MyHelloWorldInstallPpi.inf
MyHelloWorldInstallPpi.inf
[Defines]
INF_VERSION = 0x00010005
VERSION_STRING = 1.0
BASE_NAME = MyHelloWorldInstallPpi
MODULE_TYPE = PEIM # 这里必须得是PEIM,表明我们要创建的是一个PEI Module
FILE_GUID = c4f822d4-02e0-4ebf-854d-390dc8ca6166
ENTRY_POINT = MyInstallPpiEntryPoint # 入口函数可以自己随便起名字,只要和.c文件中的一致即可
[Sources]
MyHelloWorldInstallPpi.c
# 我们这一次实验只有这一个.c函数,我们创建自己的PPI,
# 功能是输出HelloWorld的debug信息,并且将其Install到PPI Database中,
# 方便后续我们自己调用
[LibraryClasses]
BaseLib
PeimEntryPoint
BaseMemoryLib
DebugLib
PeiServicesLib
PrintLib
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec
[Pcd]
[Ppis]
[Depex]
TRUE
MyHelloWorldInstallPpi.c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>
#include <Pi/PiPeiCis.h>
EFI_GUID gEfiHelloWorldPpiInstallGuid = {0xf0915e25, 0xe749, 0x4a7a, {0x9f, 0x31, 0xbd, 0xb5, 0x4c, 0x05, 0x22, 0xc4}};
/********************************************************************************
* 当需要将一个PEIM的代码共享给其它PEIM调用的时候,就可以把它安装在PPI的数据库 PPI Database中。
*
* 步骤:
* 1、定义PPI结构体并实例化,结构体里面是具体的功能函数(函数指针)实现
*
* 2、将PPI结构体添加到EFI_PEI_PPI_DESCRIPTOR PPI_List[],这个数组里都是PPI函数指针的struct
*
* 3、在入口函数中Install PPI_List[],将这一套PPI注册在Database中。
*
********************************************************************************/
// 定义PPI功能函数接口原型和结构体
typedef
EFI_STATUS
(EFIAPI *EFI_PRINT_HELLO_WORLD_MSG)(
IN CHAR16 *Msg
);
typedef struct _EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI
{
EFI_PRINT_HELLO_WORLD_MSG peiPrintHelloWorldMsg;
} EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI;
// 实现PPI函数功能,并紧接着实例化结构体
// 功能:打印任意字符串Msg
EFI_STATUS
EFIAPI
PrintHelloMsg (
IN CHAR16 *Msg
)
{
DEBUG ((EFI_D_ERROR, "[MyHelloWorldInstallPpi] PRINT_HELLO_WORLD_MSG is called \r\n"));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldInstallPpi] PrintHelloMsg : %s \r\n", Msg));
return EFI_SUCCESS;
}
// 实例化PPI结构体
EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI mPeiHelloPpi = {
PrintHelloMsg
};
// 添加进PPI_LIST[],并且将PPI和相关的guid绑定
EFI_PEI_PPI_DESCRIPTOR mPeiHelloPpiList[] = {
{
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiHelloWorldPpiInstallGuid,
&mPeiHelloPpi
}
};
/*
* @brief PEIM 的入口函数,PEIM的main函数
*
* @return 状态码
*/
EFI_STATUS
EFIAPI
MyInstallPpiEntryPoint(
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES ** PeiServices
)
{
EFI_STATUS status;
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] MyInstallPpiEntryPoint Start..\r\n"));
// Install PPI
status = (*PeiServices) ->InstallPpi (PeiServices, &mPeiHelloPpiList[0]);
// Install 失败的处理
if (EFI_ERROR(status))
{
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] Install PPI failed.. \r\n"));
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] EFI return value is %d \r\n", status));
return status;
}
// Install 成功,打印通知
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] Install PPI success! \r\n"));
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] MyHelloWorldInstallPPIEntry End.. \r\n"));
return EFI_SUCCESS;
}
这样,就成功的开发了一个PPI。
这个PPI会在PeiCore中受到PeiDispatchor调度,自动运行。
但是我们还不能直接用这个PPI。
上面说过,PPI是PEIM之间的通信方式。
也就是说,PPI是PEIM的对外暴露给其他PEIM的功能接口,因此,我们Install好了PPI还需要再写一个PEIM,来使用我们现在写好的这个PPI。
Locate 一个自己的 PPI
Locate PPI,如同Install PPI,也就是PEI Services里,gPs里,EDK2已经给我们写好的一个API.
新建一个文件夹(就是PEIM),路径为edk2\OvmfPkg\MyHelloWorldLocatePpi\
,创建两个文件,分别叫做MyHelloWorldLocatePpi.c
、 MyHelloWorldLocatePpi.inf
MyHelloWorldLocatePpi.inf
[Defines]
INF_VERSION = 0x00010005
VERSION_STRING = 1.0
BASE_NAME = MyHelloWorldLocatePpi
MODULE_TYPE = PEIM
FILE_GUID = af521e0f-4aef-498a-8f19-b1de83a77c70
ENTRY_POINT = MyLocatePpiEntryPoint
[Sources]
MyHelloWorldLocatePpi.c
[LibraryClasses]
BaseLib
PeimEntryPoint
BaseMemoryLib
DebugLib
PeiServicesLib
PrintLib
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec # 多一个我们写PPI的那个Pkg
[Pcd]
[Ppis]
gEfiHelloWorldPpiInstallGuid
# 用到了Install这个PEM的PPI,所以要告诉本模块,
# 该PPI的guid,用于查找;
# 另外,也可以在C文件中直接调用,更方便
[Depex]
gEfiHelloWorldPpiInstallGuid
# 这边是使用我们自己创建的PpiGuid的,
# 这样可以确保我们的调用Ppi的函数时,
# 该Ppi已经被Install了。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>
#include <Pi/PiPeiCis.h>
// EFI_GUID gEfiHelloWorldPpiInstallGuid = {0xf0915e25, 0xe749, 0x4a7a, {0x9f, 0x31, 0xbd, 0xb5, 0x4c, 0x05, 0x22, 0xc4}};
// 定义PPI功能函数接口原型和结构体
typedef
EFI_STATUS
(EFIAPI *EFI_PRINT_HELLO_WORLD_MSG)(
IN CHAR16 *Msg
);
typedef struct _EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI
{
EFI_PRINT_HELLO_WORLD_MSG peiPrintHelloWorldMsg;
} EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI;
EFI_STATUS
EFIAPI
MyLocatePpiEntryPoint(
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES ** PeiServices
)
{
EFI_STATUS Status;
// 定义一个变量,用于接收解析到的PPI,相当于接受实例
EFI_PEI_PRINT_HELLO_WORLD_MSG_PPI *mHelloWorldPpi = NULL;
DEBUG ((EFI_D_ERROR, "[MyLocatePpiEntryPoint] MyLocatePpiEntryPoint Locate PPI Start..\n"));
// Locate PPI
Status = PeiServicesLocatePpi (
&gEfiHelloWorldPpiInstallGuid,// 这里的GUID虽然没有定义也没有extern,但是因为我们在inf里写了,所以可以直接用
0,
NULL,
(VOID **)&mHelloWorldPpi
);
if (EFI_ERROR(Status))
{
DEBUG ((EFI_D_ERROR, "[MyLocatePpiEntryPoint] Locate PPI failed..\r\n"));
DEBUG ((EFI_D_ERROR, "[MyInstallPpiEntryPoint] EFI return value is %d \r\n", Status));
return Status;
}
// Locate 成功,打印通知
DEBUG ((EFI_D_ERROR, "[MyLocatePpiEntryPoint] Locate PPI success! \r\n"));
// 调用PPI内的功能
mHelloWorldPpi-> peiPrintHelloWorldMsg(L"2025 Tyler Wang Locate PPI Hello World ...\n");
DEBUG ((EFI_D_ERROR, "[MyLocatePpiEntryPoint] MyLocatePpiEntryPoint Locate PPI End..\n"));
return EFI_SUCCESS;
}
编译
进入edk2
目录,在edksetup.bat
最后一行添加
build -a X64 -p OvmfPkg\OvmfPkgX64.dsc -D DEBUG_ON_SERIAL_PORT
这样以后打开cmd之后,只需要运行edksetup.bat
即可自动编译出.fd文件。
编译通过之后,使用qemu模拟器。
在qemu模拟器的路径下,例如我是D:\Program Files\qemu
,创建setup-qemu-x64.bat
文件。
里面内容是:
"D:\Program Files\qemu\qemu-system-x86_64.exe" -bios "D:\edk2\edk2\Build\OvmfX64\DEBUG_VS2019\FV\OVMF.fd" -M "pc" -m 256 -cpu "qemu64" -boot order=dc -serial stdio
这里面的路径请根据自己打情况自行修改。
在qemu模拟器的路径下,cmd运行setup-qemu-x64.bat | findstr "Hello World"
,如下图
可以观察到Hello World现象了。
后记
InstallPpi.c文件写好了之后,我中间编译了好几次,一直显示fail,如下图:
一直以为是我的cl.exe环境配置有问题
NMAKE : fatal error U1077: D:\Develop\Microsoft\VisualStudio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x64\cl.exe: ش롰0x2
Stop.
然而,在我删去自己的PEIM重新编译OvmfPkg这个dsc之后,却可以编译通过。
百思不得其解。
接下来的编译失败的信息也少得可怜,也仅仅是告知我是我的PEIM模块出了问题。。。。
build.py...
: error 7000: Failed to execute command
D:\Develop\Microsoft\VisualStudio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx86\x86\nmake.exe /nologo tbuild [D:\edk2\edk2\Build\OvmfX64\DEBUG_VS2019\X64\OvmfPkg\MyHelloWorldInstallPpi\MyHelloWorldInstallPpi]
build.py...
: error F002: Failed to build module
D:\edk2\edk2\OvmfPkg\MyHelloWorldInstallPpi\MyHelloWorldInstallPpi.inf [X64, VS2019, DEBUG]
虽然始终找不到问题在哪里,但是可以确定是自己的问题,接下来就是开始漫长的排查。
下面介绍一下我的做法,供给后来的和我一样的小白们参考/(ㄒoㄒ)/~~
Step 1、将.c文件中所有东西都注释掉,仅仅保留 入口函数和return EFI_SUCCESS;语句
build一下,发现可以通过。
Step 2、将入口函数中的语句一行一行取消注释。。。。。到了哪一句无法编译通过,就是谁的问题。
后来终于定位到了,原来是这里DEBUG,不小心少复制了一个D
不得不吐槽,vscode 配合 EDK2原生的这个编译器,真是个灾难,编译不通过什么提示都没有。。。。定位这么小的错误需要半天!!!!!!!
vscode更是个大烂货,这么明显的错误都没有提示~~~~
这个一句句的排查也只能够是这种实验的小模块,如果是大工程,那就很耗费精力了。。。。(也许可以2分法排查?)
看来,写一点编译一点,这是一个好习惯。
少写多编,少些多提交,始终是个习惯啊
【UEFI】PEI阶段从概念到代码的更多相关文章
- 第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)
学习<Windows程序设计>记录 概念贴士: 1. 线程描述了进程内代码的执行路径. 2. _stdcall是新标准C/C++函数的调用方法.从底层来说,使用这种调用方法参数的进栈顺序和 ...
- 第二章--Win32程序运行原理 (部分概念及代码讲解)
学习<Windows程序设计>记录 概念贴士: 1. 每个进程都有赋予它自己的私有地址空间.当进程内的线程运行时,该线程仅仅能够访问属于它的进程的内存,而属于其他进程的内存被屏蔽了起来,不 ...
- OpenResty 执行阶段的概念和用途
主要还是 Nginx 的执行阶段知识了,都是因为 OR 才会那么深刻, 它有些自己的阶段. 主要还是参照 春哥的 Nginx 教程 请多读几遍,如果不清楚nginx的执行阶段就无法充分利用 openr ...
- bluetooth(蓝牙) AVRCP协议概念及代码流程解析
一 概念 AVRCP全称:The Audio/Video Remote Control Profile (AVRCP) 翻译成中文就是:音视频远程控制协议.概念:AVRCP定义了蓝牙设备之间的音视频传 ...
- 基于ARMv8的固件系统体系结构
基于ARMv8的固件系统体系结构 The architecture of ARMv8-based firmware systems 自2011年发布以来,ARMv8处理器架构在移动设备市场上已经相当普 ...
- (译)UEFI 启动:实际工作原理
本文是我翻译自国外技术博客的一篇文章,其中讲述了 UEFI 的一些基本概念和细节. 本文的原始链接位于: https://www.happyassassin.net/2014/01/25/uefi-b ...
- UEFI EVENT 全解
Event和Timer在UEFI当中是怎么实现的以及原理,我们先从Timer开始,然后细细的拨开隐藏在底层的实现. 先说Timer,那什么是Timer呢?其实在中文里面我们把它叫做定时/计数器,但是我 ...
- 笔记三(UEFI详解)
1.SEC 安全验证 SEC(Security Phase)阶段是平台初始化的第一个阶段,计算机系统加电后进入这个阶段. 1)接收并处理系统启动和重启信号:系统加点信号.系统重启信号.系统运行过程中的 ...
- UEFI启动(翻译)
本文是我翻译自国外技术博客的一篇文章,其中讲述了 UEFI 的一些基本概念和细节. 本文的原始链接位于: https://www.happyassassin.net/2014/01/25/uefi-b ...
- BIOS、UEFI、Boot Loader都是些什么
BIOS.UEFI.Boot Loader都是些什么 目录 BIOS.UEFI.Boot Loader都是些什么 什么是BIOS 基本的输入输出是什么 自检程序"检"了什么 系统自 ...
随机推荐
- 分合之道:最小生成树的 Kruskal 与 Prim 算法
最小生成树问题 想象你是一位城市规划师,面前摊开一张地图,标记着散落的村庄.你的任务是用最经济的成本,在村庄间铺设道路,让所有村庄互通.这个问题看似简单,却隐藏着一个经典的数学命题:如何在一张&quo ...
- FANUC发那科工业机器人减速器维修小细节
在现代工业生产中,FANUC发那科机器人已成为不可或缺的一部分.然而,随着时间的推移,发那科机械手减速器可能会出现故障,影响机器人的正常工作. 一.了解减速器的结构与工作原理 在开始FANUC发那科机 ...
- 腾讯解禁 QQ 极速版,且看我收集的最全 QQ 各类版本
因为利益关系,腾讯早就限制QQ极速版的登录了,近日居然解除限制了,面对越来越臃肿的QQ,我给大伙准备了几十个版本的QQ,总有一个适合你. QQ版本合集 给大伙们收集了QQ版本合集,分别有历史版本.精简 ...
- MT Photos——一个比群晖Moments更好用的AI相册管理神器
MT Photos是一款为NAS用户量身打造的照片管理系统. 通过AI技术,自动将您的照片整理.分类,包括但不限于时间.地点.人物.照片类型. 您可以在任何支持Docker的系统中运行它. 如果您的操 ...
- Featurewiz-Polars:一种强大且可扩展的特征选择解决方案,适用于XGBoost
前言:"Featurewiz-Polars"是一个用于特征工程的 Python 库,结合了特征选择和特征生成的功能.它基于"Polars",这是一个高性能的 D ...
- 红日复现为什么失败之struct-046流量分析加msf特征总结
struts2漏洞 一.指纹识别 s2的url路径组成(详见struts.xml配置文件):name工程名+namespace命名空间+atcion名称+extends拓展名 部署在根目录下,工程名可 ...
- 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异 引言 在开发 Web 应用时,处理 HTTP 错误响应是常见的任务,尤其是在客户端代码中捕获并向用户展示错误信息.然而,当使用 HTTP ...
- 部署sing-box代理服务器绕过付费校园网上网
解决的问题 学校一般会有2个网络,一个是教学区的免费校园网,一个是寝室楼的付费校园网.如何不交钱也能在寝室楼上网是一个问题. 以及,如果校园网在12点之后断网,如果解决断网问题 sing-box Gi ...
- 实现Windows之间(win10)的桌面连接的三步走方法
实现Windows之间(win10)的远程桌面连接的三步走方法 目录 目录 实现Windows之间(win10)的远程桌面连接的三步走方法 目录 环境 step1:打开两台Windows电脑的 ...
- 解决 Mac(M1/M2)芯片,使用node 14版本
前言 nvm 在安装 Node.js v14.21.3 时,报错: nvm install 14 Downloading and installing node v14.21.3... Downloa ...