用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 …...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
用 FFmpeg 实现 RTMP 推流直播
RTMP(Real-Time Messaging Protocol) 是直播行业中常用的传输协议。 一般来说,直播服务商会给你: ✅ 一个 RTMP 推流地址(你推视频上去) ✅ 一个 HLS 或 FLV 拉流地址(观众观看用)…...
