LLVM Cpu0 新后端5 静态重定位 动态重定位

news2024/11/25 2:42:19

 想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:

LLVM 后端实践笔记

代码在这里(还没来得及准备,先用网盘暂存一下):

链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s 

目录

一、准备知识

1.1 链接器与编译器

1.2 静态重定位

1.3 动态重定位

二、修改的文件

2.1 Cpu0AsmPrinter.cpp

2.2 Cpu0ISelDAGToDAG.cpp/.h

2.3 Cpu0ISelLowering.cpp/.h

2.4 Cpu0InstrInfo.td

2.5 Cpu0MCInstLower.cpp/.h

2.6 Cpu0MachineFunctionInfo.cpp/.h

2.7 Cpu0RegisterInfo.cpp

2.8 Cpu0Subtarget.cpp/.h

2.9 Cpu0TargetObjectFile.cpp/.h

2.10 MCTargetDesc/Cpu0BaseInfo.h

三、修改后的效果

3.1 static模式

3.2 static small section模式

3.3 dynamic模式

3.4 dynamic small section模式


一、准备知识

1.1 链接器与编译器

首先,显而易见链接器和编译器肯定不是一个东西。编译器的作用是中端优化以及后端转化成目标架构相关的汇编文件或者目标文件,到这里编译器就算是完成了他的工作了。之后就需要链接器了,链接器的作用是将生成的目标文件与其他的应用库和系统库一起链接成可执行文件,传统链接器在这一过程中主要做了两个工作,一是段合并,链接器会将不同文件的相似段进行合并,另一个就是符号重定位,链接器会对文件进行第二次扫描,利用第一次扫描的符号表信息,对符号引用地址的地方进行地址的更新,这个就是符号的解析以及重定位过程。

注意,这里我们说的是传统链接器,因为现在的链接器都支持LTO(链接时优化),其实相当于将编译器部分的一些中端优化和后端的工作在链接阶段做,链接阶段所需的所有文件是可见的,因此能够在一个更高的视野上做一个全局的优化,所以现代链接器的功能绝不仅仅是上述两点。

本章是做Cpu0架构重定位的适配,因此本章简单介绍一下当前linux系统上的两种重定位机制。我们用下边的这个例子:

# extern.c
int extern_aaa = 555;
int extern_func() {
  return 666;
}

#test.c
extern int extern_aaa;
int global_bbb = 111;
static int static_ccc = 222;
extern int extern_func();
int global_func() {
  return 333;
}
static int static_func() {
  return 444;
}
int main() {
  int ddd1 = extern_aaa;
  int ddd2 = global_bbb;
  int ddd3 = static_ccc;
  int ddd4 = extern_func();
  int ddd5 = global_func();
  int ddd6 = static_func();
  return 0;
}

1.2 静态重定位

静态重定位一般由操作系统中的重定位装入程序完成。重定位装入程序根据当前内存的分配情况,按照分配区域的起始地址逐一调整目标程序指令中的地址部分。目标程序在经过重定位装入程序加工之后,不仅进入到分配给自己的绝对地址空间中,而且程序指令中的地址部分全部进行了修正,反映出了自己正确的存储位置,保证了程序的正确运行。

特点:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故成为静态重定位。

优点:无需硬件支持。

缺点:程序重定位之后就不能在内存中搬动了;要求程序的存储空间是连续的,不能把程序放在若干个不连续的区域中。

clang -fno-PIC -g -c extern.c -o extern.o
clang -fno-PIC -g -c test.c -o test.o
clang -no-pie -g extern.o test.o -o test.out

clang默认是使用动态重定位的,因此我们使用no-pic选项来关闭PIC,同时打开-g选项方便我们看到更多的符号以及调试,我的机器的clang版本是15,不同版本的clang可能会有一些区别。

首先我们先通过objdump -d命令先看一下test.o中的内容

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <global_func>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	b8 4d 01 00 00       	mov    $0x14d,%eax
   9:	5d                   	pop    %rbp
   a:	c3                   	retq
   b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

