树莓派ARM汇编语言编程十讲(第6讲)

内容简介
树莓派单板机(Raspberry Pi Single Computer)是一种极了不起的产品,用户可以以非常低的成本获得一个Linux环境并带GPIO硬件扩展的迷你计算机系统。新一代树莓派4B还提供了良好的工业物联网和AIoT支持。树莓派单板机拥有完整的生态链,软硬件资源丰富,是嵌入式系统开发和智能硬件产品创新的很好选择。
作为嵌入式系统与嵌入式智能硬件开发基础中的基础,汇编语言是许多从事信息科学和工程领域的技术人员应该掌握的一项基本技能。目前,市场上针对树莓派单板机系统介绍C、Scratch、Python等编程语言与实践方面的资源很多,但鲜有系统针对树莓派单板机ARM汇编语言编程方面的介绍。这里以袁志勇主编的《嵌入式系统原理与应用技术》(北京航空航天大学出版社2019年1月第3版)一书中ARM汇编语言编程知识为基础,采用树莓派单板机及Linux操作系统验证平台,较系统地介绍树莓派ARM汇编语言编程技术与示例。由于准备仓促,不妥之处,还请各位不吝赐教。
第6讲:GNU ARM汇编语言程序结构与子程序调用及树莓派GNU ARM汇编子程序调用举例
第6讲目录
·树莓派Linux服务调用介绍
·GNU ARM汇编语言的程序结构与子程序调用
·树莓派GNU ARM汇编子程序调用举例
一、树莓派Linux服务调用介绍

