当前位置: 首页 > news >正文

用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,ClangGCC编译器分别都导出了大致相同的标志和内部函数.
这些功能,再加上无运行时类C感觉语言(容易移植以前代码),使我选择D作为C的替代.

安装工具链

试用它来编写面向RISC-V的裸机应用.第一步是下载工具链(以下工具应该在LinuxMacOS上工作).需要三个不同的组件:

1,LDC1.30(基于LLVM的D编译器).可从GitHub这里下载.确保用1.30版.
2,GNUriscv64-unknown-elf工具链.可从这里下载.

3,QEMURISC-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后,调用在下一节定义的叫dstartD函数.

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>;
};

表明RAM0x80000000地址开始(以下则是特殊内存或无法访问).虚机的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,noreturnuintptr.

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协议传输该字节.
为了用主机读字节,需要主机上插入UARTUSB的适配器,然后可从主机上相应设备文件(一般/dev/ttyUSB0)中读取.

今天模拟在QEMU中的裸机代码,所以不需要特殊的适配器.QEMU模拟UART设备并打印出写至其传输寄存器字节.

启用易失性加载/存储

写入设备内存时,确保编译器不会删除加载/存储非常重要.如,如果设备放在0x10000000,可转换整数为指针来直接写入该地址.对编译器,只是写入可能是未定义行为或导致死码随机地址(如,如果从未读回该值,编译器可能会确定可消除写入).需要通知编译器,必须保留读/写设备内存,且不能优化.D为此用了volatileStorevolatileLoad内部函数.
可在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

安装后,要弄清楚QEMUUART设备在内存何处,以便可写入它.
QEMU虚机器定义了许多虚设备,就有UART设备.在virt.dts中再次浏览QEMU设备树,如下:

uart@10000000 {interrupts = <0x0a>;interrupt-parent = <0x03>;clock-frequency = <0x384000>;reg = <0x00 0x10000000 0x00 0x100>;compatible = "ns16550a";
};

表示0x10000000地址上有ns16550aUART设备
实际硬件上,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,+relaxldc2调用中,以在编译器中启用链接器松弛.

删除未用的函数

查看程序的(makeprog.list)反汇编,注意到有些函数永远不会调用.这是因为已内联这些函数,但未删除定义.即使标记为私有,D总是导出函数/全局变量目标文件中.幸好,现代链接器非常聪明,且很容易让链接器删除这些未用函数.传递--function-sections--data-sectionsLDC,让它在自己的节中(仍在.text,.data等中)放每个函数/全局.

现在,如果传递--gc-sections标志给链接器,它删除未引用节(从而删除未用的函数/全局变量).有了这些标志,最终的"你好"二进制文件,降到了160字节.

