18

修志龙_ZenonXiu · 2022年08月07日 · 上海市

必须了解的64位编程知识 (1)

自2011年arm公布了armv8-a 64构架以来,到现在已经近10年。Arm的软件生态系统快速向64位进化,Linux kernel arm构架支持的演化基本只在arm64上实施。 2017年Apple宣布从iOS11开始所有的应用都需要是64bit的,
https://developer.apple.com/d...

iOS 11 and later, all apps use the 64-bit architecture. If your app targets an earlier version of iOS, you must update it to run on later versions. 

2019年,Google宣布从2019年8月开始所有在 google store 上的应用必须是64bit的。
https://android-developers.go...
Starting August 1, 2019:

All new apps and app updates that include native code are required to provide 64-bit versions in addition to 32-bit versions when publishing to Google Play.
Starting August 1, 2021:

Google Play will stop serving apps without 64-bit versions on 64-bit capable devices, meaning they will no longer be available in the Play Store on those devices.

Arm从Cortex-A76开始只在EL0提供32位(aarch32)的支持,在EL1-EL3 CPU硬件只提供64位支持(aarch64),这包括Cortex-A77, A78处理器。在不久的将来,arm提供的应用处理器在EL0-EL3都只支持64位。Cortex- A53/A57/A72/A73/A55/A75这些处理器还是在EL0-EL3都支持32和64位。
64bit programming-ELs.jpg
早在2011年arm刚推出64bit构架时,很多arm的客户工程师没有太多接触到64位构架的处理器,因此对在64位处理器编程有些困惑。因此,我特地写了一个有关如何从 arm 32 bit迁移到64bit的培训内容提供到给客户的培训中。并且与 Chris合作写了一个白皮书,
https://community.arm.com/cfs...

随着Cortex-A处理器的演化,会有越来越多的人在64位系统是开发固件,driver, 应用。因此希望此文可以有些帮助。

本文主要讲述C编程相关的考虑,汇编相关的不在此文讨论范围。

概述
Arm的64位编程需要注意的事项和其他构架基本一样。
大多数的工作都是由使用aarch64的编译工具完成,只要将代码重新编译即可,但是有一有些注意事项:

  1. 有些代码在32位系统上可以正常工作,但移植到64位系统上可能会有问题
  2. 需要特别注意数据类型转换
  3. 指针运算操作需要特别注意
  4. 在不同数据类型之间运算需要注意

数据模型
在32位系统和64位系统上不同数据类型的大小有所不同,
ILP32是32位系统上使用的数据类型,LLP64是指64位系统使用一种数据模型,long类型数据大小还是4 byte,而long long类型的数据大小是8 byte,这种数据模型在Windows上使用,而Linux系统上采用的是LP64数据模型,long类型大小是8 byte, long long 类型也是8 byte. 而int类型在32位和64位系统上,都是4 byte。
对于指针类型,在32位系统上,其类型大小为4 byte,在64位系统上为 8 byte。
64bit programming-data model.jpg
当然如果你之前使用的16位,8位CPU系统,那么int类型可能相应的是2 byte和1 byte, 那么移植这样的代码到32位系统,以下内容也有帮忙。

重回大学,C语言知识
C语言博大精深,由于对CPU构架有了更好的认识,越来越理解C语言设计后面的原理和考虑。
如果你精通C语言,下面的章节可以忽略。而且你写代码也许已经可以在32位和64位系统上可靠运行。但是很多人因为会忽略C语言定义的基本规则,从而导致代码移植的问题,当然这也是正常的,毕竟很多人之前很长只写过在32位CPU上运行的代码,而且代码跑的很好。

C语言的通用类型转换规则

这些规则包含:

  1. 显式数据类型转换
    a) Type casting, 比如 (int) a;
    强制数据类型转换
  2. 隐式数据类型转换
    a) Integral promotion
    b) Usual 算式转换
    这些数据类型转换在写出可移植的代码非常关键。