0000000000000010 <main>:
  10:	55                   	push   %rbp
  11:	48 89 e5             	mov    %rsp,%rbp
  14:	48 83 ec 20          	sub    $0x20,%rsp
  18:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
  1f:	8b 04 25 00 00 00 00 	mov    0x0,%eax
  26:	89 45 f8             	mov    %eax,-0x8(%rbp)
  29:	8b 04 25 00 00 00 00 	mov    0x0,%eax
  30:	89 45 f4             	mov    %eax,-0xc(%rbp)
  33:	8b 04 25 00 00 00 00 	mov    0x0,%eax
  3a:	89 45 f0             	mov    %eax,-0x10(%rbp)
  3d:	b0 00                	mov    $0x0,%al
  3f:	e8 00 00 00 00       	callq  44 <main+0x34>
  44:	89 45 ec             	mov    %eax,-0x14(%rbp)
  47:	e8 00 00 00 00       	callq  4c <main+0x3c>
  4c:	89 45 e8             	mov    %eax,-0x18(%rbp)
  4f:	e8 1c 00 00 00       	callq  70 <static_func>
  54:	89 45 e4             	mov    %eax,-0x1c(%rbp)
  57:	8b 45 f8             	mov    -0x8(%rbp),%eax
  5a:	03 45 f4             	add    -0xc(%rbp),%eax
  5d:	03 45 f0             	add    -0x10(%rbp),%eax
  60:	03 45 ec             	add    -0x14(%rbp),%eax
  63:	03 45 e8             	add    -0x18(%rbp),%eax
  66:	03 45 e4             	add    -0x1c(%rbp),%eax
  69:	48 83 c4 20          	add    $0x20,%rsp
  6d:	5d                   	pop    %rbp
  6e:	c3                   	retq
  6f:	90                   	nop

0000000000000070 <static_func>:
  70:	55                   	push   %rbp
  71:	48 89 e5             	mov    %rsp,%rbp
  74:	b8 bc 01 00 00       	mov    $0x1bc,%eax
  79:	5d                   	pop    %rbp
  7a:	c3                   	retq

1f、29、33三条指令就是对于extern_aaa、global_bbb、static_ccc三个变量的调用,我们能够看到当前是用0来占位的。我们能够看到他们最终都被存到了rbp寄存器一定偏移的栈中了,因此局部变量通过当前函数栈桢的基地址(rbp)就可以直接拿到,不存在重定位的问题。然后我们看3f、47、4f这三条指令,e8是callq指令的编码,我们能够看到对于extern_func和global_func这两个函数后边的地址也是通过用0来占位的,然而4f这条后边的数据是 0x1c 00 00 00,由于是小端存储,这个数据的实际大小其实是0x00 00 00 1c=28,这里28是static_func函数相对于当前rip的偏移,当执行到这一条的时候rip指向的是下一条的地址,因此static_func函数的地址是0x54+0x1c=0x70,我们能看到与下边static_func函数的地址相对应。为什么static_func函数的地址能直接算出来呢?是因为在链接过程中虽然会进行段合并,但是段内的数据也不会打乱,因此static_func函数相对main函数的位置还是一样的,我们反汇编看test.out也能够验证这一点,这条指令还是0xe8 1c 00 00 00,不会变的。

我们使用objdump -R test.o或者readelf -r test.o命令能够看到重定位表里边的内容。

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000022 R_X86_64_32S      extern_aaa
000000000000002c R_X86_64_32S      global_bbb
0000000000000036 R_X86_64_32S      .data+0x0000000000000004
0000000000000040 R_X86_64_PLT32    extern_func-0x0000000000000004
0000000000000048 R_X86_64_PLT32    global_func-0x0000000000000004

重定位表里边能够看到几种数据:偏移、类型、哪个符号、以及最后的对齐值。我们会做简单的解释。我们先看第一条extern_aaa的,它对应的指令是1f: 8b 04 25 00 00 00 00,extern_aaa的重定位表的数据是偏移0x22处对齐0,22就是1f中25后边的位置,我们知道这里就是给extern_aaa用0占位的区域。

我们objdump -d test.out查看可执行文件的反汇编:

