RISCV汇编编程讲解

第一章 引言

为什么要讲riscv?

         riscv的特点:

        -诞生于顶尖学术机构:诞生于加州大学伯克利分校的体系结构研究院。吸引了大批的顶尖企业参与(e.g. 谷歌、华为、高通、阿里巴巴为rsicv的发展提供了大量的资金支持和贡献了技术和人才)。

        -精简指令集:指令相对精简,指令格式规整,易于实现和理解。只包含了最基本和常用的指令,避免了复杂和特殊用途的指令,使得处理器的硬件设计更加简洁高效。比如说长度固定(32位/64位)。

        -模块化设计:可以根据不同的应用需求选择不同的模块进行组合。这使得riscv可以适应从嵌入式系统到高性能服务器等不同的应用场景,具有很高的灵活性。

        -开源:例如有多个开源的riscv编译器(如gcc和llvm)可供选择。

        -低功耗:指令集设计精简,处理器的硬件实现相对简单,有助于降低功耗。

命名规范

        RV[###][abc......xyz]。

        [###]表示的是寄存器的位宽,后面的字母标识处理器支持的指令集模块集合。

        例如:RV32MA、RV64GC。

模块化        

        模块化ISA vs 增量式ISA

        RISC-V ISA = 1个基本整数指令集 + 多个可选的扩展指令集。

        基本整数指令集:唯一强制要求实现的基础指令集,其他指令集都是可选的扩展模块。

        扩展模块指令集:RISC-V允许在视线中以可选的形式实现其他标准化和非标准化的指令集扩展。特定组合“IMAFD”被称为“通用”组合,用英文字母G表示。

扩展指令集描述
M整式乘法与除法指令集
A存储器原子指令集
F单精度浮点指令集
D双精度浮点指令集
C压缩指令集(16位长度)
......其它标准化和非标准化指令集
HART

        hart = hardware thread(个人理解为超线程)

        一个hart就是一个虚拟的cpu。一个hart可以不受外界干扰的自主地去获取和执行risc-v指令。

特权级别
levelencodingname
000user/applicationU
101supervisorS
210reserved
311machineM

        当运行在用户态时,就是说cpu运行在user级别,进入到内核的时候就是supervisor级别。固件就是就是machine级别。运行在machine模式是不开虚拟地址的,全部运行在物理地址。

        risc-v芯片一上电首先是进入到machine模式,再进入到supervisor模式,此时也叫保护模式,虚拟地址打开。

        进程的实现依赖于虚拟地址,虚拟地址的实现需要MMU硬件支持。

Control and Status Register(CSR)

        不同的特权级别下时分布对应各自的一套CSR,用于控制和获取响应level下的处理器工作状态。

        高级别的特权可以访问低级别的CSR。比如说machine级别可以访问user级别的csr。

        rsicv定义了专门用于操作csr的指令。

        如果是用户程序,不太需要跟csr打交道。

异常和中断

        异常:主动触发,cpu给你一次改过自新的机会,去执行一段挽救程序;当执行到非法指令的时候,cpu会停掉此指令流跳到一个特殊的地址去执行一段特殊的程序(自己写的程序,想对异常做的处理),执行完之后回到之前的指令再次运行;比如说除0异常;

        中断:被动触发;cpu停掉当前程序,跳转到执行中断处理程序,执行完返回到下一条指令去执行;比如说外设通知你发生一件什么事情,跑去执行别的指令,执行完再回到下条指令,就像中断没有发生过一样。

 ELF介绍

        ELF(Executable Format)是一种unix-like系统上的二进制文件格式标准。

        ELF标准中定义的采用ELF格式的文件分为4类:

ELF文件类型说明实例
可重定位文件(relocatable file)内容包含了代码和数据,可以被链接成可执行文件或共享目标文件linux上的.o文件
可执行文件可以直接执行的文件linux上的a.out文件
共享目标文件内容包含了代码和数据,可以作为链接器的输入,在链接阶段和其他的relacatable file或者shared object file一起链接成新的object file;或者在运行阶段,作为动态链接器的输入,和可执行文件结合,作为进程的一部分来运行。

linux上的.so文件

核心转储文件(core dump file)进行意外终止时,系统可以将该进程的部分内容和终止时的其他状态信息保存到该文件中以供调试分析。linux上的core文件

        ELF文件格式:

ELF Header
Program Header Table运行视图
.text程序指令
.init做初始化的一些指令
.data数据:全局变量等
.bss
......

Section Header Table

从链接角度去描述了这个文件的内容

        .text和.init等这些信息放置的时候都会section(节)对齐,但是在运行的时候会合在一起以节省空间,于是有了segment(段)概念,segment的信息存放于program header table中。segment fault就是因为出错的时候在内存中失败。

        ELF文件处理相关工具:

       Binutils官网地址:https://www.gnu.org/software/binutils/

        ar:归档文件,将多个文件打包成一个大文件。

        as:被gcc调用,输入汇编文件,输出目标文件工链接器ld连接。

        ld:gnu链接器,被gcc调用,它把目标文件和各种库文件结合在一起,重定位数据,并链接符号引用。

        objcopy:执行文件格式转换。

        objdump:显示ELF文件的信息。

        readelf:显示更多ELF格式文件的信息(包括DWARF调试信息)

        示例1:

gcc -c hello.c
readelf -h hello.o  // 查看头信息readelf -S hello.o  // 查看section信息
readelf -SW hello.o

        实例2:

gcc -g -c hello.c
objdump -S hello.o // 显示汇编指令
 嵌入式开发

        嵌入式开发是一种比较综合性的技术,它不单指纯粹的软件开发技术,也不单是一种硬件配置技术;它是在特定的硬件环境下针对某款硬件进行开发,是一种系统级别的与硬件结合比较紧密的软件开发技术。程序并不是运行在本地,而是运行在特殊的硬件上。

        参与编译和运行的机器根据其角色可以分成以下三类:

        -build系统:生成编译器可执行程序的计算机。编译器在build系统上编译出来的。 

        -host系统:运行编译器可执行程序,编译链接应用程序的计算机系统。

        -target系统:运行应用程序的计算机系统。

        根据build/host/target的不同组合我们可以得到如下的编译方式分类:

        -本地(native)编译:build==host==target

        -交叉(cross)编译:build==host!=target

QEMU

        QEMU是一套由(Fabrice Bellard)编写的以GPL许可证分发源码的计算机系统模拟软件,在GNU/Linux平台上使用广泛。 

        QEMU,支持多种体系架构。譬如:IA-32(x86),AMD 64, MIPS 32/64,RISC-V 32/64等等。

        QEMU有两种主要运作模式:

        -user mode: 直接运行应用程序 (比如说hello.o运行直接输出“hello”)

        -system mode:模拟整个计算机系统,包括中央处理器及其他周边设备。

第二章 汇编语言编程

 基本组成

        汇编文件一般后缀为.S或.s,.S包含了预处理的语句,.s就是纯粹的汇编语句。

        一个完整的RISC-V汇编程序有多条语句(statement)组成。一条典型的RISC-V汇编语言由3部分组成:

[label:] [operation] [comment]

        打方括号表示可选。

        -label表示一个标号,必须以":"结尾。label相当于一个地址,给这个地址起了个名字。是这条指令存放在内存的地址。

        -operation可以由以下多种类型:

                -instruction(指令):直接对应二进制机器指令的字符串

                -preudo-instruction(伪指令):为了提高编写代码的效率,可以用一条伪指令指示汇编器产生多条实际的指令(instruction)。(要在汇编器的手册里查看定义)

                -directive(指示/伪操作):通过类似指令的形式(以“.”开头),通知汇编器如何控制代码的产生等,不对应具体的指令。属于汇编器自己定义的一些语法。(要在汇编器的手册里查看定义)

                -macro:采用.macro/.endm自定义的宏

        例子:


.macro do_nothing      #directivenop            #preudo-instructionnop            #preudo-instruction
.endm.text              #directive  告诉汇编器生成的指令要放到text的section中.global _start     #directive  _start是个全局变量,外部可见,有点像extern_start:                 #labelli x6, 5            #preudo-instructionli x7, 4            #preudo-instructionadd x5, x6, x7      #instructiondo_nothing          #calling macro.end                #End of file
RISC-V汇编指令操作对象

        寄存器:

        -在RISC-V中,Hart在执行算术逻辑运算时所操作的数据必须直接来自寄存器

        -32个通用寄存器,x0~x31;

寄存器别名
x0读0寄存器,其值永远为0,写操作对其无效。
x1ra,用于存储函数调用后的返回地址。
x2sp,栈指针。
x3gp,全局指针。一半指向全局变量和静态变量。它可以帮助快速访问全局数据区域,提高对全局变量的访问效率。
x4tp,线程指针。主要用于多线程编程,指向当前线程的特定数据结构或信息。
x5-x7,x28-x31t0-t6,临时寄存器。通常用于临时存储或中间结果的保存。在程序执行过程中,可用于暂存数据、参与运算等。
x8-x9,x18-x27s0-s11,保存寄存器。用于在函数调用过程中保存寄存器的值,以便在函数返回时恢复这些值。
x10-x17a0-a7,参数寄存器。用于传递函数的参数。参数可以通过这些寄存器传递给被调用函数,方便函数内部对参数进行访问和处理。
 RISC-V汇编指令类型
31-2524-2019-1514-1211-76-0
funct7rs2rs1funct3rdopcodeR-type
imm[]imm[]rs1funct3rdopcodeI-type
imm[]rs2rs1funct3imm[4:0]opcodeS-type
imm[]rs2rs1funct3imm[4:1[11]]opcodeB-type
imm[]imm[]imm[]imm[]rdopcodeU-type
imm[]imm[]imm[]imm[]rdopcodeJ-type

        R-type:(register),每条指令中有三个fields,用于指定3个寄存器参数。

         I-type:(Immediate),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits)。

        S-type: (Store),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但fields的组织方式不同于I-type)。(用来访问内存的指令)

        B-type: (Branch),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但取值为2的倍数)。(跟分支跳转有关)

        U-type:(Upper),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits,用于表示一个立即数的高20位)。(auipc、lui)

        J-type:(Jump),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits)。

