龙芯2K500使用的是LoongArch指令集架构,是不同与现有的X86、Arm、Risc-V的指令集架构。
在官方资料库中,提供了下面的资料:
上述资料中,要了解LoongArch指令集架构的基础,可以从《龙芯架构参考手册-卷一基础架构.pdf》开始。
另外,《计算机体系结构基础(LoongArch)-3rd.pdf》也非常值得学习,是以LoongArch为基础详细讲解计算机硬件体系结构。
有了板子,有了书,对于深入理解计算机硬件和指令,可以理论联系实际,加深理解。
学用LoongArch的基础之一,就是学习LoongArch的汇编。
汇编通常分为两种,一种是裸机汇编,一种是在现有系统下的汇编。
裸机汇编难度较大,因为系统的方方面面,你都需要详细的了解,才能进行驱动控制。
在现有系统下的汇编,则可以调用现有系统的接口,将时间精力放在主要逻辑上。
官方为龙芯2K500提供的是一个裁剪版本的Linux,在此基础上编写汇编程序,可以调用Linux系统提供的系统调用。
在《LoongArch 系统调用(syscall)ABI.pdf》文件中,对“Linux/LoongArch 系统调用 ABI”进行了详细的说明,后面示例程序中,将会进行调用。
学习c/c++,从hello world开始;学习汇编,也同样可以从hello world开始。
编写前,需要先准备好编译环境。编译c/++使用的是GCC,编译汇编也可以用GCC。
可以参考《交叉工具链使用说明.pdf》使用“toolchain-loongarch64-linux-gnu-gcc8-host-x86_64-2022-07-18.tar.xz”来建立交叉编译环境。
也可以直接使用 foxsen/loongarch-assembly: assembly experiment environment for loongarch 提供的docker环境快速搭建。
一个标准的基础LoongArch汇编组成如下:
.section .data
.section .text
.global main
main:
其中:
然后,我们编写一个简单的汇编文件,让运行后输出信息到stdout(标准输出)和stderr(标准错误输出)
.section c
string1: .asciz "Hello World!\r\nI'm LoongArch!\r\n"
string2: .asciz "Error:It is a test.\r\n"
.section .text
.global main
main:
li.w $a7, 64 # write syscall number
li.w $a0, 1 # stdout file descriptor == 1
la $a1, string1 # string address
li.w $a2, 32 # string len
syscall 0x0 # syscall
li.w $a7, 64 # write syscall number
li.w $a0, 2 # stderr file descriptor == 2
la $a1, string2 # string address
li.w $a2, 22 # string len
syscall 0x0 # syscall
li.w $a7, 93 # exit syscall number
li.w $a0, 0
syscall 0x0 # syscall
我们先看看这个汇编最终的输出,然后再做讲解:
在上述运行过程中,如果不加任何参数(或者管道输出),会显示全部内容,这个内容,对应汇编里面.data部分的定义。
如果加了2>/dev/null
,表示把stderr部分的输出,重定向到/dev/null,也就是不显示stderr的内容。
在Linux的系统调用中,输出内容的基础是write(),其具体定义如下:
write()
函数定义:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
如果fd=1,则输出到标准输出stdout,如果fd=2,则输出到标注错误输出stderr。
在c/c++中,我们可以很方便的调用write()来输出内容。但是在汇编中,不能直接调用,需要通过系统ABI的方式来调用。
前面说过的《LoongArch 系统调用(syscall)ABI.pdf》就排上用场了。
上图中的__NR_write是一个数字,它就是系统调用号,实际对应write(),我们在汇编程序中,通过这个系统调用号来调用其实际对应的功能。
对应的代码如上述汇编代码:
li.w $a7, 64 # write syscall number
li.w $a0, 1 # stdout file descriptor == 1
la $a1, string1 # string address
li.w $a2, 32 # string len
syscall 0x0 # syscall
涉及到的寄存器,在手册中有说明:
li.w表示装载数据到寄存器,la表示传递地址
上述代码的逻辑就是,调用64号系统调用,也就是调用write(),并传入对应的参数:fd=1,buf对应string1,len为string1的长度(包含\0,所以为字符本身长度+1)
最后,通过上图中的LoongArch的syscall可以看到,之前的几步,正是为了设置对应的寄存器,然后最后完成调用,输出string1。
而输出string2的部分,则类似,只是设置$a0为2,表示输出到stderr。
程序的主要逻辑完成后,最后正常退出,并返回状态0,对应的汇编如下:
li.w $a7, 93 # exit syscall number
li.w $a0, 0
syscall 0x0 # syscall
系统调用编号93对应:
上述代码编写好了以后,保存为:hello-LoongArch.S,然后编译:
loongarch64-linux-gnu-gcc -static hello-LoongArch.S -o hello-LoongArch
最后会生成 hello-LoongArch ,将该文件传到龙芯2K500开发板,使用下面的指令运行,就能得到前面演示的结果了:
# 全部输出
./hello-LoongArch
# 错误信息输出到/dev/null
./hello-LoongArch 2>/dev/null
# 正常输出输出到/dev/null
./hello-LoongArch 1>/dev/null
参考资料:
更多回帖