看到这个位置填了0x 28 40 40 00(也就是0x00 40 40 28的小端序列),链接器在链接的时候会将我们.rela.text记录的符号进行更新,填到记录的offset处。我们lldb调试test.out可以看到:

我们能够看到0x404028地址处存的数据是0x6f也就是111,与我们代码中写的相一致。

1.3 动态重定位

动态重定位在程序执行寻址时进行重定位,访问地址时,通过地址变换机构改变为内存地址。用户程序原封不动地装入内存,运行时再完成地址的定位工作。动态重定位需要硬件的支持,要求系统中配备定位寄存器和加法器

特点:不是在程序执行之前而是在程序执行过程中进行地址重定位。更确切的说,是在每次访问内存单元前才进行地址变换。动态重定位可使装配模块不加任何修改而装入内存,但是它需要硬件一定位寄存器的支持。

优点:目标模块装入内存时无需任何修改,因而装入之后再搬迁也不会影响其正确执行,这对于存储器紧缩、解决碎片问题是极其有利的;一个程序由若干个相对独立的目标模块组成时,每个目标模块各装入一个存储区域,这些存储区域可以不是顺序相邻的,只要各个模块有自己对应的定位寄存器就行。

缺点:需要硬件寄存器的支持。

这一部分参考了海枫老师的课程:聊聊Linux动态链接中的PLT和GOT,大家想更多了解的话可以过去详读。

我们先看动态链接生成的反汇编的形式:

0000000000001150 <main>:
    1150:	55                   	push   %rbp
    1151:	48 89 e5             	mov    %rsp,%rbp
    1154:	48 83 ec 20          	sub    $0x20,%rsp
    1158:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%rbp)
    115f:	48 8b 05 72 2e 00 00 	mov    0x2e72(%rip),%rax        # 3fd8 <extern_aaa>
    1166:	8b 00                	mov    (%rax),%eax
    1168:	89 45 f8             	mov    %eax,-0x8(%rbp)
    116b:	8b 05 bf 2e 00 00    	mov    0x2ebf(%rip),%eax        # 4030 <global_bbb>
    1171:	89 45 f4             	mov    %eax,-0xc(%rbp)
    1174:	8b 05 ba 2e 00 00    	mov    0x2eba(%rip),%eax        # 4034 <static_ccc>
    117a:	89 45 f0             	mov    %eax,-0x10(%rbp)
    117d:	b0 00                	mov    $0x0,%al
    117f:	e8 ac fe ff ff       	callq  1030 <extern_func@plt>
    1184:	89 45 ec             	mov    %eax,-0x14(%rbp)
    1187:	e8 b4 ff ff ff       	callq  1140 <global_func>
    118c:	89 45 e8             	mov    %eax,-0x18(%rbp)
    118f:	e8 0c 00 00 00       	callq  11a0 <static_func>
    1194:	89 45 e4             	mov    %eax,-0x1c(%rbp)
    1197:	31 c0                	xor    %eax,%eax
    1199:	48 83 c4 20          	add    $0x20,%rsp
    119d:	5d                   	pop    %rbp
    119e:	c3                   	retq
    119f:	90                   	nop

一方面我们能够看到对于外部变量和全局变量等的地址是用的rip寄存器的相对地址来表示的,然后还有一个plt的东西,不知道是什么?

首先我们考虑我们的需求,我们希望在程序运行的时候修正这个地址,那么我们直接就让运行时来修正可不可以呢?答案肯定是不可以的,因为我们的代码都在代码段,数据在数据段,运行时可以修改数据段的东西,但是我们的代码段是只读不能修改的。设想一下,如果代码段是能被运行时修改的话,所有的计算机程序都会被黑客彻底攻克了吧?他们可以按他们的意图随便修改代码段的功能。所以说运行时不能直接修正地址。

这个时候就提出了got表这个东西。got表中存储的是函数的地址。为每一个需要重定位的符号建立一个GOT表项。当动态链接器装载共享对象时查找每一个需要重定位符号的变量地址,填充GOT。当指令需要访问变量或者函数的地址时,从对应的GOT表项中读出地址,再访问即可。对应的指令可能是callq *(addr in GOT)或者movq offset(%rip) %rax(%rax就是全局变量的地址,可以用(%rax)解引用)。