这是链接器优化的基本形式.还有更高级的(LTO)链接时间优化.如果传递-flto=thin-flto=fullLDC,则它生成的目标文件将是LLVM位码.然后,要用LLVMgold链接器插件(或用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&#xff0c;实现二级回复的入库操作 1.1 两个子组件&#xff08;comment-item和comment-frame&#xff09;与父组件reply之间的属性传值 comment-item&#xff1a; props: {item: {type: Object,default () {return {}}}},comment-frame&#xff1a; props: {commentObj: {…...

【概念辨析】二维数组传参的几种可能性

一、二维数组传参竟然不是用二级指针进行接收&#xff1f; 今天进行再一次的二级指针学习时&#xff0c;发现了一条以前没怎么注意过的知识点&#xff1a;二维数组进行传参只能用二维数组&#xff08;不能省略列&#xff09;进行接收或者是数组指针。 问题复现代码如下&#xf…...

python和C++代码实现图片九宫格切图程序(附VS2015配置Opencv教程)

1、python代码实现图片分割成九宫格 需要包含的库&#xff0c;没有下载安装的&#xff0c;需要自己安装哦。 实现原理很简单&#xff0c;就是用PIL库不断画小区域&#xff0c;切下来存储成新的小图片。 假设每一个格子的宽和高分别是w、h&#xff0c;那么第row行&#xff08…...

【深度学习】优化器

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

SpringBoot使用validator进行参数校验

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

论文复现:风电、光伏与抽水蓄能电站互补调度运行(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: &#xff02;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 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、H5、以及各种小程序&#xff08;微信/支付宝/百度/头条/QQ/钉钉&#xff09;等多个平台。 即使不跨端&#xf…...

编译原理【运行时环境】—什么是活动记录、 活动记录与汇编代码的关系

系列文章戳这里&#x1f447; 什么是上下文无关文法、最左推导和最右推导如何判断二义文法及消除文法二义性何时需要消除左递归什么是句柄、什么是自上而下、自下而上分析什么是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) 是一个卓越的平台&#xff0c;可以生成虚拟化解决方案来满足每个最终客户的需求&#xff0c;包括交付独立的虚拟化应用程序、提供安全…...

Bootstrap入门到精通(最全最详细)

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

C/C++每日一练(20230223)

目录 1. 数据合并 2. 回文链表 3. 完美矩形 1. 数据合并 题目描述 将两个从小到大排列的一维数组 (维长分别为 m,n , 其中 m,n≤100) 仍按从小到大的排列顺序合并到一个新的一维数组中&#xff0c;输出新的数组. 输入描述 第 1 行一个正整数 m , 表示第一个要合并的一维…...

c语言中const 是什么意思?(面试)

const关键字使用非常的灵活&#xff0c;在c中&#xff0c;const因位置不同有不同的作用&#xff0c;因情景不同有不同的角色&#xff0c;使用起来也是非常的灵活。 可以定义const常量&#xff0c;具有不可变性。 例如&#xff1a;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树的比较七、红黑树的应用八、红黑树模拟实现一、红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存…...

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 绿幕技术

绿幕&#xff08;green screen&#xff09;技术&#xff0c;又称 chroma key effect&#xff0c;实际上是将图片上指定颜色设置为透明的图形处理技术&#xff0c;这些透明区域也可以被任意背景图片替换。 这种技术在 视频合成中被广泛使用。iOS 中&#xff0c;通过 CoreImage …...

git 的使用方法(上 - 指令)

目录前言&#xff1a;一、Git 是什么&#xff1f;二、SVN与Git的最主要的区别&#xff1f;三、Git 安装四、git 配置1. 创建仓库 - repository2. 配置3. 工作流与基本操作五、Git 的使用流程1. 仓库中创建 1.txt文件2. 查看工作区的文件状态3. 添加工作区文件到暂存区4. 创建版…...

Windows 平台 oracle11g 单机 打补丁(33883353)

一、从oracle官网下载最新补丁包和打包工具 二、 对数据库及软件作全备 略 三、解压p33883353_112040_MSWIN-x86-64.zip 在33883353文件夹中打开README.html 2.1 OPatch Utility You must use the OPatch utility version 11.2.0.3.34 or later to apply this patch. 必须…...

1个寒假能学会多少网络安全技能?

现在可以看到很多标题都声称三个月内就可以转行网络安全领域&#xff0c;并且成为月入15K的网络工程师。那么&#xff0c;这个寒假的时间能学多少网络安全知识&#xff1f;是否能入门网络安全工程师呢&#xff1f; 答案是肯定的。 虽然网络完全知识是一门广泛的学科&#xff…...

六、肺癌检测-训练指标和数据增强

上一篇文章讲了训练过程和tensorboard可视化&#xff0c;这一篇文章记录下训练指标和数据增强的东西。 五、肺癌检测-数据集训练 training.py model.py_wxyczhyza的博客-CSDN博客 一、目标 1. 记录精度、召回率、F1分数 2. 样本均衡和样本随机化 3. 数据增强 二、要点 1…...

儿童饰品发夹发卡出口美国办理什么认证?

亚马逊美国站上传新产品&#xff0c;很多时候都是需要类目审核的&#xff0c;后台给出要求提供认证&#xff0c;产品类目不同&#xff0c;所需要提供的认证证书是不一样&#xff0c;儿童产品需要提交的是CPC认证&#xff0c;玩具&#xff0c;母婴用品&#xff0c;儿童书包&…...

Hive---Hive语法(一)

Hive语法&#xff08;一&#xff09; 文章目录Hive语法&#xff08;一&#xff09;Hive数据类型基本数据类型&#xff08;与SQL类似&#xff09;集合数据类型Hive数据结构数据库操作创建库使用库删除库表操作创建表指定分隔符默认分隔符&#xff08;可省略 row format&#xff…...

微信小程序日记、微信小程序个人空间、个人日记

一.简述 个人比较喜欢微信小程序&#xff0c;因为小程序所追求的用户体验、代码质量、美观的样式&#xff0c;简单方便丰富的api、样式封装等&#xff0c;同时又与普通的前端开发非常相似&#xff0c;让人很容易就上手。 这篇博客介绍的是一款记录个人/家庭日常记录的微信小程…...

CentOS 8利用Apache安装部署下载服务器

1&#xff1a;部署的目的是做一个类似下面开源镜像网站&#xff0c;把一些软件或者资料上传到服务器上面&#xff0c;减少用户在互联网上下载资料&#xff0c;提高效率&#xff0c;减少病毒。 2&#xff1a;使用下面的命令配置本机的IP地址主机名等信息。后期使用IP地址进行访问…...

【数据结构与算法】顺序表增删查改的实现(动态版本+文件操作)附源码

目录 一.前言 二.顺序表 1.概念及结构 2.顺序表结构体的定义 3.初始化顺序表&#xff0c;销毁顺序表和打印 3.接口 a.尾插 SepListpushback 头插 SepListpushfront b.尾删 SepListpopback 头删 SepListpopfront c.查询 SepListsearch d.修改 SepListmodify 三…...

【虹科】基于Lidar的体积监控实现高效的库存管理

迄今为止&#xff0c;很多物料厂家测量库存的结果数据仍然不准确&#xff0c;会存在很大的误差&#xff0c;导致供应链效率低下——这个问题可以通过Lidar技术轻松解决。近年来&#xff0c;全球供应链的脆弱性已经多次得到证明。无论是油轮被困在苏伊士运河&#xff0c;阻塞海峡…...