操作系统(如Linux)的主要职责之一是为应用程序提供服务。这些服务大多都涉及外围设备(显示器、键盘、鼠标、网络等)、磁盘文件(包括磁盘及固态存储设备)读写等操作。Linux服务调用 (Linux Severce Call)又称Linux系统调用(Linux System Call),调用程序须向Linux提供所执行的操作信息,包括:
(1) 要执行的系统调用号送寄存器R7;
(2) 要写入/读取的设备号送寄存器R0;
(3) 数据缓冲区指针送寄存器R1;
(4) 要写入/读取的数据个数送寄存器R2;
(5) 执行SVC 0(执行0号软件中断)。
下面是树莓派中常常用到的几个Linux服务调用:
调用号1:终止程序
当应用程序退出时,使用服务调用 (SVC 0指令) 将控制返回给Linux,程序退出服务调用ARM汇编指令序列如下:
mov R0,#0 @ 0=“正常结束”退出码→R0
mov R7,#1 @ 终止程序系统调用号1→R7
svc 0 @ 执行终止程序服务调用软件中断
调用号3:将数据从I/O设备读入内存缓冲区
Linux可支持多个自定义设备和磁盘文件,一些标准设备有固定的标准名称,如stdin、stdout等。这里,stdin指的是标准字符输入流,默认的标准输入设备是键盘,不过,它也可重定向到其他设备或文件。从键盘输入字符至内存缓冲区服务调用ARM汇编指令序列如下:
mov R7,#3 @ 从I/O读取数据系统调用号3→R7
mov R0,#0 @ 0=stdin码(标准输入设备为键盘)
ldr R1,=string @ R1指向输入缓冲区string
mov R2,#30 @ 要输入的最大字符个数
svc 0 @ 执行从stdin读取字符串Linux服务调用软件中断
调用号4:将数据从内存缓冲区写到I/O设备
第二个标准设备名是stdout,默认的标准输出设备是显示器,不过,它也可重定向到其他设备或文件。
mov R7,#4 @ 写数据到I/O系统调用号4 →R7
mov R0,#1 @ 1=stdout码 (标准输出设备为显示器)
ldr R1,=string @ R1指向要显示的string缓冲区
mov R2,#16 @ 待显示数据的字节数
svc 0 @ 执行在stdout显示字符数据服务调用软件中断
树莓派Linux有数百个Linux服务调用。
二、GNU ARM汇编语言的程序结构与子程序调用
1.GNU ARM汇编语言的程序结构
GNU ARM汇编语言程序是以程序段为单位组织代码。段是相对独立的指令序列或数据序列,具有特定的名称。段可以分为代码段和数据段,代码段的内容为可执行代码,数据段存放代码运行时要用到的数据。一个ARM汇编程序至少有一个代码段。当程序较长时,可以分割多个代码段和数据段,多个段在程序汇编链接时最终形成一个可执行文件。
GNU ARM可执行文件通常由以下几部分组成:
●一个或多个代码段(.text),代码段的属性为只读;
●零个或多个包含初始化数据的数据段(.data),数据段的属性为可读写;
●零个或多个不包含初始化数据的数据段(.bss),数据段的属性为可读写。
代码段(.text):在采用段式内存管理的架构中,代码段 (text segment) 通常是指用于存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
数据段(.data):在采用段式内存管理的架构中,数据段 (data segment) 通常是指用于存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
BSS段(.bss):BSS是Block Started by Symbol的简称。在采用段式内存管理的架构中,BSS段 (bss segment) 通常是指用于存放程序中未初始化的全局变量的一块内存区域。BSS段属于静态内存分配。
GNU ARM链接器根据系统默认或者用户设置的段规则,将各个段安排在存储器的相应位置。因此,GNU ARM汇编源程序中段之间的相对位置与可执行目标代码中段的相对位置可能会有所不同。
例1:包括代码段和数据段的GNU ARM汇编语言程序结构举例。
@ Filename:arm_exp.s
.text @ 标识代码段
.global _start @ 声明全局标号_start
_start: ldr R1,=info @ R1指向待显示的info缓冲区
mov R2,#16 @ 字符字符数
mov R0,#1 @ 1=stdout (标准输出设备为显示器)→R0
mov R7,#4 @ 写字符数据系统调用号4 →R7
svc 0 @ Linux系统调用 (在stdout标准设备显示字符串)
mov R0,#0 @ “正常结束”退出码→R0
mov R7,#1 @ 终止程序系统调用号→R7
svc 0 @Linux系统调用(终止程序)
.data @ 标识数据段
info: .ascii “Output a string\n” @ 16个字符组成的字符串 (空格及\n计为一个字符)
.end
本例ARM汇编源程序分成了两个段,一个是.text伪操作指示的代码段,另一个是.data伪操作指示的数据段。.text伪操作用于将代码编译到代码段或代码段的子段;.data伪操作用于将数据编译到数据段或数据段的子段。在数据段中,.ascii伪操作告诉汇编程序将字符串放到数据段,然后就能像在ldr伪指令中通过标号string对其进行访问。"Output a string\n"字符串中的 “\n”字符表示换行。如果字符串后不跟 “\n”换行符,则在Linux终端显示出字符串后,会紧接着在同一行显示Linux终端命令提示符$。另外,本例ARM汇编程序有两处使用了Linux服务调用,一处是使用Linux系统调用码1终止程序,另一处是使用Linux系统调用码4将字符数据从内存缓冲区输出到显示器 (stdout标准输出设备)上显示。
在树莓派Linux终端编辑、汇编、链接本例程序,程序运行后将在终端窗口显示“Output a string”字符串(见图1)。
在这里插入图片描述
图 1 含代码段和数据段的GNU ARM汇编程序执行
2.GNU ARM汇编语言的子程序调用
在GNU ARM汇编语言中,子程序 (Subroutine) 调用一般由BL指令实现,子程序调用格式如下:
BL 子程序名
这里子程序名其实就是一个符号地址,它的实际值是相对当前PC值的一个地址偏移量,该地址偏移量是一个24位有符号数,GNU AS汇编器将其左移两位后有符号扩展为32位,然后与PC值相加,即得到跳转的子程序入口地址,跳转范围为±32 MB。
该指令执行时完成的操作:将子程序的返回地址存放在链接寄存器LR (R14)中,然后将程序计数器PC (R15)指向子程序的入口处,当子程序执行结束要返回到主程序 (Main program) 调用处时,只需将存放在LR中的返回地址重新传送到程序计数器PC中即可(见图2)。
在这里插入图片描述
图2 ARM汇编语言的子程序调用
ARM汇编子程序执行到最后,LR中的返回地址重新传送到程序计数器PC的方法有两种:一种是使用MOV PC, LR指令,另一种是使用BX LR指令(BX指令功能:PC←LR&0xfffffffe, T←LR[0]&1),它们都能从ARM汇编子程序返回到主程序。
主程序在调用子程序时,一方面要把初始化数据传送给子程序,另一方面要把子程序运行结果传送给主程序,因此,主子程序之间的参数传递显得尤为重要。
一般有三种方法实现参数传递:
(1)使用寄存器:该方法是将所需传递的参数直接放在主程序的寄存器中,这是最常用的方法;
(2)使用存储单元:主程序将参数存放在公共存储单元,子程序从公共存储单元获取参数;
(3)使用堆栈:主程序将参数压入堆栈,子程序执行时从堆栈中获取参数。
三、树莓派GNU ARM汇编子程序调用举例
前面介绍了Linux服务调用和ARM汇编子程序调用,现在此基础上编写一个树莓派GNU ARM汇编子程序调用示例。
例2:树莓派GNU ARM汇编子程序调用举例。
本例程序清单见图3所示。在这里插入图片描述
图3 构建和执行subp程序(1)
首先用nano编辑器分别创建ARM 汇编源程序文件“subp.s“及对源程序进行汇编和链接的“build”批处理文件,build批处理文件内容包含as -o subp.o subp.s和ld -o subp subp.o两个命令,这类似于很多GUI编译器菜单中的Build命令。然后,用chmod命令使build成为可执行文件:
chmod +x build
接着,可键入./build命令。如果该文件正确,可键入./subp执行程序,此时Linux终端显示True字符串(见图3)。
本例程序中的bl d_logic语句为主程序中的子程序调用指令,从d_logic: cmp R0,#0指令开始到mov PC,LR指令结束的部分为子程序,该子程序的功能是根据主程序中_start: mov R0,#1语句的操作数为0或者为非零值,在显示器显示False或者True字符串。这里用_start: mov R0,#1语句表示调用d_logic子程序后,显示True字符串。若将_start: mov R0,#1修改为_start: mov R0,#0则调用d_logic子程序后显示False。我们还可以在本程序基础上继续修改完善功能,如增加从stdin键盘输入字符来显示对应的True/False字符串(比如,按T键显示True,按F键显示False),这可使用Linux服务调用来实现。
在这里插入图片描述
图4 构建和执行subp程序(2)
在图4中,使用了bash -x (调试模式)执行build命令,此时会显示出build文件中的汇编、链接等命令序列。
下面在Pi Linux终端执行ls -l命令, build文件以及subp各文件输出信息如下:
pi@yuanzy:~ $ ls -l
-rwxr-xr-x 1 pi pi 39 4月 8 23:05 build
-rwxr-xr-x 1 pi pi 944 4月 8 23:32 subp
-rw-r–r-- 1 pi pi 796 4月 8 23:32 subp.o
-rw-r–r-- 1 pi pi 1206 4月 8 22:57 subp.s
观察上面的文件的大小,可看出可执行文件subp仅有944字节(不到1 KB)。这是因为执行subp程序不需要运行时(runtime)或任何其他的库,因此生成的可执行程序代码量极小。
下面使用objdump命令反汇编subp.o目标文件,可以看到本例程序的ARM机器码及对其进行反汇编后的ARM汇编语句(见图5)。
pi@yuanzy:~ $ objdump -d subp.o
subp.o: 文件格式 elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a00001 mov r0, #1
4: eb000002 bl 14 <d_logic>
8: e3a00000 mov r0, #0
c: e3a07001 mov r7, #1
10: ef000000 svc 0x00000000
00000014 <d_logic>:
14: e3500000 cmp r0, #0
18: 159f1014 ldrne r1, [pc, #20] ; 34 <d_logic+0x20>
1c: 059f1014 ldreq r1, [pc, #20] ; 38 <d_logic+0x24>
20: e3a02006 mov r2, #6
24: e3a00001 mov r0, #1
28: e3a07004 mov r7, #4
2c: ef000000 svc 0x00000000
30: e1a0f00e mov pc, lr
34: 00000000 .word 0x00000000
38: 00000006 .word 0x00000006
在这里插入图片描述
图5 反汇编subp.o目标文件
End of This Lecture.
(作者Email联系:yuanzywhu@163.com)
发布时间:2020年4月9日
上一讲链接
下一讲链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

袁易学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值