但是这样有一个问题,一个动态库可能有成百上千个符号,但是我们引入该动态库可能只会使用其中某几个符号,像上面那种方式就会造成不使用的符号也会进行重定位,造成不必要的效率损失。我们知道,动态链接比静态链接慢1% ~ 5%,其中一个原因就是动态链接需要在运行时查找地址进行重定位。所以ELF采用了延迟绑定的技术,当函数第一次被用到时才进行绑定。实现方式就是使用plt。

Linux 动态链接器提供动态重位功能,所有外部函数只有调用时才做重定位,实现延迟绑定功能。下面是以调用puts函数为例画出了整个动态重定位的执行过程:(两张图用的海枫老师的,画的太形象了,再说一下,大家感兴趣的话可以去海枫老师那里学习一下)

PLT属于代码段,在进程加载和运行过程都不会发生改变,PLT指向GOT表的关系在编译时已完全确定,唯一能发生变化的是GOT表。GOT表项已完成重定位的情况下,PLT利用GOT表直接调用到真实的动态库函数,下面puts函数的调用过程:

上述就是本文对于链接器、静态重定位和动态重定位的一个介绍,虽然写的内容挺多的,但是其实也不是那么详细,因为这一块涉及到的内容太多了,大家感兴趣的话可以自行查阅资料学习,之后有时间的话我也会出一篇详细介绍这方面的文章,接下来就是本章的主题了。

二、修改的文件

虽然这一章节的改动涉及到的文件可能还不少,但是增加的一些函数的逻辑和功能实现都算是相对比较简单的,多数都是一些指令的lower操作,包括DAG阶段指令lower成Cpu0架构相关的DAG,然后再lower成什么样的汇编指令,等等。LLVM中对于指令的修改算是相对比较容易的工作了,主要是你要明白为什么要这么改?其次让你设计的话你是否能想到这个解法?其实后端对于指令的优化涉及到的不像中端那么多,流程上的东西稍微多一点。

2.1 Cpu0AsmPrinter.cpp

在 EmitFunctionBodyStart() 函数中增加了对 .cpload 的输出,.cpload 是一条伪指令,它用来标记一段伪代码,将会被展开成多条指令。另外,.set nomacro 用来判断汇编器的操作生成超过一种机器语言,并输出警告信息。

.set     noreorder
.cpload  $6
.set     nomacro

伪指令展开是在 Cpu0MCInstLower.h 中完成的,LowerCPLOAD() 函数。

2.2 Cpu0ISelDAGToDAG.cpp/.h

实现获取基地址的指令,也就是通过指令将 GOT 地址加载到寄存器。填充 Select 函数,对 ISD::GLOBAL_OFFSET_TABLE 做替换,将其更改为针对我们指定寄存器作为基地址寄存器的 Node。同时还填充了 SelectAddr 函数,对 PIC 模式,返回节点的操作数。

2.3 Cpu0ISelLowering.cpp/.h

在构造函数中,使用 setOperationAction(ISD::GlobalAddress, MVT::i32, Custom) 来告诉 llc,我们实现了全局变量的自定义实现。在 LowerOperation() 函数中,新增一个 switch 分支,当处理 ISD::GlobalAddress 时,跳转到我们自定义的 lowerGlobalAddress() 方法。而后者也在这里实现,这一部分比较关键,会根据设置好的条件,选择下降成 PIC 模式还是 static 模式,small section 还是标准 section,该函数会返回一个 DAG Node。

另外还实现了一些创建地址模式 node 的函数,用来创建不同配置下的 node,比如静态模式,PIC 标准 section 模式等,这个取决于我们的指令设计,其实我们在写代码之前把要创建的指令规划好了,写起来还是比较容易的,主要是指令的设计。

函数 getTargetNodeName() 用来返回节点的名字,在其中增加了 GPRel 和 Wrapper 节点,用来实现对全局变量类型的打印功能。