小端序的概念

         riscv是小端序编指令。

算数指令
指令语法描述例子
ADDADD RD, RS1, RS2RS1和RS2的值相加,结果保存到RDadd x5, x6, x7
SUBSUB RD, RS1, RS2RS1的值减去RS2的值,结果保存到RDsub x5, x6, x7
ADDIADDI RD, RS1, RS2RS1的值和IMM相加,结果保存到RDaddi x5, x6, x7
LUILUI RD, IMM构造一个32位的数,高的20位存放IMM,低12位清零,结果保存到RDlui x5, 0x12345
AUIPCAUIPC RD, IMM构造一个32位的数,高20位存放IMM,低12清零。结果和PC相加保存到RD。auipc x5, 0x12345
基于算术运算指令实现的其他伪指令
伪指令语法等价指令指令描述例子
LILI RD, IMMLUI和ADDI的组合将立即数加载到RD中li x5, 0x12345678
LALA RD, LABELAUIPC和ADDI组合为RD加载一个地址值la x5,label
NEGNEG RD, RSSUB RD, X0, RS对RS中的值取反并将结果存放在RD中neg x5, x6
MVMV RD, RSADDI RD, RS, 0将RS中的值拷贝到RD中mv x5, x6
NOPNOPADDI x0, x0, 0什么也不做nop
LI(Load Immediate)

        用算数指令构建一个大数  

