用D写裸机
原文
用D编写裸机RISC-V
应用
这篇文章展示,如何用D
编写,目标为RISC-VQEMU
模拟器的程序裸机"你好"
.项目
为什么是D
?
我最近一直在用C
编写裸机代码
,我有点对C缺乏特征感到沮丧.D
引入了叫betterC
的模式(基本上禁止了D运行时的所有语言功能
).使得D裸机编程
大致与C
一样.没有得到D的所有功能
,但足够涵盖我想要的(事实上,对系统编程
,我更喜欢更好C
子集的D
超过完整的D
).
以下是D
中我最看重的积极
因子:
1,体面的导入
系统(不再有头文件和#include
).
2,自动检查边界
,及绑定串和数组
.
3,结构
中的方法.
4,编译时
代码求值:编译时
运行D代码
!
5,强大的元编程
.
6,迭代器
.
7,默认支持线本存储
.
8,域
保护和RAII
.
9,@safe
的一些内存
安全保护.
10,相当全面和可读
的在线规范
.
11,活跃的不和谐(discord)
频道,在几分钟内回答问题.
12,基于LLVM
的编译器(LDC)
和正式GCC
项目的一部分的GNU
编译器(GDC)
.
13,Clang
和GCC
编译器分别都导出了大致相同的标志和内部函数
.
这些功能,再加上无运行时
和类C
感觉语言(容易移植
以前代码),使我选择D
作为C
的替代.
安装工具链
试用它来编写面向RISC-V
的裸机应用.第一步
是下载工具链(以下工具应该在Linux
或MacOS
上工作).需要三个
不同的组件:
1,LDC1.30
(基于LLVM
的D编译器).可从GitHub
这里下载.确保用1.30
版.
2,GNU
的riscv64-unknown-elf
工具链.可从这里下载.
3,QEMU
的RISC-V
模拟器:qemu-system-riscv64
.可从这里下载,或按系统QEMU
包的一部分提供.
用LDC
,因为它支持riscv64
目标.我也用过GDC
来裸机开发
,但它需要构建riscv64-unknown-elf-gdc
源码的工具链,但没人提供预构建的二进制
文件.
用GNU
工具汇编,链接
,其他工具用objcopy/objdump
,及QEMU
来模拟
硬件.
安装
这些后,应该可运行:
$ ldc2 --version
LDC - the LLVM D compiler (1.30.0):
...$ riscv64-unknown-elf-ld
riscv64-unknown-elf-ld: no input files$ qemu-system-riscv64 -h
...
CPU
入口
编写裸机代码
,所以没有操作系统
,没有控制台
,没有文件
,什么都没有.CPU
只是在初始安装后,在预先指定
的地址开始执行
指令,安装
链接脚本时就知道该地址
.现在可定义_start
符号作为入口
,并假设链接器,会在CPU
入口处的此标签
处放代码.
D函数
需要有效的栈指针
,因此执行D代码
前需要用有效
地址加载sp
栈指针寄存器
.
创建内容如下的叫start.s
的文件:
.section ".text.boot".globl _start
_start:la sp, _stack_startcall dstart
_hlt:j _hlt
现在假设_stack_start
是带有效
栈地址的符号
,在链接
脚本中,正确安装
它.加载sp
后,调用在下一节定义的叫dstart
的D函数
.
D入口
现在可在dstart.d
中定义dstart
函数.现在只是个无限循环
.
module dstart;extern (C) void dstart() {while (1) {}
}
链接脚本
在编译
该程序之前,需要一些链接脚本
来告诉链接器
,代码应该如何布局
.需要指定地址文本节
开始地址(入口地址),并为.rodata,.data,.bss
数据节和栈
保留空间.
入口地址
目标为QEMU
的虚RISC-V
机器,所以要找到它的入口
.
可通过告诉它转储
设备树,向QEMU
查询机器中所有设备
列表,请求如下:
$ qemu-system-riscv64 -machine virt,dumpdtb=virt.dtb
$ dtc virt.dtb > virt.dts//..
在virt.dts
中找到以下项:
memory@80000000 {device_type = "memory";reg = <0x00 0x80000000 0x00 0x8000000>;
};
表明RAM
从0x80000000
地址开始(以下则是特殊
内存或无法访问).虚机的CPU
入口是在0x80000000
内存中存储的第一个指令
.
链接脚本中,要告诉链接器
,它应该在0x80000000
放_start
函数.为此,在0x80000000
的.text
节,首先放.text.boot
节,来告诉
它.然后,包括其余的.text
节,然后是只读
,可写
数据和BSS
.
在link.ld
中:
ENTRY(_start)SECTIONS
{.text 0x80000000 : {KEEP(*(.text.boot))//第1节*(.text*)}.rodata : {. = ALIGN(8);*(.rodata*)*(.srodata*). = ALIGN(8);}.data : {. = ALIGN(8);*(.sdata*)*(.data*). = ALIGN(8);}.bss : {. = ALIGN(8);_bss_start = .;*(.sbss*)*(.bss*)*(COMMON). = ALIGN(8);_bss_end = .;}.kstack : {. = ALIGN(16);. += 4K;_stack_start = .;}/DISCARD/ : { *(.comment .note .eh_frame) }
}
什么是BSS
?
BSS
是编译器假定全部初化为零
的内存区域.一般直接复制程序的静态数据
到ELF
可执行文件.
如果程序中有个你好
串,则确切的字节在二进制文件中的某个(在只读
数据节中)位置
.
但是,许多静态数据
初化为零,因此不是直接把0零字节
写入ELF
文件,链接器为节省空间,创建要求必须在运行时
初化为零,但ELF
文件自身不包含该数据的特殊(BSS)
节.
所以即使有巨大的1MB
零数组,你的ELF
二进制文件会很小,因为仅当应用
启动时,该节才会扩展到RAM
.
一般操作系统在启动
程序前安装BSS
,但因为裸机,因此必须在dstart
函数中手动
这样.为了初化成功,在链接脚本
中定义了地址分别是BSS
节的开头和结尾
的_bss_start和_bss_end
符号.
为栈保留空间
还为.kstack
节保留一页,并在节尾
(栈向下
增长)标记了_stack_start
符号.栈必须为16
字节对齐.
编译!现在,有了编译基本裸机
的一切.
$ ldc2 -Oz -betterC -mtriple=riscv64-unknown-elf -mattr=+m,+a,+c --code-model=medium -c dstart.d
$ riscv64-unknown-elf-as -mno-relax -march=rv64imac start.S -c -o start.o
$ riscv64-unknown-elf-ld -Tlink.ld start.o dstart.o -o prog.elf
看看其中的一些标志:
1,Oz
:针对大小积极优化
.
2,betterC
:启用更好C
模式(禁止内置
的D运行时).
3,mtriple=riscv64-unknown-elf
:针对riscv64
裸机elf
构建.
4,mattr=+m,+a,+c
:启用以下RISC-V
扩展:m
(乘/除
),a
(原子
)和c
(压缩指令
).
5,code-model=medium
:RISC-V
中的代码模型,控制如何构建
远处位置指针.
medium
代码模型(也叫medany
)允许定位
当前地址的2GiB
以内的符号
,建议用64
位程序.更多信息,见SiFive
帖子
6,mno-relax
:禁止汇编
程序中的链接器放松
(LDC
中默认禁止).链接器放松
是RISC-V
相关的允许链接器
利用(gp全局指针
)寄存器的优化
.
重复输入
命令会乏味,所以创建Makefile
(或Knitfile
):
SRC=$(wildcard *.d)
OBJ=$(SRC:.d=.o)all: prog.bin%.o: %.dldc2 -Oz -betterC -mtriple=riscv64-unknown-elf -mattr=+m,+a,+c,+relax --code-model=medium --makedeps=$*.dep $< -c -of $@
%.o: %.sriscv64-unknown-elf-as -march=rv64imac $< -c -o $@
prog.elf: start.o $(OBJ)riscv64-unknown-elf-ld -Tlink.ld $^ -o $@
%.bin: %.elfriscv64-unknown-elf-objcopy $< -O binary $@
%.list: %.elfriscv64-unknown-elf-objdump -D $< > $@
run: prog.binqemu-system-riscv64 -nographic -bios none -machine virt -kernel prog.bin
clean:rm -f *.bin *.list *.o *.elf *.dep-include *.dep
并用:
$ make prog.bin
编译,此文件
是程序的原始转储
.此时,高达22
个字节.
查看反汇编
程序,请运行:
$ make prog.list
...
$ cat prog.list
prog.elf: file format elf64-littleriscvDisassembly of section .text:0000000080000000 <_start>:80000000: 00001117 auipc sp,0x180000004: 02010113 addi sp,sp,32 # 80001020 <_stack_start>80000008: 00000097 auipc ra,0x08000000c: 00c080e7 jalr 12(ra) # 80000014 <dstart>0000000080000010 <_hlt>:80000010: a001 j 80000010 <_hlt>...0000000080000014 <dstart>:80000014: a001 j 80000014 <dstart>
在0x80000000
正确链接了_start
函数,并是期望的汇编!
如果试运行:
$ make run
qemu-system-riscv64 -nographic -bios none -machine virt -kernel prog.bin
它只会进入无限循环(按下Ctrl-A,Ctrl-X
退出QEMU
).得到输出之前,还有更多
工作.
更多安装:初化BSS
现在修改dstart
来初化BSS
.需要声明一些extern
变量,以便D代码可用_bss_start
和_bss_end
链接器符号.然后可从_bss_start
循环到_bss_end
并用零赋值
该区间内的所有字节
.完成后,初化了BSS
,可(用可能初化为零
的全局变量)运行任意D
代码.
extern (C) {extern __gshared uint _bss_start, _bss_end;void dstart() {uint* bss = &_bss_start;uint* bss_end = &_bss_end;while (bss < bss_end) {*bss++ = 0;}import main;kmain();}
}
在main.d
中有裸机主
入口:
module main;void kmain() {}
创建最小D运行时
因为缺乏运行时
,一些D语言功能
不可用.如,未定义串
和size_t
等类型,不能用断定
.创建最小
运行时的第一步是创建object.d
文件.D
编译器搜索
此特殊文件并自动导入
.
因此,可在此处定义串
和size_t
等类型.这是我喜欢用的最小定义
,它也定义了ptrdiff_t,noreturn
和uintptr
.
module object;alias string = immutable(char)[];
alias size_t = typeof(int.sizeof);
alias ptrdiff_t = typeof(cast(void*) 0 - cast(void*) 0);alias noreturn = typeof(*null);static if ((void*).sizeof == 8) {alias uintptr = ulong;
} else static if ((void*).sizeof == 4) {alias uintptr = uint;
} else {static assert(0, "指针必须为4到8位");
}
写入UART
设备
大多数系统
都有UART
设备.一般,工作原理,写个字节
到内存中的特殊位置
,用板上某些引脚
上的UART
协议传输
该字节.
为了用主机读字节
,需要主机上插入UART
到USB
的适配器,然后可从主机上相应
的设备文件
(一般/dev/ttyUSB0
)中读取.
今天模拟在QEMU
中的裸机
代码,所以不需要特殊的适配器.QEMU
模拟UART
设备并打印出写至其传输寄存器
的字节
.
启用易失性加载/存储
写入设备内存
时,确保编译器不会删除加载/存储
非常重要.如,如果设备放在0x10000000
,可转换
整数为指针
来直接写入
该地址.对编译器
,只是写入可能是未定义行为或导致死码
的随机地址
(如,如果从未读回
该值,编译器可能会确定
可消除写入).需要通知编译器
,必须保留读/写
设备内存,且不能优化.D
为此用了volatileStore
和volatileLoad
内部函数.
可在object.d
中定义这些:
pragma(LDC_intrinsic, "ldc.bitop.vld") ubyte volatileLoad(ubyte* ptr);
pragma(LDC_intrinsic, "ldc.bitop.vld") ushort volatileLoad(ushort* ptr);
pragma(LDC_intrinsic, "ldc.bitop.vld") uint volatileLoad(uint* ptr);
pragma(LDC_intrinsic, "ldc.bitop.vld") ulong volatileLoad(ulong* ptr);
pragma(LDC_intrinsic, "ldc.bitop.vst") void volatileStore(ubyte* ptr, ubyte value);
pragma(LDC_intrinsic, "ldc.bitop.vst") void volatileStore(ushort* ptr, ushort value);
pragma(LDC_intrinsic, "ldc.bitop.vst") void volatileStore(uint* ptr, uint value);
pragma(LDC_intrinsic, "ldc.bitop.vst") void volatileStore(ulong* ptr, ulong value);
控制UART
安装后,要弄清楚QEMU
的UART
设备在内存何处,以便可写入
它.
QEMU
虚机器定义了许多虚设备
,就有UART
设备.在virt.dts
中再次浏览QEMU
设备树,如下:
uart@10000000 {interrupts = <0x0a>;interrupt-parent = <0x03>;clock-frequency = <0x384000>;reg = <0x00 0x10000000 0x00 0x100>;compatible = "ns16550a";
};
表示0x10000000
地址上有ns16550a
的UART
设备
实际
硬件上,UART
需要编写一些内存映射
配置寄存器(来安装波特率和其他选项
)来正确初化.但是,不需要初化QEMU
设备.
它模仿NS16550A
设备,写入其传输
寄存器,就可写入UART
的字节(用QEMU
模拟时出现在控制台
上).ns16550a
的传输
寄存器是第一个映射
寄存器,所以它在0x10000000
位置.
在uart.d
中:
module uart;struct Ns16650a(ubyte* base) {static void tx(ubyte b) {volatileStore(base, b);}
}alias Uart = Ns16650a!(cast(ubyte*) 0x10000000);
现在在kmain
中,可测试UART
.
module main;import uart;void kmain() {Uart.tx('h');Uart.tx('i');Uart.tx('\n');
}
$ make prog.bin
$ qemu-system-riscv64 -nographic -bios none -machine virt -kernel prog.bin
hi
按Ctrl-A,Ctrl-x
退出QEMU
(程序从kmain
返回后进入无限循环
).
制作简单的打印函数
现在可用println
函数包装Uart.tx
函数,并很快有个裸机你好世界
!.
在object.d
中:
import uart;void printElem(char c) {Uart.tx(c);
}void printElem(string s) {foreach (c; s) {printElem(c);}
}void print(Args...)(Args args) {foreach (arg; args) {printElem(arg);}
}void println(Args...)(Args args) {print(args, '\n');
}
而在main.d
中:
void kmain() {println("你好,世界");
}
$ make prog.bin
$ qemu-system-riscv64 -nographic -bios none -machine virt -kernel prog.bin
Hello world!
你有它,(模拟
)裸机你好世界
!
一些初化不必要(最终没有用BSS
中的变量),但对编写
更复杂的裸机程序,应该正确安装
好了.
添加支持断定和检查边界
如果试用D断定式
,注意到链接
失败:
riscv64-unknown-elf-ld: dstart.o: in function `_D6dstart5kmainFZv':
dstart.d:(.text+0x3c): undefined reference to `__assert'
它正在找__assert
函数,所以在文件
中创建一个:
size_t strlen(const(char)* s) {size_t n;for (n = 0; *s != '\0'; ++s) {++n;}return n;
}extern (C) noreturn __assert(const(char)* msg, const(char)* file, int line) {
//用[0..长度]语法转换符指针到边界串string smsg = cast(string) msg[0 .. strlen(msg)];string sfile = cast(string) file[0 .. strlen(file)];println("fatal error: ", sfile, ": ", smsg);while (1) {}
}
现在可用assert
语句!
D还支持检查边界
,编译器也会在检查边界失败时调用__assert
.表明现在也有工作的检查边界
.
试试main.d
:
void kmain() {char[10] array;int x = 12;println(array[x]);
}
运行它给出:
fatal error: main.d: array index out of bounds
检查
边界数组!
此代码不打印行号
,因为要求转换整
到串
,练习留给读者.
启用链接器放松
链接放松是RISC-V
工具链中允许通过(存储在gp
寄存器中)全局指针
访问全局变量
的优化
.此值
为数据节
中指针
,允许通过直接从gp
偏移,而不是(需要多个RISC-V
指令)从头开始构造
全局地址的加载
全局变量指令
.
要启用链接器放松
,必须做三件事:
1,修改链接脚本
,使其为全局指针
定义一个符号
.
2,在_start
函数中用此值加载gp
寄存器.
3,在编译器中启用
链接器放松.
要修改链接脚本
,只需在.rodata
节定义的开头加以下内容
:
__global_pointer$ = . + 0x800;
这设置了__global_pointer$
符号(链接器假定在gp
中存储的特殊符号
),来指向数据段的0x800
字节(RISC-V
指令,可在一条指令中加载/存储
,gp
寄存器沿任一方向偏移最多0x800
字节的值).这允许gp
偏移覆盖大部分/所有
静态数据.
接着加到_start
:
.option push
.option norelax
la gp, __global_pointer$
.option pop
要暂时启用norelax
选项,否则汇编
程序会优化为mv gp,gp
.
最后,可从riscv64-unknown-elf-as
调用中删除-mno-relax
标志,并添加-mattr=+m,+a,+c,+relax
到ldc2
调用中,以在编译器中启用链接器松弛
.
删除未用的函数
查看程序的(makeprog.list)
反汇编,注意到有些函数永远不会调用
.这是因为已内联这些函数
,但未删除定义.即使标记
为私有,D
总是导出函数/全局变量
到目标文件
中.幸好,现代链接器
非常聪明,且很容易让链接器
删除这些未用
函数.传递--function-sections
和--data-sections
给LDC
,让它在自己的节中(仍在.text,.data
等中)放每个函数/全局
.
现在,如果传递--gc-sections
标志给链接器
,它删除未引用节
(从而删除未用的函数/全局变量
).有了这些标志
,最终的"你好"
二进制文件,降到了160
字节.
这是链接器
优化的基本形式
.还有更高级的(LTO)
链接时间优化.如果传递-flto=thin
或-flto=full
给LDC
,则它生成的目标文件将是LLVM
位码.然后,要用LLVM
的gold
链接器插件(或用LLD
)调用链接器
,以便可读取这些文件.用此方法,链接器
应用可跨目标文件
完整的编译器优化
.
线本存储和全局变量
默认,全局
变量在D中是线本
的.表明如果按int x;
声明全局.然后,每当访问x
时,编译器会通过系统的线程指针
访问(在RISC-V
上,在tp
寄存器中存储
它).
表明如果用线本
变量,最好确保tp
指向x
所在的内存块
,如果有多线程
,每个线程的tp
应该指向不同线本块
(每个线程都有自己的x
私有副本).
总之,需要初化dstart
中,每个线程的.tdata
和.tbss
节,并用当前线程的本地.tdata
指针加载tp
.
要在所有线程
间共享全局
,需要按不变或共享
标记.标记为共享
的变量
会有些限制,基本上强制标记
与它相关的都为共享
.仍可不检查
就读取/写入
它,但至少应知道是否正在访问共享变量
(并手动验证
是否同步
).
在D的未来
版本中,除非通过原子
内部函数,否则可能禁止
直接访问共享变量
.如果有锁
来保护变量,那么需要手动
丢弃共享
限定符,这并不完美,但强制承认访问共享全局
可能不安全.
总是可将__gshared
属性用作逃生通道
,这改变共享
为全局
,但不更改类型(无限制).__gshared
的全局
等价于C全局.
相关文章:
用D写裸机
原文 用D编写裸机RISC-V应用 这篇文章展示,如何用D编写,目标为RISC-VQEMU模拟器的程序裸机"你好".项目 为什么是D? 我最近一直在用C编写裸机代码,我有点对C缺乏特征感到沮丧.D引入了叫betterC的模式(基本上禁止了D运行时的所有语言功能).使得D裸机编程大致与C一…...

(二十五)、实现评论功能(5)【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】
1,实现二级回复的入库操作 1.1 两个子组件(comment-item和comment-frame)与父组件reply之间的属性传值 comment-item: props: {item: {type: Object,default () {return {}}}},comment-frame: props: {commentObj: {…...

【概念辨析】二维数组传参的几种可能性
一、二维数组传参竟然不是用二级指针进行接收? 今天进行再一次的二级指针学习时,发现了一条以前没怎么注意过的知识点:二维数组进行传参只能用二维数组(不能省略列)进行接收或者是数组指针。 问题复现代码如下…...

python和C++代码实现图片九宫格切图程序(附VS2015配置Opencv教程)
1、python代码实现图片分割成九宫格 需要包含的库,没有下载安装的,需要自己安装哦。 实现原理很简单,就是用PIL库不断画小区域,切下来存储成新的小图片。 假设每一个格子的宽和高分别是w、h,那么第row行(…...

【深度学习】优化器
1.什么是优化器 优化器是在深度学习的反向传播过程中,指引损失函数(目标函数)的各个参数往正确的方向更新合适的大小,使得更新后的各个参数让目标函数不断逼近全局最小点。 2.优化器 2-1 BGD 批量梯度下降法,是梯度下…...

SpringBoot使用validator进行参数校验
Validated、Valid和BindingResultBean Validation是Java定义的一套基于注解的数据校验规范,比如Null、NotNull、Pattern等,它们位于 javax.validation.constraints这个包下。hibernate validator是对这个规范的实现,并增加了一些其他校验注解…...

论文复现:风电、光伏与抽水蓄能电站互补调度运行(MATLAB-Yalmip全代码)
论文复现:风电、光伏与抽水蓄能电站互补调度运行(MATLAB-Yalmip全代码) 针对风电、光伏与抽水蓄能站互补运行的问题,已有大量通过启发式算法寻优的案例,但工程上更注重实用性和普适性。Yalmip工具箱则是一种基于MATLAB平台的优化软件工具箱,被广泛应用于工程界优化问题和…...

FastCGI sent in stderr: "PHP message: PHP Fatal error
服务器php7.2卸载安装7.4之后,打开网站一直无法访问,查看nginx错误日志发现一直报这个错误:2023/02/23 11:12:55 [error] 4735#0: *21 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught ReflectionException: Class translator does not exist in …...
【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)
文章目录 一、什么是跨时钟域?二、跨时钟域传输的问题?2、1 亚稳态(单bit:两级D触发器(双DFF))2、2 数据收敛(多bit亚稳态)(格雷码编码、握手协议、异步FIFO、DMUX)2、3 多路扇出:(先同步后扇出)2、4 数据丢失(延长输入数据信号):类似脉冲展宽2、5 异步复位(…...
UNI-APP学习
uni-app的基本使用 uni-app介绍 官方网页 uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。 即使不跨端…...

编译原理【运行时环境】—什么是活动记录、 活动记录与汇编代码的关系
系列文章戳这里👇 什么是上下文无关文法、最左推导和最右推导如何判断二义文法及消除文法二义性何时需要消除左递归什么是句柄、什么是自上而下、自下而上分析什么是LL(1)、LR(0)、LR(1)文法、LR分析表LR(0)、SLR(1)、LR(1)、LALR(1)文法之间的关系编译原理第三章习…...

【Windows Server 2019】发布服务器 | 远程桌面服务的安装与配置 Ⅰ——理论,实验拓扑和安装基于RemoteAPP的RDS
目录1. 理论1.1 什么是远程桌面服务2. 实验拓扑2.1 拓扑说明3. 安装基于RemoteAPP的RDS1. 理论 1.1 什么是远程桌面服务 远程桌面服务 (RDS) 是一个卓越的平台,可以生成虚拟化解决方案来满足每个最终客户的需求,包括交付独立的虚拟化应用程序、提供安全…...

Bootstrap入门到精通(最全最详细)
文章目录前言一、Bootstrap是什么?二、Bootstrap安装方式一:将压缩包下载到本地引入使用方式二:使用Bootstrap官方cdn二.Bootstrap容器下面是屏幕宽度在不同大小时不同容器的显示状态三.Bootstrap栅格系统bootstrap网格系统有以下六个类网格系…...

C/C++每日一练(20230223)
目录 1. 数据合并 2. 回文链表 3. 完美矩形 1. 数据合并 题目描述 将两个从小到大排列的一维数组 (维长分别为 m,n , 其中 m,n≤100) 仍按从小到大的排列顺序合并到一个新的一维数组中,输出新的数组. 输入描述 第 1 行一个正整数 m , 表示第一个要合并的一维…...
c语言中const 是什么意思?(面试)
const关键字使用非常的灵活,在c中,const因位置不同有不同的作用,因情景不同有不同的角色,使用起来也是非常的灵活。 可以定义const常量,具有不可变性。 例如:const int Max100; Max会产生错误; 便于进行类…...

网络工程(三)ensp配置静态路由
配置静态路由 这里选择的路由器是AR2220 因为有三个GE接口 下面说拓扑图 一、定义AR路由ip地址和下一条 AR1system-viewsysname AR1interface g0/0/0ip address 10.0.0.254 8interface g0/0/1ip address 50.0.0.1 8下一条代码[AR1]ip route-static 0.0.0.0 0 50.0.0.2AR2 s…...

深入浅出C++ ——手撕红黑树
文章目录一、红黑树的概念二、红黑树的性质三、红黑树节点的定义四、红黑树的插入操作五、红黑树的验证五、红黑树的删除六、红黑树与AVL树的比较七、红黑树的应用八、红黑树模拟实现一、红黑树的概念 红黑树,是一种二叉搜索树,但在每个结点上增加一个存…...

Linux服务:Nginx服务重写功能
目录 一、重写功能 1、重写功能作用 2、rewrite指令 ①if指令 ②return指令 ③ set指令 ④break指令 3、rewrite标志 ①redirect标志 ②permanent标志 ③break标志 ④last标志 ⑤rewrite标志实验 一、重写功能 1、重写功能作用 重写功能(rewrite)用于实现URL的重…...

3.知识图谱概念和相关技术简介[知识抽取、知识融合、知识推理方法简述],典型应用案例介绍国内落地产品介绍。一份完整的入门指南,带你快速掌握KG知识,芜湖起飞!
1. 知识图谱(KG)的概念 知识图谱(KG)得益于Web的发展(更多的是数据层面),有着来源于KR、NLP、Web、AI多个方面的基因。知识图谱是2012年后的提法,基础还是语义网和本体论。 知识图谱的本质包含: 知识表示——Knowledge Representation基于知识表示的知识库——Knowledge…...

iOS 绿幕技术
绿幕(green screen)技术,又称 chroma key effect,实际上是将图片上指定颜色设置为透明的图形处理技术,这些透明区域也可以被任意背景图片替换。 这种技术在 视频合成中被广泛使用。iOS 中,通过 CoreImage …...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...