2.4 Cpu0InstrInfo.td

定义了 Cpu0Hi、Cpu0Lo、Cpu0GPRel、Cpu0Wrapper 几个Cpu0架构在DAG阶段Node,被用来处理全局地址(注意与寄存器 Hi、Lo 的区分)。

实现了几个 Pat,这种 td 结构指示在 lower DAG 时,将指定的 llvm node 下降为另一种机器相关的 DAG node。这些Pat的语法我们上一节课介绍过,这里就不细说了。设计到的语法还是蛮简单的。

2.5 Cpu0MCInstLower.cpp/.h

实现对 MCInst 指令的 lower,在 LowerOperand() 函数中,针对 MO_GlobalAddress 类型的操作数做特殊处理,实现 LowerSymbolOperand() 函数,也就是对符号操作数的处理,当处理全局变量时,能够返回一个符号表达式(比如 %got_lo 这种)。

另外,实现了 LowerCPLOAD() 函数,该函数用来对伪指令 .cpload 进行展开,展开内容为:

lui    $gp, %hi(_gp_disp)
addiu  $gp, $gp, %lo(_gp_disp)
addu   $gp, $gp, $t9

_gp_disp 是一个重定位符号,它的值是函数开头到 GOT 表的偏移,加载器在加载时填充这个值。展开的指令中,我们能看到,$gp 存放的就是 sdata 段的起始地址,而将 $gp 与 $t9 相加($t9 用来保存函数调用的函数地址),就调整好了在某次函数调用时的 sdata 段数据的起始位置。$gp 是需要参与栈调整的,它是 callee saved 寄存器。

2.6 Cpu0MachineFunctionInfo.cpp/.h

实现获取全局基地址寄存器的几个辅助函数。

2.7 Cpu0RegisterInfo.cpp

保留寄存器集合中增加 $gp,但通过宏来控制是否使能对保留寄存器 $gp 的判断。

2.8 Cpu0Subtarget.cpp/.h

增加了处理编译选项的代码,提供了三个编译选项:cpu0-use-small-section、cpu0-reserve-gp、cpu0-no-cpload。第一个是控制是否使用 small section 的选项,后两个选项是将在编译器中用到的配置,分别是是否保留 $gp 作为特殊寄存器以及是否发射 .cpload 伪指令。

2.9 Cpu0TargetObjectFile.cpp/.h

声明并定义了几个判断 small section 的实现方法,属于 Cpu0TargetObjectFile 的成员方法。判断某个地址是否是合法的 small section 地址、判断是否能放到 small section 内。

2.10 MCTargetDesc/Cpu0BaseInfo.h

声明全局变量偏移表的类型枚举,增加 MO_GOT16 和 MO_GOT 两个类型。

三、修改后的效果

示例程序:

int gStart = 3;
int gI = 100;
int test_global()
{
  int c = 0;
  
  c = gI;
  
  return c;
}

3.1 static模式

命令:

clang++ -target mips-unknown-linux-gnu -S -emit-llvm 5_1.cpp -o 5_1.ll
llc -march=cpu0 -relocation-model=static -filetype=asm 5_1.ll -o 5_1.static.s

生成的汇编:

_Z11test_globalv:
...
        lui     $2, %hi(gI)
        ori     $2, $2, %lo(gI)
        ld      $2, 0($2)
        st      $2, 4($sp)
        ld      $2, 4($sp)
...
        .type   gStart,@object                  # @gStart
        .data
        .globl  gStart
        .p2align        2
gStart:
        .4byte  3                               # 0x3
        .size   gStart, 4

        .type   gI,@object                      # @gI
        .globl  gI
        .p2align        2
gI:
        .4byte  100                             # 0x64
        .size   gI, 4

首先使用lui指令加载 gI 的高 16 位部分,放到 $2 中高 16 位,低 16 位填 0;然后将 $2 与 gI 的低 16 位做或运算,将低16位的内容也填上,最后,通过 ld 指令,将 $2 指向的内容(此时 $2 保存的是指向 gI 的地址)取出来,放到 $2 中。gStart 和 gI 都存放在 .data 段。