#例子:构建一个大数# imm is in range of [-2048, +2047]
# 0x80000
lui x5, 0x80
addi x5, x0, 0x80# imm is NOT in the range of [-2048, +2047]
# and the most-significant-bit of "lower-12" is 0
# 0x12345001
lui x6, 0x12345
addi x6, x6, 0x001# imm is NOT in the range of [-2048, +2047]
# and the most-significant-bit of "lower-12" is 1
# 0x12345FFF
lui x7 0x12346
addi x7, x7, -1

        用伪指令

li a7 xxxxxxxxx
LA(Load Address)
语法LA RD, LABEL
例子la x5, foo

        LA是一个伪指令

        具体编程时给出需要加载的label,编译器会根据实际情况利用auipc和其他指令自动生成正确的指令序列。

        常用语加载一个函数或者变量的地址。

        例子

_start:la x5, _start    # x5 = _startjr x5

        反汇编出来很可能就是一条auipc指令。

逻辑运算指令
指令格式语法描述例子
ANDR-typeAND RD, RS1, RS2RD = RS1 & RS2and x5, x6, x7
ORR-typeOR RD, RS1, RS2RD = RS1 | RS2or x5, x6, x7
XORR-typeXOR RD, RS1, RS2RD = RS1 ^ RS2xor x5, x6, x7
ADDII-typeANDI RD, RS1, IMMRD = RS1 & IMMandi x5, x6, 20
ORII-typeORI RD, RS1, IMMRD = RS1 | IMMor x5, x6, 20
XORII-typeXORI RD, RS1, IMMRD = RS1 ^ IMMxor x5, x6, 20

        所有的逻辑指令都是按位操作。

逻辑移位运算指令​​​​​​​​​​​​​​
指令格式语法描述例子
SLLR-typeSLL RD, RS1, RS2逻辑左移sll x5, x6, x7
SRLR-typeSRL RD, RS1, RS2逻辑右移srl x5, x6, x7
SLLII-typeSLLI RD, RS1, IMM逻辑左移立即数slli x5, x6, 3
SRLII-typeSRLI RD, RS1, IMM逻辑右移立即数srli x5, x6, 3

        无论是逻辑左移还是右移,补足的都是0。

算术移位运算指令​​​​​​​​​​​​​​
指令格式语法描述例子
SRAR-typeSRA RD RS1, RS2算术右移sra x5, x6, x7
SRAII-typeSRAI RD, RS1, IMM算术右移立即数srai x5, x6, 3

        算术右移时按照符号位值补足。

内存读写指令