Integral promotion
著名的K&R C编程语言里面说,

“A character, a short integer, or an integer bit-field, signed or unsigned, or an object of enumeration type, may be used in an expression wherever an integer maybe used.“
“If an int type can represent all values of the original type, the value is converted to an int, otherwise it is converted to an unsigned int.”

一个使用有符号或是无符号char, short, 整型位域类型和枚举类型的表达式,使用int整型来表达。
如果一个有符号 int类型可以表达原来类型的所有值的话,原来的数据类型转换成有符号 int类型,否则的话转换乘无符号int类型。

也就是说如果一个整型类型的大小小于int 类型大小,那么在运算过程中,需要先被隐式转换成 int或是unsigned int类型。当然存储在内存里面的大小还是原始大小。这个结合到armv8构架也好理解:
Arm构架是个load/store的RISC构架,对一个数据运算时,需要从内存中取出这个值到寄存器中,然后做ALU运算,最后写回到内存。
而做ALU运算时,运算的寄存器是整个 W(32bit)或是X(64bit)寄存器,而不会只做8bit,16bit的运算,这样的话,需要将char,short类型的进行符号位/零扩展到32bit W寄存器。比如,要进行一下操作

char a;
a+=1;

实际对于到汇编代码为,

LDRSB W0, [X1]    //从&a的内存中取出1byte,并将bit7符号位扩展到整个 32 bit W0
ADD  W0, W0, #1  //32bit加法,+1
STRB  W0, [X1]    //将1byte,写回内存

这个Integral promotion可以由下表表示
64bit programming-type convert.jpg

举几个例子

unsigned char va= 0x55; 
if (~va == 0xAA)
   return 1;
else
   return 0;

上面是return 1还是0呢?
也许你的算法是 ~va= ~(0x55) = 0xAA
其实这是有问题的,因为~va是个表达式,根据上面的讲述,char需要做Integral promotion,因此~va的值在32位系统上是0xFFFFFFAA, 所以返回的是0.

Usual 算式转换
大学里面学过,如果两个类型不一样的数据进行运算,需要把小的数据类型先转换成大的数据类型,再进行运算。比如

 int a;
 long long b, c;
 c=a+b;

先要把 a转换成long long 类型再进行加法运行。结合到arm的指令,虽然有 W和X寄存器,他们可以在代码里混用,

ADD W2, W1, W0
ADD X5,  X4, X3

但是没有

ADD X3, X2, W1

对于 char/short 类型,先进行Integral promotion,然后再转换类型。

Usual 算式转换包含

  • Arithmetic operators with two operands:*,/,%,+, and-
  • Relational and equality operators:<,<=,>,>=,==, and!=
  • The bitwise operators,&,|, and^
  • The conditional operator,?:(for the second and third operands)

这里隐式转换的规则为

64bit programming-type convert 2.jpg

有时候符号位和类型大小都需要隐式转换

int+ long -> long
unsigned + signed -> unsigned

这时候转换的顺序是
当一个负的整型提升为无符号的相同或更大类型的整型时,它首先提升为更大类型的有符号整型,然后再转换成无符号类型,比如,

inta = -1;
unsigned long long b = 2;
unsigned long long c;
c = a + b; 
  1. a先转化成signed long long (0xFFFF_FFFF_FFFF_FFFF), -1LL
  2. a再转化成 unsigned long long, (0xFFFF_FFFF_FFFF_FFFF)

另外在使用一个数字常量时

  • 整型常量(除非有后缀,比如8LL)会被当成是可以表示这个值的最小类型,比如8 会是 int型
  • 如果这个常数是16进制的,编译器可以当成有符号或是无符号数来处理
  • 十进制的常数总会被当成有符号数来处理

必须了解的64位编程知识 (2)

推荐阅读
关注数
8683
文章数
55
mindshare_zenon
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息