3.2 static small section模式

命令:

clang++ -target mips-unknown-linux-gnu -S -emit-llvm 5_1.cpp -o 5_1.ll
llc -march=cpu0 -relocation-model=static -cpu0-use-small-section -filetype=asm 5_1.ll -o 5_1.static.small.s
_Z11test_globalv:
...
	st	$2, 4($sp)
	ori	$2, $gp, %gp_rel(gI)
	ld	$2, 0($2)
	st	$2, 4($sp)
	ld	$2, 4($sp)
...
	.type	gStart,@object                  # @gStart
	.section	.sdata,"aw",@progbits
	.globl	gStart
	.p2align	2
gStart:
	.4byte	3                               # 0x3
	.size	gStart, 4

	.type	gI,@object                      # @gI
	.globl	gI
	.p2align	2
gI:
	.4byte	100                             # 0x64
	.size	gI, 4

其中 $gp 寄存器保存了 .sdata 的起始绝对地址,在加载时赋值(此时 $gp不能被当做普通寄存器分配),gp_rel(gI) 是计算 gI 相对于段起始的相对偏移,在链接时会计算,所以第一条指令结束时,$2 中就保存了 gI 的绝对地址。第二条指令做 gI 的取值操作。gStart 和 gI 都存放在 .sdata 段。因为 sdata 是自定义段,所以汇编选用了 .section 伪指令来描述。

这种模式下,$gp 的内容是在链接阶段被赋值的, gI 相对于 .sdata 段的相对地址也能在链接时计算,并替换在 %gp_rel(gI) 的位置,所以整个重定位过程是静态完成的(运行开始时地址都已经固定好了)。

3.3 dynamic模式

命令:

clang++ -target mips-unknown-linux-gnu -S -emit-llvm 5_1.cpp -o 5_1.ll
llc -march=cpu0 -relocation-model=pic -filetype=asm 5_1.ll -o 5_1.pic.s
_Z11test_globalv:
...
	st	$2, 4($sp)
	lui	$2, %got_hi(gI)
	addu	$2, $2, $gp
	ld	$2, %got_lo(gI)($2)
	ld	$2, 0($2)
	st	$2, 4($sp)
	ld	$2, 4($sp)
...
	.type	gStart,@object                  # @gStart
	.data
	.globl	gStart
	.p2align	2
gStart:
$gStart$local:
	.4byte	3                               # 0x3
	.size	gStart, 4

	.type	gI,@object                      # @gI
	.globl	gI
	.p2align	2
gI:
$gI$local:
	.4byte	100                             # 0x64
	.size	gI, 4

由于全局数据放到了 data 段,所以 $gp 中保存了在这个函数中全局变量在 data 段的起始地址。通过 %got_hi(gI) 和 %got_lo(gI) 就可以获得全局变量的 GOT 偏移,分别做与 $gp 的高16位的偏移和低16位的偏移的结果,其实就是与 $gp 的偏移,进而得到它在运行时的地址。值得一提的是,这些汇编代码,都是在 td 文件中被定义如何展开的。

3.4 dynamic small section模式

命令:

clang++ -target mips-unknown-linux-gnu -S -emit-llvm 5_1.cpp -o 5_1.ll
llc -march=cpu0 -relocation-model=pic -cpu0-use-small-section -filetype=asm 5_1.ll -o 5_1.pic.small.s
_Z11test_globalv:
...
	st	$2, 4($sp)
	ld	$2, %got(gI)($gp)
	ld	$2, 0($2)
	st	$2, 4($sp)
	ld	$2, 4($sp)
...
	.type	gStart,@object                  # @gStart
	.section	.sdata,"aw",@progbits
	.globl	gStart
	.p2align	2
gStart:
$gStart$local:
	.4byte	3                               # 0x3
	.size	gStart, 4

	.type	gI,@object                      # @gI
	.globl	gI
	.p2align	2
gI:
$gI$local:
	.4byte	100                             # 0x64
	.size	gI, 4