内存读
指令格式语法描述例子
LBI-type        LB RD, IMM(RS1)读取一个8bits的数据到RD中,内存地址=RS1 + IMM,数据保存到RD之前执行sign-extended。lb x5, 40(x6)
LBUI-type        LBU RD, IMM(RS1)数据在保存到RD之间会执行zero-extendedlbu x5, 40(x6)
LHI-type        LH RD, IMM(RS1)读取一个16bit的数据。lh x5, 40(x6)
LHUI-type         LHU RD, IMM(RS1)读取伊恩16bit的数据。lhu x5, 40(x6)
LWI-type         LW RD, IMM(RS1)读取一个32bit的数据。lw x5, 40(x6)
内存写
指令格式语法描述例子
SBS-type       SB SR2, IMM(RS1)将RS2寄存器中低8bits的数据写出到内存中,内存地址=RS1 + IMM。sb x5, 40(x6)
SHS-type        SH SR2, IMM(RS1)sh x5, 40(x6)
SWS-type         SW SR2, IMM(RS1)sw x5, 40(x6)
条件分支指令
指令格式语法描述例子
BEQB-typeBEQ RS1, RS2, IMM如果相等则跳转beq x5, x6, 100
BNEB-typeBNE RS1, RS2, IMM如果不相等则跳转bne x5, x6, 100
BLTB-typeBLT RS1, RS2, IMM按照有符号数方式比较,如果小于则跳转blt x5, x6, 100
BLTUB-typeBLTU RS1, RS2, IMM按照无符号数方式比较,如果小于则跳转bltu x5, x6, 100
BGEB-typeBGE RS1, RS2, IMM按照有符号方式比较,大于等于就跳转bge x5, x6, 100
BGEUB-typeBGEU RS1, RS2, IMM按照无符号方式比较,大小等于就跳转

bgeu x5, x6, 100

伪指令语法等价指令描述
BLEBLE RS, RT, OFFSETBGE RT, RS, OFFSET有符号方式比较,<=
BLEUBLEU RS, RT, OFFSETBGEU RT, RS, OFFSET 无符号方式比较,<=
BGTBGT RS, RT, OFFSETBLT RT, RS, OFFSET有符号比较,>
BGTUBGTU RS, RT, OFFSETBLTU RT, RS, OFFSET无符号比较,>
BEQZBEQZ RS, RT, OFFSETBNE RS, x0, OFFSET==0
BNEZBNEZ RS, RT, OFFSETBLT RS, x0, OFFSET!=0
BLTZBLTZ RS, RT, OFFSETBGE x0, RS, OFFSET< 0
BLEZBLEZ RS, RT, OFFSETBLT RT, R0, OFFSET<= 0
BGTZBGTZ RS, RT, OFFSETBGE RT, RS, OFFSET> 0
BGEZBGEZ RS, OFFSETBGE RT, RS, OFFSET>= 0
无条件跳转
JAL(Jump And Link)
语法JAL RD, LABEL
例子jal x1, label

        jal指令用于调用子过程,跳转到指定位置,并且把下一条指令的地址写入RD,保存为返回地址。

JALR(Jump And Link Register)
语法JALR RD, IMM(RS1)
例子jalr x0, 0(x5)

        jalr指令同样用于调用子过程,跳转到指定位置,并且把下一条指令的地址写入RD,保存为返回地址。

例子
int a = 1;
int b = 1;void sum()
{a = a + b;return;       // jalr x0, 0(x5)   不需要保存下一条指令的地址
}void _start()
{sum();        // jal x5, sum/***other codes***、
}
思考:

        如何解决更远距离的跳转?

AUIPC X6, IMM-20
JALR X1, X6, IMM-12
指令寻址模式总结

        所谓寻址指的是指令中定位操作数或者地址的方式

寻址模式解释例子
立即数寻址操作数是指令本身的一部分。addi x5,x6,20
寄存器寻址操作数存放在寄存器中,指令中指定访问的寄存器从而获取该操作数。add x5, x6, x7
基址寻址操作数在内存中,指令中通过指定寄存器(基址base)和立即数(偏移量offset),通过base+offset的方式获得操作数在内存中的地址从而获取该操作数。sw x5,40(x6)
PC相对寻址在指令中通过PC和指令中的立即数相加获得目的地址的值。beq x5,x6,100

第三章 函数的调用

函数调用过程概述

        当caller和callee不是一个人写的,这个时候就需要制定一套规定。​​​​​​​

函数调用的规则
寄存器名ABI名(编程用名)用途约定谁负责在函数调用过程中维护这些寄存器
x0zero读取时总为0,写入时不起任何效果N/A

x1

ra存放函数返回地址Caller
x2sp存放栈指针callee
x5-x7,x28-x31

t0-t2,t3-t6

临时寄存器,callee可能会使用这些寄存器,所以callee不保证这些寄存中的值在函数调用过程中保持不变。对于caller来说,如果需要的话应在调用前保存这些值。caller
x8,x9,x18-x27s0,s1,s2-s11保存寄存器。callee需要保证这些寄存器的值在函数返回后仍然维持函数之前的原值。callee
x10,x11a0,a1参数寄存器,用于在函数调用过程中保存第一个和第二参数。caller
x12-x17a2,a7参数寄存器。如果有更多的参数则要利用到栈。caller

        函数执行过程的三部曲:

        -保存现场

        -执行函数内容

        -恢复现场                

例子(尾调用)

例子(非尾调用)

riscv编程与c混合编程
汇编调用c语言

