NEMU(RISC-V64)基础知识(一)
目录
1、术语和定义
2、CISC和RISC的区别
3、vimtutor指令查看常见VIM使用命令
4、GDB调试
5、x86中寄存器
6、x86中指令的具体行为
7、中断和异常
8、RISC-V的中断
9、一条指令在NEMU中的执行过程
10、NEMU中的输入输出
11、在NEMU的运行时环境中执行程序步骤
12、NEMU中的DiffTest(Differential Testing,差异测试)
13、NEMU实现RISC-V指令
1、术语和定义
- 计算机(状态机模型视角):
- 计算机根据当前时序逻辑部件(存储器、计数器、寄存器等)的状态,在组合逻辑部件(加法器等)的作用下,计算并转移到下一时钟周期的新状态
- 程序运行过程:每执行完一条指令,进行一次确定的状态转移
- 指令:计算机进行一次状态转移的输入激励
术语 |
全称 |
定义 |
ISA |
Instruction Set Architecture |
指令集架构,又称指令集或指令集体系,是计算机体系结构中与程序设计有关的部分,包含了基本数据类型,指令集,寄存器,寻址模式,存储体系,中断,异常处理以及外部I/O |
MIPS |
Microprocessor without Interlocked Pipeline Stages |
MIPS指令集,指令长度为32位,有32个寄存器,运算只能存在于寄存器之间 (1)R型指令:op + rs + rt + rd + shamt + funct rs/rt是源操作数所在寄存器号,rd是目标操作数所在寄存器号,shamt是位移量,funct是功能 (2)I型指令:op + rs + rt + constant or address rs是源操作数,rt是目标操作数所在寄存器号,constant or address是第二个源操作数 (3)J型指令:op + address address是转移地址(26位) |
CISC |
Complex Instruction Set Computer |
复杂指令系统计算机,采用复杂指令实现软件功能的硬化。如x86架构 |
RISC |
Reduced Instruction Set Computer |
精简指令系统计算机,简化单条指令功能,复杂指令的功能由简单指令的组合来实现。尽量使用寄存器-寄存器操作指令,指令格式力求一致。如ARM、MIPS架构 |
riscv |
RISC-V |
第五代精简指令集,开放指令生态 |
x86 |
Intel x86 Architecture |
含有8个32位通用寄存器 |
TRM |
TuRing Machine |
图灵机,有存储器、PC、寄存器、加法器,重复(1)从PC指示的存储器位置取出指令,(2)执行指令,(3)更新PC的过程。 |
SoC |
System of Chip |
系统级芯片,是产品级别,包含完整系统并有嵌入软件的全部内容 |
RTL |
Register Transfer Level |
寄存器传输级语言,不关注寄存器和组合逻辑的细节,通过描述寄存器到寄存器之间的逻辑功能描述电路的HDL层次 |
GPR |
General Purpose Register |
通用寄存器 |
2、CISC和RISC的区别
CISC |
RISC |
|
指令系统 |
复杂,庞大 |
简单,精简 |
指令数目 |
一般大于200条 |
一般小于100条 |
指令字长 |
不固定 |
定长 |
可访存指令 |
不加限制 |
只有Load/Store指令 |
指令执行时间 |
相差较大 |
绝大多数在一个时钟周期内完成 |
指令使用频度 |
相差很大 |
都比较常用 |
通用寄存器数量 |
较少 |
多 |
目标代码 |
难以用优化编译生成高效的目标代码程序 |
采用优化的编译程序,生成代码较为高效 |
控制方式 |
绝大多数为微程序控制 |
绝大多数为组合逻辑控制 |
指令流水线 |
可以通过一定方式实现 |
必须实现 |
3、vimtutor指令查看常见VIM使用命令
(1)hjkl:分别表示←、↓、↑、→移动
(2)$:指针到达行尾,0:返回到行首w:指针到达下一个词的词首,e:到达当前词的词尾
(3)dd:删除当前行,x:删除当前光标所指字符,dw:删除从光标到下一个单词之间的字符,d$:删除从光标起到该行结束。u:撤回操作,U:撤回整行的操作
(4)r[某字符]:将光标处字符替换为“某字符”
(5)G:到达文档尾部,gg:到达文档首部
(6)先输入冒号,然后有多种指令可以额外设置:
- set number:显示行号
- set cursorline:突出显示当前行
- set hlsearch:高亮显示搜索到的文本
4、GDB调试
g++ hello.cpp -g -Wall -o helloworld:-Wall表示在编译时启用所有警告,-Werror表示将警告视为错误
- break line:设置第line行的断点
- delete line:删除第line行的断点
- run:开始调试
- step:执行下一行
- continue:继续执行
- watch xx:查看变量xx
5、x86中寄存器
- x86处理器有8个32位的通用寄存器。E表示Extended的32位寄存器,EAX的低两位字节为AX,AX的高字节为AH,低字节为AL。
- 汇编格式区别:
- x86汇编格式中,第一个为目的操作数,第二个为源操作数,方向从右向左。AT&T,第一个为源操作数,第二个为目的操作数,方向从左向右。
- AT&T汇编格式,寄存器需要加前缀%,立即数需要加前缀$;x86不用。
- AT&T内存寻址用(),x86用[]
6、x86中指令的具体行为
(1)NEMU中的实现:
instruction_prefix |
指令前缀,0或1字节 |
operand_size prefix |
操作数宽度前缀,指示当前指令需要改变的操作数的宽度 |
opcode |
操作码,1或2字节,操作码第二字节为\r表示后面跟着一个ModR/M字节,且ModR/M字节中reg/opcode域解释为通用寄存器的编码,用来表示一个操作数 |
ModR/M |
操作码的补充码,0或1字节,用于确定指令操作数,R/M表示寄存器或内存,为低三位,Reg/Opcode为中三位,Mod取值为3表示为寄存器,否则为内存,为高二位,Opcode决定是否有ModR/M |
SIB |
对ModR/M的补充,0或1字节,包括Scale,Index,Base,ModR/M决定是否有SIB |
displacement |
地址偏移量,0,1,2,4字节 |
immediate |
立即数,0,1,2,4字节 |
address_size prefix(NEMU不用到) |
|
segment override(NEMU不用到) |
(2)指令示例:
Opcode |
Instruction |
描述 |
88 /r |
mov r/m8, r8 |
/r表示后面跟着一个ModR/M字节,且ModR/M字节中reg/opcode域解释为通用寄存器的编码,用来表示一个操作数,r/m8表示八位寄存器或内存,r8表示8位寄存器。指令表示传送一个8位寄存器的操作数到8位寄存器或内存 |
89 /r |
mov r/m32, r32 |
指令表示传送一个32位寄存器的双字操作数到32位寄存器或内存 |
B8 + rd id |
mov r32 imm32 |
+rd表示32位通用寄存器的编码,id表示32位立即数,指令表示传送一个32位立即数到32位寄存器中 |
A3 |
mov moffs32, eax |
moffs32表示32位的段内偏移量。指令表示将eax寄存器的内容传送到moffs32段内偏移量的地址 |
7、中断和异常
(1)中断和异常:
- 异常(内中断):当前指令执行过程中进行检测。CPU执行指令时,由CPU在其内部检测到的、与正在执行的指令相关的同步事件,分为故障(Fault)、陷阱(Trap)和终止(Abort)三类
- 同步:产生原因能够被精确定位于某条指令
- 中断(外中断):当前指令执行后进行检测。由外部设备触发的、与当前正在执行的指令无关的异步事件
- 异步:产生原因不能被精确定位于某条指令
(2)异常分类:
- 硬件中断:
- 终止(Abort):指令执行中出现硬件故障,如控制器出错,越权,越级,栈溢出,存储器校验中断,主存故障
- 外中断:ctrl+c、打印机缺纸
- 软件中断:
- 故障(Fault):指令启动后、执行结束前被检测到的异常事件,如缺页、缺段、出现非法操作码、整除0
- 自陷(Trap):人为设定,在程序中用一条特殊指令或通过某种方式设定特殊控制标志来人为设置一个“陷入”,当执行到“陷入”指令时,CPU进入操作系统系统内核程序执行,完成相应“陷入”类型的处理,再执行自陷指令的下一条指令
- 自陷可用于实现程序调试时的断点设置和单步跟踪
(3)中断分类:
- 可屏蔽中断:通过INTR(可屏蔽中断请求线)向CPU发送中断请求信号,可以通过在中断控制器中设置相应的屏蔽字进行屏蔽中断请求信号
- 不可屏蔽中断:通过NMI(不可屏蔽中断请求线)向CPU发送中断请求信号,不可被屏蔽
(4)异常和中断响应过程:
- 关中断:中断允许触发器(IF)实现,IF=1为开中断,表示允许响应中断,IF=0为关中断,表示不允许响应中断
- 保存断点和程序状态:将程序的断点(返回地址)和程序状态字寄存器(PSWR,Program Status Word Register)保存到栈或特定寄存器
- 识别异常和中断:
- 软件识别:异常状态寄存器记录异常原因
- 硬件识别(向量中断):异常或中断处理程序(ISR,Interrupt Service Routine)的首地址为中断向量,所有中断向量存放在中断向量表中
- 转到中断处理程序
(5)中断仲裁:存在多个中断优先级的中断源向处理器发起请求,处理器对中断源进行仲裁,划分处理顺序
- RISC-V中存在四种中断类型:外部中断(External Interrupt)、计时器中断(Timer Interrupt)、软件中断(Software Interrupt)、调试中断(Debug Interrupt),中断优先级由高到低分别为:
- 外部中断
- 软件中断
- 计时器中断
- 调试中断只有调试器介入调试时才发生
(6)中断嵌套:前一个中断还没响应完,又开始响应新的中断
- RISC-V硬件无法支持硬件中断嵌套行为,因为响应中断进入异常模式后,中断被全局关闭,无法响应新中断:
- 进入异常后,mstatus寄存器的MIE域会被硬件自动更新为0,表示中断被全局关闭,无法响应新的中断
- 退出中断后,MIE域才被硬件根据MPIE域的值恢复成中断发生之前的值,从而再次全局打开中断嵌套
- 可以通过软件方式,在程序跳入中断服务程序后,强行修改mstatus寄存器的值,将MIE域置1,开启全局中断
8、RISC-V的中断
(1)进入异常:在目录ics2021/nemu/src/isa/riscv64/include中的isa-def.h文件的riscv64_CPU_state结构体定义CSR寄存器,当进入异常时,停止执行当前程序流,从mtvec寄存器定义的PC地址开始执行,同时更新mcause、mepc、mtval、mstatus寄存器
- mtvec(Machine Trap-Vector Base-Address Register):机器模式异常入口基址寄存器,trap后进入异常的程序PC地址
- mcause(Machine Cause Register):机器模式异常原因寄存器,定义了异常种类,包括高1位Interrupt域,低31位异常编号域,异常编号决定进一步跳转到具体的异常服务程序
- mepc(Machine Exception Program Counter):机器模式异常PC寄存器,定义了异常返回地址,在进入异常时,硬件将自动更新mepc寄存器的值为当前遇到异常的指令PC值
- mtval(Machine Trap Value Reisger):机器模式异常值寄存器,反映引起异常的信息,值为当前异常的存储器访问地址或者指令编码
- mstatus(Machine Status Register):机器模式状态寄存器,其中MIE域表示在机器模式下控制中断全局使能,MPIE域的值更新为异常发生前MIE域的值,MPP域在异常结束之后,能够使用MPP的值恢复出异常发生之前的工作模式
(2)退出异常:
- mret指令停止执行当前程序流,从CSR寄存器mpec定义的PC地址开始执行,rtl_j(s, cpu.mepc),并更新mstatus寄存器的某些域,MIE域的值更新为当前MPIE域的值,MPIE域的值更新为1
- RISC-V规定的进入异常和退出异常机制中没有硬件保存和恢复上下文的操作,因此需要软件明确地使用指令进行上下文的保存和恢复
9、一条指令在NEMU中的执行过程
- 取指:通过cpu_exec函数获取指令,decode.c文件中的isa_fetch_decode函数中调用了在ifetch.h头文件中定义的instr_fetch函数,该函数实现了从Decode结构体获得当前pc指向的指令,获得指令的opcode
- 译码:首先执行cpu-exec.c文件的fetch_decode_exec_updatepc函数进行指令译码和更新pc的操作,实际调用fetch_decode函数。decode.c文件中的isa_fetch_decode函数int idx = table_main(s)实现了查表功能,def_INSTR_TAB宏定义了具体的译码工作,在riscv32中NEMU主要实现了I型指令、S型指令和U型指令。同时借助译码辅助函数和译码操作数辅助函数得到具体的操作对象。译码出来的结果作为查表操作,返回标识该指令的唯一ID
- 执行:根据返回的idx调用对应的执行辅助函数def_EHelper,根据nemu_state.state的状态码判断是否执行成功,成功则输出“HIT GOOD TRAP”用户提示符
- 更新pc:ifetch.h头文件中定义了instr_fetch函数,执行指令后,通过s->dnpc的赋值来进行更新pc的操作
10、NEMU中的输入输出
- 访问设备 = 读出数据 + 写入数据 + 控制状态
- 端口I/O(Port-Mapped I/O):设备寄存器地址进行编号,CPU用专门的I/O指令映射设备端口号对设备进行访问
- x86中,in指令将设备寄存器中的数据传输到CPU寄存器,out指令将CPU寄存器中的数据传输到设备寄存器
- 内存映射I/O(Memory-Mapped I/O,MMIO):通过不同的物理内存地址给设备编址,将一部分物理内存的访问重定向到I/O地址空间中
- 控制状态本质上是对读/写设备寄存器的操作
- 设备的输入输出通过CPU寄存器进行数据交互,输入输出对程序的影响仅仅体现在输入时会进行一次不能提前确定的状态转移
11、在NEMU的运行时环境中执行程序步骤
- 步骤一:gcc编译AM(与架构相关的运行时环境)下的源文件为目标文件,通过Makefile文件中的ar命令将目标文件打包为静态链接库am-riscv64-nemu.a文件
- 步骤二:在Makefile文件中定义gcc、g++等交叉编译,将程序源码编译成目标文件
- 步骤三:在Makefile文件通过gcc和ar将程序依赖的运行库klib(kernel library,封装了与结构无关的库函数)等编译为静态链接文件
- 步骤四:根据Makefile文件riscv64-nemu.mk文件的指示,ld链接脚本linker.ld将上述目标文件和静态链接文件链接成为可执行文件(LD各ELF文件,executable and linkable format,跨平台的标准文件格式)
- 步骤五:通过汇编文件start.S定义了栈顶元素,主程序入口,跳转到trm.c文件的_trm_init()函数执行,进行TRM相关的初始化工作
- 步骤六:执行_trm_init()函数中的main()函数执行程序的主体功能
- 步骤七:程序执行结束,则调用halt()函数结束运行
- 附注:文件位置如下:
- Makefile:/home/Dengqy/ics2021/abstract-machine/Makefile
- am-riscv64-nemu.a:/home/Dengqy/ics2021/abstract-machine/am/build/am-riscv64-nemu.a
- riscv64-nemu.mk:/home/Dengqy/ics2021/abstract-machine/scripts/riscv64-nemu.mk
- linker.ld:/home/Dengqy/ics2021/abstract-machine/scripts/linker.ld
- start.S:/home/Dengqy/ics2021/abstract-machine/am/src/riscv/nemu/start.S
- trm.c:/home/Dengqy/ics2021/abstract-machine/am/src/platform/nemu/trm.c
12、NEMU中的DiffTest(Differential Testing,差异测试)
(1)定义:提供一个和DUT(Design Under Test,测试对象)功能相同但实现方式不同的REF,二者接受相同有定义的输入,观测它们的行为是否相同。
(2)在NEMU中检查执行每一条指令后DUT和REF(其他的全真系统模拟器)的状态(二元组S=<R, M>)是否一致,定义的API:
- difftest_regcpy:获取及设置REF的寄存器状态到DUT
- dlsym函数:根据动态链接库操作句柄与符号,返回符号对应的地址
- difftest_memcpy:在DUT host memory的buf和REF guest memory的dest间拷贝n个字节
- difftest_exec:REF执行n条指令
- difftest_step:让REF执行和nemu相同的指令,然后独处REF中的寄存器,进行对比
- difftest_checkregs:把通用寄存器和PC从DUT中读出的寄存器的值进行比较,并返回布尔值
- difftest_skip_dut:跳过dut的指令检查,因为dut的单步执行可能会执行多条指令
- difftest_skip_ref:ref跳过无法与nemu产生一致行为的指令
13、NEMU实现RISC-V指令
(1)RISC-V定义了32个通用寄存器,其中x0寄存器值恒为0,可使用x0寄存器作为操作数,来完成相同功能的操作。指令长度固定,为32位。共有六种基本指令。
- 字长(word length):ALU正常处理的信息量的大小
- 每一个元素被称为一个字(word)
- rd为目的操作寄存器(destination register),指定保存执行结果的寄存器
- opcode为操作码,区分指令的类别
- funct3和funct7分别为区分某类指令下的具体指令的三位功能码和七位功能码
- rs1是第一个源操作数寄存器(source register),rs2为第二个源操作数寄存器
- imm为立即数(immediate)
(2)寄存器:
寄存器 |
ABI名称 |
说明 |
x0 |
zero |
0值寄存器,硬编码为0,写入数据忽略,读取数据为0 |
x1 |
ra |
用于返回地址(return address) |
x2 |
sp |
用于栈指针(stack pointer) |
x3 |
gp |
用于通用指针(global pointer) |
x4 |
tp |
用于线程指针(thread pointer) |
x5 |
t0 |
存放临时数据或备用链接寄存器 |
x6-x7 |
t1-t2 |
存放临时数据寄存器 |
x8 |
s0/fp |
需要保存的寄存器或帧指针寄存器 |
x9 |
s1 |
需要保存的寄存器 |
x10-x11 |
a0-a1 |
函数传递参数寄存器或函数返回值寄存器 |
x12-x17 |
a2-a7 |
函数传递参数寄存器 |
x18-x27 |
s2-s11 |
需要保存的寄存器 |
x28-x31 |
t3-t6 |
存放临时数据寄存器 |
PC:RISC-V当前PC值即当前指令的地址
(3)不同类型指令解析
- rs1是第一个源操作数寄存器(source register),rs2为第二个源操作数寄存器
- imm为立即数(immediate)
- R型指令:定义了“寄存器-寄存器”的运算指令
- opcode:0110011
- funct3和funct7:add为0000000、000;sub为0100000、000;sll为0000000、001;slt为0000000、010;sltu为0000000、011;xor为0000000、100;srl为0000000、101;sra为0100000、101;or为0000000、110;and为0000000、111。
- 包括指令:add、sub、sll(逻辑左移,shift left logical)、srl(逻辑右移,shift right logical)、sra(算术右移,shift right arithmetic)、slt(三元运算符,set less than)、sltu(无符号比较三元运算符,set less than unsigned)、xor(异或,exclusive or)、or(或)、and(与)
- op rd, rs1, rs2,即rd ← (rs1) op (rs2)
- I型指令:定义了短立即数-寄存器指令和读内存load指令
- 短立即数-寄存器指令
- opcode:0010011
- funct3:addi为000、slti为010、sltiu为011、xori为100、ori为110、andi为111、slli为001、srli为101、srai为101
- funct7:slli为0000000、srli为0000000、srai为0100000
- 包括指令:addi、slti、sltiu、xori、ori、andi、slli、srli、srai
- op rd, rs1, imm,即rd ← (rs1) op imm
- 读内存load指令,从存储器读出数据装入寄存器
- opcode:0000011
- funct3:lb为000,lbu为100,lh为001,lhu为101,lw为010
- 包括指令:lb(将值作为地址,取出该地址所对应的内存中的8位数值)、lh(内存中取出16位数值)、lw(内存中取出一个字,即32位数值)、lbu(内存中取出8位无符号数值)、lhu(内存中取出16位无符号数值)
- op rd, rs1, imm,即rd ← M[(rs1) + imm]
- 短立即数-寄存器指令
- S型指令:访存写入Sotre(byte、halfword、word),将寄存器的数据存入存储器
- opcode:0100011
- funct3:sb为000,sh为001,sw为010
- 包括指令:sb(写入低8位)、sh(写入低16位)、sw(写入低32位)
- op rs2, imm(rs1),即(rs2) → M[imm + (rs1)]
- B型指令:定义了分支转移,条件满足,(PC) + imm → PC,否则(PC) + 4 → PC
- opcode:1100011
- func3:beq为000,bne为001,blt为100,bge为101,bltu为110,bgeu为111
- 包括指令:beq(a1==a2则跳转,branch on equality)、bne(a1!=a2则跳转,branch on not equality)、blt(a1<a2则跳转,branch on less than)、bge(a1≥a2,branch on greater than or equal)、bltu(branch on less than unsigned)、bgeu(branch on greater than or equal unsigned)
- U型指令:定义了长立即数
- opcode:lui为0110111,auipc为0010111
- 包括指令:lui(得到立即数的高二十位,低位补零,装入寄存器,load upper immediate)、auipc(组装32位相对偏移地址,得到立即数的高二十位,低位补零,与PC值相加,装入寄存器,add upper immediate to pc)
- J型指令:定义了无条件跳转,过程调用和返回
- opcode:jal为1101111,jalr为1100111
- funct3:jalr为000
- 包括指令:过程调用jal(jal x1, label:将下一条指令的地址(返回地址)保存到x1寄存器,程序跳转到label,jump and link)、返回jalr(跳转到存储在x1中的地址,jump and link register)
- jalr:rd ← PC + 4, PC ← (rs1) + imm,jal:pc ← (PC) + imm
NEMU(RISC-V64)基础知识(一)相关推荐
- 现代计算机入门知识,计算机基础知识
基础知识 1.计算机系统的组成如下图所示: 2.现代计算机的5大基本部件: 运算器.控制器.存储器.输入设备.输出设备 现在我们把运算器和控制器统称为CPU(central processing un ...
- python历史以及基础知识
1. Python 基础知识 1.1 Python 历史 1.1.1 Python 起源 Python 的作者,Guido von Rossum,荷兰人.1982 年,Guido 从阿姆斯特丹大学 获 ...
- 重磅:服务器基础知识全解终极版(145页PPT)
重磅:服务器基础知识全解终极版(145页PPT) 2020-12-26 阅 1 转 19 终极版来啦,本文内容共145页PPT干货,针对历史发布内容在CPU.内存.GPU.硬盘.网卡等9个章节做 ...
- Linux操作系统基础知识学习
Q1.什么是GNU?Linux与GNU有什么关系? A: 1)GNU是GNU is Not Unix的递归缩写,是自由软件基金会(Free Software Foundation,FSF)的一个项目, ...
- 数字系统设计的基础知识
不论是学习Verilog HDL,还是学习数字系统设计,本文的知识都是必要的. 数字系统的基础知识 进制 二进制 编码 布尔代数 布尔表达式 组合逻辑电路 时序逻辑电路 时序机 功能器件 存储器件 逻 ...
- 【软考】《希赛教育·软件设计师考前冲刺与考点分析》计算机硬件基础知识——学习笔记
Content 第1章 计算机硬件基础知识 第2章 操作系统基础知识 第3章 程序语言和语言处理程序基础知识 第4章 数据结构 第5章 数据库系统基础知识 第6章 网络基础知识 第7章 软件工程基础知 ...
- IT行业最全的服务器硬件基础知识大全,值得收藏!
服务器硬件基础知识 高度计量单位 容量计量单位 速率单位 计算单位和峰值 端口自协商 服务器主要软件 服务器标准 服务器的逻辑结构 处理器缓存 内存频率 系统启动方式 主板南北桥区别 交换与路由 堆叠 ...
- 详解服务器异构计算FPGA基础知识
随着云计算,大数据和人工智能技术应用,单靠CPU已经无法满足各行各业的算力需求.海量数据分析.机器学习和边缘计算等场景需要计算架构多样化,需要不同的处理器架构和GPU,NPU和FPGA等异构计算技术协 ...
- 硬件工程师基础知识(http://huarm.taobao.com/ )
硬件工程师基础知识 1. 请列举您知道的电阻.电容.电感品牌(最好包括国内.国外品牌). 电阻: 美国:AVX.VISHAY威世 日本:KOA兴亚.Kyocera京瓷.muRata村田.Pan ...
- 计算机组成原理基础知识试题及答案,[电脑基础知识]计算机组成原理试题库.doc...
[电脑基础知识]计算机组成原理试题库.doc 计算机组成原理练习题一. 单项选择题CPU响应中断的时间是. A中断源提出请求: B取指周期结束: C执行周期结束: D间址周期结束. 2下列说法中是正确 ...
最新文章
- Python快速入门(1)
- ultraEdit-32 PHP/HTML智能提示
- 用户 NT AUTHORITY\NETWORK SERVICE 登录失败解决方法
- 要闻君说:台积电将为iPhone生产5纳米A系列芯片?腾讯云TStack与银河麒麟完成互认证……...
- PHPCMS v9里面,推荐位ID【posid】的值是如何确定的?是自定义的还是官方定义好的?...
- STM32工作笔记002---STM32初探-概述
- php怎样空格分开输入三个数,php函数在每一空行拆分一个数组?
- 《软件设计精要与模式》书评
- pdf如何去除保护限制,pdf复制打印限制怎么解除?
- 有关并联机器人动力学的学习
- html css纯写桌球运动轨迹,纯JS实现椭圆轨迹运动的代码
- html边界填充边框,CSS边界与填充
- 央企整体上市进程加快 掘金央企重组股
- 重磅!厦门大学信息学院11篇论文入选AI顶会AAAI 2021
- iOS开发实战细节——通知写法
- xdocreport根据模板生成合同(docx/pdf)神器:(三)如何制作报告模板并根据它生成docx或者ppt
- SAP 用户没有下载数据到本地的权限
- 撒娇吧使大家啊让你撒何地什么科的你
- springboot班级同学录网站
- 阿松嘚嘚嘚-数据库篇1-数据库市场有学问