数据存放在 sdata 段, $gp寄存器指向sdata 段的开头,%got(gI)计算gI全局变量的 GOT 偏移。

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

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

相关文章

springboot集成uid-generator生成分布式id

一、简介 uid-generator是由百度技术部开发,GitHub地址 UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器 Snowflake算法 Snowflake算法描述&#xff1a;指定机器 & 同一时刻 & 某一并发序列&#xff0c;是唯一的。据此可生成一个64 bits的唯一ID&#x…

【Linux】信号(二)

上一章节我们进行了信号产生的讲解。 本节将围绕信号保存展开。 目录 信号保存&#xff1a;信号的一些概念&#xff1a;关于信号保存的接口&#xff1a;sigset_t的解释&#xff1a;对应的操作接口&#xff1a;sigprocmask&#xff1a;sigpending&#xff1a;代码实践&#xf…

Macbook M芯片JDK的安装

Macbook M芯片JDK的安装 下载 搜索zulu&#xff1b; 进入这个网址 https://www.azul.com/downloads/#zulu 进入页面后向下滑动 选择对应选项 然后点击Download进行下载。 选择.dmg格式的安装包&#xff0c;点击。 安装 下载完成后&#xff0c;到下载目录&#xff0c;双击…

日进2000,我怎么做到的

昨天遇到一个有意思的项目&#xff0c;让我一天进账2000&#xff0c;一个字&#xff1a;爽。 这几天接洽了一位新客户&#xff0c;主要诉求就是优化系统&#xff0c;基于LNMP的系统优化。正好这个领域我比较熟悉&#xff0c;以前都是在公司做项目&#xff0c;也不怎么涉猎系统优…

LabVIEW硬件与仿真双模式设计液压系统

在实际项目中&#xff0c;结合LabVIEW与液压运动控制器&#xff0c;通过设计两种运行方式&#xff1a;硬件运行和仿真运行&#xff0c;使得系统既能进行实际操作又能进行仿真测试。此设计不仅方便了开发调试&#xff0c;也为教学培训和展示提供了极大的便利。本文将从项目背景、…

【优选算法】栈 {后缀表达式求值;中缀转后缀表达式;中缀表达式求值}

一、经验总结 何时使用栈结构解题&#xff1f; 做过相似的使用栈结构解得的题目嵌套处理&#xff1a;在从前向后处理的过程中&#xff0c;由于之后内容的不确定性而导致当前操作不能贸然进行&#xff0c;需要先进行保存&#xff0c;直到遇到区间结束标志&#xff08;如’)&am…

MLU370-M8 chattts-ui快速出击

目录 一、paas平台环境选择二、代码环境准备1.代码下载2.环境安装modelsopetransformersaccelerate 3.常规pip安装4.代码修改4.代码修改 三.算法启动 一、paas平台环境选择 驱动选择&#xff1a;5.10.22及以上 镜像选择&#xff1a;pytorch2.1 二、代码环境准备 1.代码下载…

HC-SR505人体感应灯

1硬件 1.1硬件组成 1.正点原子探索者开发板 2 HC-SR505迷你小型人体感应模块 3 继电器&#xff0b;5V小灯 HC-SR505迷你小型人体感应模块介绍 1.2 硬件连接 1.HC-SR505&#xff08;连接在PE0&#xff09; 2.继电器&#xff08;连接在PE1&#xff09; 2.主要代码 int ma…

【Stable Diffusion】(基础篇一)—— Stable Diffusion的安装

本系列笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili **Stable Diffusion&#xff08;简称…

SpringCloud Gateway中Filters详细说明

前面 https://blog.csdn.net/J080624/article/details/139494909 我们研究了GateWay中各种路由断言的使用。SpringCloud GateWay 还提供了各种过滤器用来对请求和响应进行处理。 官网地址&#xff1a;SpringCloud Gateway Filter 【1】GatewayFilter Factories 路由过滤器允…

前端修改接口返回测试工具 Inssman使用教程

之前用的requestly现在要登录才能用了&#xff0c;然后我又登录不上去&#xff0c;同事又推荐了个谷歌插件&#xff0c;试了下&#xff0c;挺好用&#xff0c;还不用登录&#xff0c;用法和之前差不多 下载网站&#xff1a;https://chromewebstore.google.com/detail/inssman-…