c语言调用汇编
int foo(int a, int b)
{int c;asm volatile("add %[sum], %[add1], %[add2]"  // 汇编指令:[sum] = r'(c)                  // 输出操作数列表(可选):[add1]"r"(a), [add2]"r"(b)     // 输入操作数列表(可选));return c;
}

第四章 UART串行接口调用

状态寄存器读写
语法CSRRW RD, CSR, RS1
例子csrrw t6, mscratch, t6

t6 = mscratch;mscratch = t6

语法CSRRS RD, CSR, RS1
例子csrrs x5, mie, x6x5 = mie; mie |= x6

UART(统一异步接收传输协议)

        通过异步收发传输器,是一种双全工、异步串行通信方式,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据,可以实现双全工传输和接收。

        UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。

        UART在发送或接收过程中的一帧数据由4部分组成,起始位(低电平)、数据位、奇偶校验位和停止位(高电平)。

        起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

        

        信号与管脚定义

        qemu 模拟了 ns16550a 芯片,通过查阅资料可知内存映射将对应的基地址映射到了 0x1000_0000 处。

        platform.h编写

/* This machine puts UART register here in physical memory. */
#define UARTO 0x10000000

        uart.c编写

#define UART_REG(reg) ((volatile uint8_t*)(UART0 + reg))
#define uart_read_reg(reg) (*(UART0 + reg))
#define uart_write_reg(reg, v) (*(UART0 + reg)) = (v)

         NS16550a编程接口介绍

        编写uart.c

#include "types.h"#define UART0 0x10000000L/** The UART control registers are memory-mapped at address UART0. * This macro returns the address of one of the registers.*/
#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))/** Reference* [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html*//** UART control registers map. see [1] "PROGRAMMING TABLE"* note some are reused by multiple functions* 0 (write mode): THR/DLL* 1 (write mode): IER/DLM*/
#define RHR 0	// Receive Holding Register (read mode)
#define THR 0	// Transmit Holding Register (write mode)
#define DLL 0	// LSB of Divisor Latch (write mode)
#define IER 1	// Interrupt Enable Register (write mode)
#define DLM 1	// MSB of Divisor Latch (write mode)
#define FCR 2	// FIFO Control Register (write mode)
#define ISR 2	// Interrupt Status Register (read mode)
#define LCR 3	// Line Control Register
#define MCR 4	// Modem Control Register
#define LSR 5	// Line Status Register
#define MSR 6	// Modem Status Register
#define SPR 7	// ScratchPad Register/** POWER UP DEFAULTS* IER = 0: TX/RX holding register interrupts are both disabled* ISR = 1: no interrupt penting* LCR = 0* MCR = 0* LSR = 60 HEX* MSR = BITS 0-3 = 0, BITS 4-7 = inputs* FCR = 0* TX = High* OP1 = High* OP2 = High* RTS = High* DTR = High* RXRDY = High* TXRDY = Low* INT = Low*//** LINE STATUS REGISTER (LSR)* LSR BIT 0:* 0 = no data in receive holding register or FIFO.* 1 = data has been receive and saved in the receive holding register or FIFO.* ......* LSR BIT 5:* 0 = transmit holding register is full. 16550 will not accept any data for transmission.* 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.* ......*/
#define LSR_RX_READY (1 << 0)
#define LSR_TX_IDLE  (1 << 5)#define uart_read_reg(reg) (*(UART_REG(reg)))
#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))void uart_init()
{/* disable interrupts. */uart_write_reg(IER, 0x00);/** Setting baud rate. Just a demo here if we care about the divisor,* but for our purpose [QEMU-virt], this doesn't really do anything.** Notice that the divisor register DLL (divisor latch least) and DLM (divisor* latch most) have the same base address as the receiver/transmitter and the* interrupt enable register. To change what the base address points to, we* open the "divisor latch" by writing 1 into the Divisor Latch Access Bit* (DLAB), which is bit index 7 of the Line Control Register (LCR).** Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE".* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.* And due to the divisor register is two bytes (16 bits), so we need to* split the value of 3(0x0003) into two bytes, DLL stores the low byte,* DLM stores the high byte.*/uint8_t lcr = uart_read_reg(LCR);uart_write_reg(LCR, lcr | (1 << 7));uart_write_reg(DLL, 0x03);uart_write_reg(DLM, 0x00);/** Continue setting the asynchronous data communication format.* - number of the word length: 8 bits* - number of stop bits:1 bit when word length is 8 bits* - no parity* - no break control* - disabled baud latch*/lcr = 0;uart_write_reg(LCR, lcr | (3 << 0));
}int uart_putc(char ch)
{while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);return uart_write_reg(THR, ch);
}void uart_puts(char *s)
{while (*s) {uart_putc(*s++);}
}
out/os.elf:     file format elf32-littleriscvDisassembly of section .text:80000000 <_start>:.global	_start.text
_start:# park harts with id != 0csrr	t0, mhartid		# read current hart id
80000000:	f14022f3          	csrr	t0,mhartidmv	tp, t0			# keep CPU's hartid in its tp for later usage.
80000004:	00028213          	mv	tp,t0bnez	t0, park		# if we're not on the hart 0
80000008:	00029c63          	bnez	t0,80000020 <park># we park the hart# Setup stacks, the stack grows from bottom to top, so we put the# stack pointer to the very end of the stack range.slli	t0, t0, 10		# shift left the hart id by 1024
8000000c:	00a29293          	slli	t0,t0,0xala	sp, stacks + STACK_SIZE	# set the initial stack pointer
80000010:	00000117          	auipc	sp,0x0
80000014:	42010113          	addi	sp,sp,1056 # 80000430 <stacks+0x400># to the end of the first stack spaceadd	sp, sp, t0		# move the current hart stack pointer
80000018:	00510133          	add	sp,sp,t0# to its place in the stack spacej	start_kernel		# hart 0 jump to c
8000001c:	0200206f          	j	8000203c <start_kernel>80000020 <park>:park:wfi
80000020:	10500073          	wfij	park
80000024:	ffdff06f          	j	80000020 <park>
80000028:	00000013          	nop
8000002c:	00000013          	nop80000030 <stacks>:...8000203c <start_kernel>:
extern void uart_init(void);
extern void uart_puts(char *s);void start_kernel(void)
{
8000203c:	ff010113          	addi	sp,sp,-16
80002040:	00112623          	sw	ra,12(sp)
80002044:	00812423          	sw	s0,8(sp)
80002048:	01010413          	addi	s0,sp,16uart_init();
8000204c:	014000ef          	jal	ra,80002060 <uart_init>uart_puts("Hello, RVOS!\n");
80002050:	800027b7          	lui	a5,0x80002
80002054:	18478513          	addi	a0,a5,388 # 80002184 <__global_pointer$+0xffffe7f2>
80002058:	0d8000ef          	jal	ra,80002130 <uart_puts>while (1) {}; // stop here!
8000205c:	0000006f          	j	8000205c <start_kernel+0x20>80002060 <uart_init>:#define uart_read_reg(reg) (*(UART_REG(reg)))
#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))void uart_init()
{
80002060:	fe010113          	addi	sp,sp,-32
80002064:	00812e23          	sw	s0,28(sp)
80002068:	02010413          	addi	s0,sp,32/* disable interrupts. */uart_write_reg(IER, 0x00);
8000206c:	100007b7          	lui	a5,0x10000
80002070:	00178793          	addi	a5,a5,1 # 10000001 <STACK_SIZE+0xffffc01>
80002074:	00078023          	sb	zero,0(a5)* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.* And due to the divisor register is two bytes (16 bits), so we need to* split the value of 3(0x0003) into two bytes, DLL stores the low byte,* DLM stores the high byte.*/uint8_t lcr = uart_read_reg(LCR);
80002078:	100007b7          	lui	a5,0x10000
8000207c:	00378793          	addi	a5,a5,3 # 10000003 <STACK_SIZE+0xffffc03>
80002080:	0007c783          	lbu	a5,0(a5)
80002084:	fef407a3          	sb	a5,-17(s0)uart_write_reg(LCR, lcr | (1 << 7));
80002088:	100007b7          	lui	a5,0x10000
8000208c:	00378793          	addi	a5,a5,3 # 10000003 <STACK_SIZE+0xffffc03>
80002090:	fef44703          	lbu	a4,-17(s0)
80002094:	f8076713          	ori	a4,a4,-128
80002098:	0ff77713          	andi	a4,a4,255
8000209c:	00e78023          	sb	a4,0(a5)uart_write_reg(DLL, 0x03);
800020a0:	100007b7          	lui	a5,0x10000
800020a4:	00300713          	li	a4,3
800020a8:	00e78023          	sb	a4,0(a5) # 10000000 <STACK_SIZE+0xffffc00>uart_write_reg(DLM, 0x00);
800020ac:	100007b7          	lui	a5,0x10000
800020b0:	00178793          	addi	a5,a5,1 # 10000001 <STACK_SIZE+0xffffc01>
800020b4:	00078023          	sb	zero,0(a5)* - number of stop bits:1 bit when word length is 8 bits* - no parity* - no break control* - disabled baud latch*/lcr = 0;
800020b8:	fe0407a3          	sb	zero,-17(s0)uart_write_reg(LCR, lcr | (3 << 0));
800020bc:	100007b7          	lui	a5,0x10000
800020c0:	00378793          	addi	a5,a5,3 # 10000003 <STACK_SIZE+0xffffc03>
800020c4:	fef44703          	lbu	a4,-17(s0)
800020c8:	00376713          	ori	a4,a4,3
800020cc:	0ff77713          	andi	a4,a4,255
800020d0:	00e78023          	sb	a4,0(a5)
}
800020d4:	00000013          	nop
800020d8:	01c12403          	lw	s0,28(sp)
800020dc:	02010113          	addi	sp,sp,32
800020e0:	00008067          	ret800020e4 <uart_putc>:int uart_putc(char ch)
{
800020e4:	fe010113          	addi	sp,sp,-32
800020e8:	00812e23          	sw	s0,28(sp)
800020ec:	02010413          	addi	s0,sp,32
800020f0:	00050793          	mv	a5,a0
800020f4:	fef407a3          	sb	a5,-17(s0)while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
800020f8:	00000013          	nop
800020fc:	100007b7          	lui	a5,0x10000
80002100:	00578793          	addi	a5,a5,5 # 10000005 <STACK_SIZE+0xffffc05>
80002104:	0007c783          	lbu	a5,0(a5)
80002108:	0ff7f793          	andi	a5,a5,255
8000210c:	0207f793          	andi	a5,a5,32
80002110:	fe0786e3          	beqz	a5,800020fc <uart_putc+0x18>return uart_write_reg(THR, ch);
80002114:	10000737          	lui	a4,0x10000
80002118:	fef44783          	lbu	a5,-17(s0)
8000211c:	00f70023          	sb	a5,0(a4) # 10000000 <STACK_SIZE+0xffffc00>
}
80002120:	00078513          	mv	a0,a5
80002124:	01c12403          	lw	s0,28(sp)
80002128:	02010113          	addi	sp,sp,32
8000212c:	00008067          	ret80002130 <uart_puts>:void uart_puts(char *s)
{
80002130:	fe010113          	addi	sp,sp,-32
80002134:	00112e23          	sw	ra,28(sp)
80002138:	00812c23          	sw	s0,24(sp)
8000213c:	02010413          	addi	s0,sp,32
80002140:	fea42623          	sw	a0,-20(s0)while (*s) {
80002144:	01c0006f          	j	80002160 <uart_puts+0x30>uart_putc(*s++);
80002148:	fec42783          	lw	a5,-20(s0)
8000214c:	00178713          	addi	a4,a5,1
80002150:	fee42623          	sw	a4,-20(s0)
80002154:	0007c783          	lbu	a5,0(a5)
80002158:	00078513          	mv	a0,a5
8000215c:	f89ff0ef          	jal	ra,800020e4 <uart_putc>while (*s) {
80002160:	fec42783          	lw	a5,-20(s0)
80002164:	0007c783          	lbu	a5,0(a5)
80002168:	fe0790e3          	bnez	a5,80002148 <uart_puts+0x18>}
}
8000216c:	00000013          	nop
80002170:	00000013          	nop
80002174:	01c12083          	lw	ra,28(sp)
80002178:	01812403          	lw	s0,24(sp)
8000217c:	02010113          	addi	sp,sp,32
80002180:	00008067          	ret

链接

https://github.com/plctlab/riscv-operating-system-mooc

https://gitee.com/unicornx/riscv-operating-system-mooc

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle Linux 7.9 安装minikube体验

1.环境信息 前置所需&#xff1a; 操作系统&#xff1a;Oracle Linux 7.9 虚拟机配置&#xff1a;CPU:4核 内存&#xff1a;4G 容器&#xff1a;docker 26.1.4 安装minikube后环境&#xff1a; minikube: v1.33.1 kubernetes:v1.23.3 minukube体验说明&#xff1a;使用Virtua…

flume--数据从kafka到hdfs发生错误

解决&#xff1a; #1.将flume自带的依赖删除 mv /opt/installs/flume1.9/lib/guava-11.0.2.jar /opt/installs/flume1.9/lib/guava-11.0.2.jar.bak #2.将hadoop的依赖发送到flume下 cp /opt/installs/hadoop3.1.4/share/hadoop/common/lib/guava-27.0-jre.jar /opt/installs/f…

【C++ Primer Plus习题】5.9

问题: 解答: #include <iostream> #include <cstring> using namespace std;#define SIZE 20int main() {string words[SIZE];string done "done";int count 0;while (true){cout << "请输入单词:" << endl;cin >> words…

中国发布首个AI集成Linux开源操作系统

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 AI圈最近又发生了啥新鲜事&#xff1f; 中国大模型市场迎来新格局&#xff1a;百度、商汤、智谱位列前三 国际数据公司&#xff08;IDC&#xff09;于首次发布了《中国大模型平台市场份额&#xff0…

NYX靶机笔记

NYX靶机笔记 概述 VulnHub里的简单靶机 靶机地址&#xff1a;https://download.vulnhub.com/nyx/nyxvm.zip 1、nmap扫描 1&#xff09;主机发现 # -sn 只做ping扫描&#xff0c;不做端口扫描 nmap -sn 192.168.84.1/24 # 发现靶机ip为 MAC Address: 00:50:56:E0:D5:D4 (V…

适用于应用程序安全的 11 大 DevSecOps 工具

DevSecOps&#xff08;开发者安全运营&#xff09;是指将安全最佳实践融入软件开发生命周期的过程&#xff0c;从而实现更好的安全结果。这是提供全面安全基础设施的重要方面。 市场格局&#xff1a;DevSecOps市场竞争激烈。该领域有数百家供应商提供工具&#xff0c;帮助组织…

虚幻5|AI行为树,跟随task(非行为树AI)

这个可以不需要行为树 1.打开ai的角色蓝图后&#xff0c;添加一个函数&#xff0c;命名为跟距离改变速度 并用tick调用 2.编辑函数

在VBA中调用Adobe Acrobat或Reader的命令行工具,实现PDF自动打印 (‾◡◝)

在VBA&#xff08;Visual Basic for Applications&#xff09;中自动打印PDF文件通常不直接支持&#xff0c;因为VBA本身是针对Microsoft Office应用程序&#xff08;如Excel、Word和PowerPoint等&#xff09;的编程语言&#xff0c;并不直接处理PDF文件。但是&#xff0c;你可…

【变化检测】基于Tinycd建筑物(LEVIR-CD)变化检测实战及ONNX推理

主要内容如下&#xff1a; 1、LEVIR-CD数据集介绍及下载 2、运行环境安装 3、Tinycd模型训练与预测 4、Onnx运行及可视化 运行环境&#xff1a;Python3.8&#xff0c;torch1.12.0cu113 likyoo变化检测源码&#xff1a;https://github.com/likyoo/open-cd 使用情况&#xff1a…

学习文件IO,让你从操作系统内核的角度去理解输入和输出(Java实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

经验之谈 —— 数据处理与分析的6大Python库

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 Python是一种流行的高级编程语言。它拥有丰富的生态系统和庞大的社区。这个生态系统中有许多优秀的Python库。这些库提供了有用的工具&#xff0c;使开发变得更加容易。本文将介绍6个出色的Python库。这些库在不同领域都…

数据结构初阶(2)——链表OJ

目录 1.面试题 02.02. 返回倒数第 k 个节点 2.OR36 链表的回文结构 3.160. 相交链表 1.面试题 02.02. 返回倒数第 k 个节点 思路&#xff1a;快慢指针&#xff0c;快指针先走k格&#xff0c;慢指针同步 /*** Definition for singly-linked list.* struct ListNode {* i…

android13 隐藏状态栏里面的飞行模式 隐藏蓝牙 隐藏网络

总纲 android13 rom 开发总纲说明 目录 1.前言 2.问题分析 3.代码分析 4.代码修改 5.编译运行 6.彩蛋 1.前言 android13 隐藏状态栏里面的飞行模式,或者其他功能,如网络,蓝牙等等功能,隐藏下图中的一些图标。 2.问题分析 这里如果直接找这个布局的话,需要跟的逻…

[数据集][目标检测]电力场景输电线杆塔塔架金属锈蚀腐蚀生锈检测数据集VOC+YOLO格式1344张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1344 标注数量(xml文件个数)&#xff1a;1344 标注数量(txt文件个数)&#xff1a;1344 标注…

(南京观海微电子)——直流电源使用介绍

什么是稳压电源&#xff1f;直流稳压电源使用方法教程 在电子技术领域中&#xff0c;稳压电源扮演着举足轻重的角色。那么&#xff0c;究竟什么是稳压电源呢&#xff1f;稳压电源是一种能够提供稳定输出电压的电子装置&#xff0c;其核心功能是在输入电压波动或负载变化的情况…

i.MX6裸机开发(9):CCM时钟控制模块

本章参考资料&#xff1a;《IMX6ULRM》&#xff08;参考手册&#xff09;。 学习本章时&#xff0c;配合《IMX6ULRM》第18章Clock Controller Module (CCM)&#xff0c;效果会更佳&#xff0c;特别是涉及到寄存器说明的部分。 本章我们主要讲解时钟部分&#xff0c;芯片内部的…

摄影曝光:曝光模式认知

写在前面 学习整理《摄影曝光&#xff1a;拍出好照片的49个关键技法》读书笔记博文内容涉及曝光模式简单认知适合小白认知理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99%的焦虑都来自于虚度时间和没有好好做事&#xff0c;所以唯一的解决办法就是行动起来&#xff0c;认真…

【Docker】Docker Consul

docker consul Docker Consul 是一个用于服务发现和配置的开源工具&#xff0c;它是 HashiCorp 公司推出的一个项目。Consul 提供了一个中心化的服务注册和发现系统&#xff0c;可以帮助开发人员轻松地在 Docker 容器和集群之间进行服务发现和配置管理。 Consul 使用基于 HTT…

SQL注入漏洞的基础知识

目录 SQL注入漏洞的定义和原理 SQL注入的类型和攻击方法 SQL注入的防御措施 示例代码 深入研究 SQL注入漏洞的常见攻击场景有哪些&#xff1f; 如何有效防范SQL注入攻击&#xff1f; SQL注入与跨站脚本攻击&#xff08;XSS&#xff09;之间有什么区别&#xff1f; 主要…

CORS错误

说明&#xff1a;记录一次CORS&#xff08;跨域&#xff09;错误&#xff0c;及解决方法。 场景 在vscode里面运行前端项目&#xff0c;idea中运行后端项目&#xff0c;登录时&#xff0c;访问接口&#xff0c;报CORS错误&#xff0c;如下&#xff1a; 解决 在后端项目的网关…