【网络安全】【深度学习】【入侵检测】SDN模拟网络入侵攻击并检测,实时检测,深度学习

文章目录 1. 前言2. Mininet 和 Ryu 的区别2.1 Mininet2.2 Ryu2.3 总结 3. 模拟攻击3.1 环境准备3.2 创建 Mininet 网络拓扑3.2 启动 Ryu 控制器3.3 模拟网络攻击3.4 捕获流量 4. 实时异常检测4.1 在 Ryu 控制器中4.2 在 h2 机器上的实验结果4.3 深度学习模型部署上h2机器 帮助…

OrangePi Kunpeng Pro深度评测:性能与体验的完美融合

文章目录 一、引言二、硬件开箱与介绍1.硬件清单2.硬件介绍 三、软件介绍四、性能测试1. 功率测试2. cpu测试2.1 单线程cpu测试2.2 多线程cpu测试 五、实际开发体验1. 搭建API服务器2. ONNX推理测试3. 在线推理平台 五、测评总结1. 能与硬件配置2. 系统与软件3. 实际开发体验个…

[Kubernetes] 容器运行时 Container Runtime

文章目录 1.容器运行时(Container Runtime)2.容器运行时接口3.容器运行时层级4.容器运行时比较5.强隔离容器6.K8S为何难以实现真正的多租户 1.容器运行时(Container Runtime) Container Runtime 是运行于 k8s 集群每个节点中&#xff0c;负责容器的整个生命周期。Docker 就目前…

Redis的删除策略与内存淘汰

文章目录 删除策略设置过期时间的常用命令过期删除策略 内存淘汰相关设置LRU算法LFU 总结 在redis使用过程中&#xff0c;常常遇到以下问题&#xff1a; 如何设置Redis键的过期时间&#xff1f;设置完一个键的过期时间后&#xff0c;到了这个时间&#xff0c;这个键还能获取到么…

Foxmail邮箱的使用方法和功能最全介绍

Foxmail邮箱是我们办公邮箱中比较有代表性和使用性的一款邮箱软件&#xff0c;今天笔者为大家介绍一下Foxmail邮箱的功能和使用方法。 1、首先我们从安装Foxmail邮箱开始 2、点击安装等待安装成功 3、双击打开 &#xff0c;出现邮箱设置界面输入我们的账号密码&#xff0c;点击…

ESP32:往MicroPython集成PCNT以支持硬件正交编码器

背景 官方发布的1.23依然没有在ESP32中集成PCNT功能。考虑到硬件的PCNT模块可以提供4倍的编码精度&#xff08;对比使用PIn IRQ&#xff09;&#xff0c;还能提供硬件去毛刺。 还是自己集成一下吧。 实际上Github上早在2022年1月的时候就已经有人建议了将PCNT加入正式版本的功…

家庭电脑私网如何访问阿里云服务器的指定端口

这里我们以在阿里云服务器上部署一个redis server 服务&#xff0c;对外开放6379端口为例子&#xff0c;其他端口类似。 1.获取当前电脑主机对应的公网IP, 可以https://tool.lu/ip/通过这个网站拿到。 2.阿里云服务器控制台设置防火墙&#xff0c;如下图所示&#xff0c;直接添…

【leetcode22-36】链表

160.相交链表 【等比例法】 class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:if not headA or not headB:return NonepointA headApointB headBwhile pointA ! pointB:pointA pointA.next if pointA else headB …

五、身份与访问管理—身份管理和访问控制管理(CISSP)

目录 1.身份管理 1.1 目录技术 1.2 单点登录 1.2.1 Kerberos认证 1.2.2 SESAME认证 1.2.3 KryptoKnight认证 1.3 联合身份管理 1.3.1 SAML安全断言标记语言 1.3.2 标记语言 1.3.3 OpenID 1.3.4 OAuth 1.3.5 OIDC(OpenID Connect) 2.身份即服务(IDaaS) 2.1 AA…