目录
一、介绍
(一)、应用迁移
应用迁移是指将软件应用从一个计算环境迁移到另一个环境的过程。例如,可以将应用从一个数据中心迁移到另一个数据中心,从本地服务器迁移到云提供商环境,或从公有云迁移到私有云环境。由于应用通常设计为在特定网络架构中的特定操作系统上运行,或者是针对单一云平台开发的,因此将应用迁移到新环境可能会带来一系列挑战。
在迁移过程中,需要考虑以下几个方面:
- 硬件评估:评估目标环境的硬件设备是否满足应用运行的要求,包括CPU、内存、存储等。
- 软件评估:评估目标环境的操作系统、数据库、中间件等软件环境是否与应用兼容。
- 迁移策略:选择适合应用的迁移策略,如在线迁移、离线迁移等。
- 迁移测试:在迁移完成后,对应用进行测试以确保其在新环境中能够正常运行。
(二)、应用适配
应用适配是指将软件或系统适应不同的使用环境、使用条件或用户需求的一种技术手段。在不同的应用场景下,可能需要调整软件的功能、界面、交互方式等方面,以最大程度地满足用户的需求。
应用适配主要包括硬件适配和软件适配两种方式:
- 硬件适配:将软件与硬件设备相结合,针对不同的硬件设备进行适配。例如,对于不同的操作系统、不同的硬件设备,可能需要调整软件的安装、配置和运行方式。
- 软件适配:根据不同的应用场景,对软件进行相应的调整,以更好地适应不同的用户需求。例如,可以通过调整软件的界面、交互方式、功能设置等来满足不同用户的需求。
在实现应用适配时,可以采用一些技术手段来确保应用在不同环境下的兼容性,如使用响应式布局、百分比布局、资源限定符等。此外,还可以使用一些工具来辅助实现应用适配,如CSS媒体查询、Android平台的资源限定符机制等。
二、为什么要软件迁移
x86 和鲲鹏 ARM 架构之间最大的区别源自于指令集的不同。以下图的 C/C++程序代码中的“c=a+b”语句为例,我们可以看到其通过编译器编译汇编指令后.
应用需要迁移的原因:
1.汇编代码角度
同样的C语言代码,在编译成不同架构下的程序后,得到的汇编代码是不同的,这些不同点主要体现在以下3个方面:
1.1 处理器指令
以简单的给变量赋值操作为例:
(1)鲲鹏架构。
首先使用mov指令把操作数传送给寄存器,然后使用ldr指令把寄存器的值传送到内存。
(2)x86架构。
直接使用movl指令把操作数传送到内存。
鲲鹏架构和x86架构在具体的处理器指令设计上,是有重大区别的,同样的功能,两个架构的处理器指令实现的方式可能不一样。
1.2 寄存器
这一点也比较明显,两个架构下的寄存器不管是数量还是功能都有所不同。
(1)鲲鹏架构。
ARM 64有34个寄存器,其中编号x0~x29是通用寄存器,x30为程序链接寄存器,x31比较特殊,它有时候用作xzr零寄存器,有时候是栈指针寄存器sp,两者不能在一条指令里共存,另外两个寄存器是程序计数器PC、状态寄存器CPSR。
ARM 64寄存器x0~x30及xzr零寄存器都是64位的,它们的低32位构成了32位寄存器,分别用w0~w30表示,用wzr表示32位下的零寄存器。
除此之外,ARM 64还有浮点寄存器和向量寄存器。
(2)x86-64架构。
x86-64架构下有16个64位的通用寄存器,这些寄存器支持访问低位,例如访问低8位、低16位、低32位。
1.3 指令长度
x86下指令的长度是不一样的,短的只有1字节,而长的却有15字节,给寻址带来了一定的不便。
鲲鹏架构指令长度为固定的32位(ARM工作状态),寻址方便,效率较高。
三、怎么迁移
(一)、C/C++编译型语言迁移
C/C++作为典型的编译型语言,由于架构、指令集、向量寄存器的差异,程序在从 x86→鲲鹏处理器时,必须经过重新编译才能运行。
从代码工程的角度来看,C/C++的文件分为两类,一是编译构建的脚本,二是源码。其中:
1、编译构建脚本类文件在迁移过程中一般会涉及编译选项的移植,包括指定数据类型、生成代码特性、目标执行器架构、处理器硬件加速功能等。
2、源码文件的迁移,一般会涉及到编译宏的移植、编译器自带 builtin 函数移植、内联汇编移植、SSE intrinsic 函数移植等。
下图展示了 C/C++代码完整的编译构建过程。首先通过 GitHub 和第三方开源社区获取相应的源码。其次安装 gcc 版本,准备编译环境。之后使用源码中的 CMakeLists.txt 或 configure 脚本生成 makefile。随后执行 makefile 编译可执行程序,并替换依赖库。最后,将可执行程序安装部署到生产或测试系统。
在Arm架构(鲲鹏920)的麒麟操作系统V10
步骤1 编写C语言Hello World
#vim hello.c
#include <stdio.h>
int main()
{
printf("hello world");
return 0;
}
步骤2 GCC编译
#gcc hello.c -E -o hello.i
可以通过cat 查看hello.i 会发现加入很多额外的代码。
1、gcc hello.c -S -c -o hello.s 包含预处理,将 C 程序转换成汇编程序
-S 编译到汇编语言,不进行汇编和链接
-c 编译、汇编到目标代码,不进行链接
#gcc hello.c -S -c -o hello.s
2、gcc hello.c -c -o hello.o 汇编:包含预处理和编译,将汇编程序转换成可链接的二进制程序。
#gcc hello.c -c -o hello.o
因为.o文件是二进制文件,所以直接用cat查看,看到的是乱码。
3、gcc hello.c -o hello 链接:包含以上所有操作,将可链接的二进制程序和其它别的库链接在一起,形成可执行的程序文件。
#gcc hello.c -o hello
我们可以通过objdump来查看
#objdump -d hello.o
以上图中,分成三部分:
第一部分为地址;
第二部分为十六进制,表示真正装入机器中的代码数据;
第三部分是对应的汇编代码;
这是 x86_64 体系的代码,由此可以看出 x86 CPU 是变长指令集。
小结:
对于C/C++为代表的编译型语言来说,移植方法一般包括两种,也就是源代码修改和编译选项修改,有时候使用其中一种方式,有时候两种方式需要同时使用。
(二)、Java解释型语言移植
查询本地的java环境
分别在X86架构的麒麟操作系统V10,Arm架构(鲲鹏920)的麒麟操作系统V10环境下执行以下命令
步骤1 查看本机已经安装的java版本。
#yum search java
麒麟默认提供了java1.8,java-11两套java开发环境。可以依据实际情况自行确定。
我们这里选用java1.8作为开发环境
步骤2 安装java的相关开发环境
yum install java-1.8.0-openjdk java-1.8.0-openjdk-devel java-1.8.0-openjdk-headless -y
本地安装java1.8的环境
yum remove java-11-openjdk-devel java-11-openjdk java-11-openjdk-headless -y
删除java-11
步骤3 验证java,javac的版本
[root@jd4 tmp]# java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment Bisheng (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM Bisheng (build 25.292-b10, mixed mode)
[root@jd4 tmp]# javac -version
javac 1.8.0_292
在两台主机上均完成以上操作,则可以进入下一步骤。
纯java代码迁移
x86架构下完成以下操作,配套代码可以在java_demo目录中查找
步骤1 编写java测试代码
mkdir -pv /usr/local/src/code/java_demo
cd /usr/local/src/code/java_demo
vim Demo.java
import java.util.Scanner;
public class Demo{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String input = sc.nextLine();
System.out.println(input);
sc.close();
}
}
步骤2 编译并执行
[root@node1 java_demo]#javac Demo.java
[root@node1 java_demo]# ls
Demo.class Demo.java
java Demo
你输入什么,就会显示什么,如下图
在x86架构下编译及运行没问题了,把这个编译好的.class文件 复制到鲲鹏架构的服务器上。
步骤3 移植java代码
把x86架构下环境下编写的java代码拷贝到Arm架构环境。在Arm 环境完成以下操作:
先确认环境
[root@jd4 tmp]# java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment Bisheng (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM Bisheng (build 25.292-b10, mixed mode)
[root@jd4 tmp]# javac -version
javac 1.8.0_292
拷贝java代码
在x86主机执行
[root@node1 java_demo]# scp Demo.class 179.119.224.2:/tmp
The authenticity of host '179.119.224.2 (179.119.224.2)' can't be established.
ECDSA key fingerprint is SHA256:QBfezyONvuiNbSmao2ml3MjaZX/BMllINTtPJ4TetJk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '179.119.224.2' (ECDSA) to the list of known hosts.
Authorized users only. All activities may be monitored and reported.
root@179.119.224.2's password:
Demo.class
这里的ip地址请根据实际情况修改纯文
步骤4 执行java代码,并验证
在arm主机执行
[root@jd4 ~]# cd /tmp/
[root@jd4 tmp]# java Demo
hello java
hello java
Netty是开源的项目,在获得所有的源代码后,可以通过对代码 进行重新编译的方式来执行迁移。因为代码里有Java和C语言,并且 Netty项目是通过Pom进行项目组织管理的,在迁移时不但要安装C的 编译环境,还要安装openjdk和Maven。
步骤1 通过yum 安装依赖环境
#yum install gcc gcc-c++ make cmake3 libtool autoconf automake
ant wget git openssl openssl-devel apr-devel ninja-build -y
步骤2 安装Maven
下载Maven
#cd /usr/local/src
#wget http://archive.apache.org/dist/maven/maven-3/3.5.4/
binaries/apache-maven-3.5.4-bin.tar.gz
解压缩
#cd /usr/local/src
#tar fvxz apache-maven-3.5.4-bin.tar.gz
#mv apache-maven-3.5.4 /opt/maven
修改环境变量
vim /etc/profile
MAVEN_HOME=/opt/maven
PATH=$MAVEN_HOME/bin:$PATH
export MAVEN_HOME PATH
#执行下面的命令更新环境变量
source /etc/profile
查看版本
mvn -version
修改maven的配置文件,更改镜像仓库网址为国内的镜像网址。
vim /opt/maven/conf/settings.xml
<mirror>
<id> huawei </id>
<name>huawei maven</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>
步骤3 修改gcc、g++ 默认编译选项
鲲鹏架构中char类型问题
设置gcc和g++的编译选项来处理,也就是把这两个编译器的编译 加上-fsigned-char的选项。
给gcc加一个壳
[root@jd4 src]# which gcc
/usr/bin/gcc
[root@jd4 src]# mv /usr/bin/gcc /usr/bin/gcc_bak cp
[root@jd4 src]# vim /usr/bin/gcc
#!/usr/bin/env bash
/usr/bin/gcc_bak -fsigned-char "$@"
[root@jd4 src]# chmod +x /usr/bin/gcc
[root@jd4 src]# gcc --version
gcc_bak (GCC) 7.3.0
Copyright © 2017 Free Software Foundation, Inc.
看到下图,就代表gcc 加壳替换成功了。
小结:
在一个Java程序执行时,首先通过javac把java文件编译为虚拟机可以识别的class文件。 然后由JVM解释器解释class文件中的字节码,通过JVM把解释结果转变为机器码执行。 这是我们通常所说的解释执行。所以,纯Java语言编写的程序不需要重新编译。有依赖的So库需要迁移,可以先安装好编译需要的GCC与Maven,然后在新平台下重新编译So库。
CPU 和内存
CPU 和内存优化的两大方向分别是软加速和硬加速,软加速主要涉及编译优化、NUMA-Aware 亲和性优化。编译器优化主要是针对鲲鹏芯片的微架构,优化了寄存器的分配、指令的部署和流水,提升大部分指令的执行效率。具体来讲,主要分为布局优化、内存布局优化、以及循环优化等;NUMA-Aware 主要是减少内存的跨片和跨 NUMA 访问时产生时延,提高多核架构下面的并行度。此外,硬件加速则是鲲鹏处理器为性能提升提供的一个核武器。
磁盘
因为内存和 CPU 的高速缓存大小都是有限的,我们需要访问的数据大多存在磁盘上或者网络存储上。因此文件系统决定了磁盘加载到内存过程的快慢。华为鲲鹏提供了 EX3、EX14、以及 XFS 等数据访问的管理模式,能够有效提高访问磁盘的性能。
网卡
主要需要根据实际应用场景来调整 frames 和 usecs 两个特性,根据延时和带宽需求来权衡控制两个参数。
应用层
开发者可以提高并发数、优化缓存操作、启用异步读写等,针对鲲鹏平台的系统特性进行整体配置。
在具体的性能调优实践中,第一步就是选择调优方向,包括三大块硬件和应用;第二步是性能采集、分析性能瓶颈、定位热点函数;第三步是针对平台特性充分利用硬件资源;第四步是要在网络端寻求合适的参数平衡点。
- 编译型语言
编译型语言的代表是C/C++等语言,使用编译型语言编写的源代码经过一系列的编译过程,最终才能生成可执行程序,因为不同架构下指令集不同,导致依赖于指令集的二进制机器码、汇编语言都不同,所以同一段程序,在不同的架构下,需要最终编译成和架构相适应的二进制机器码。
- 解释型语言
解释型语言的代表是Java/Python等语言,Java的源代码会被编译成字节码,字节码运行在Java虚拟机JVM上,JVM具有与平台架构无关的指令集,同一段Java代码在不同的架构下都可以编译成相同的字节码。JVM对字节码进行解释,转换为物理CPU对应的机器码进行实际执行。因为不同的指令集架构可以适配不同的JVM实现,所以Java等解释型语言只需编译一次,就可以到处运行,不同架构下的JVM屏蔽了指令集之间的差异。
如果一个应用是使用纯Java语言编写的,理论上基本可以跨平台运行,但是实际情况比较复杂,有些Java应用会引用so库文件,这些so库文件很有可能是通过编译型语言例如C来编写的,这时候就要考虑so库文件的移植。
总结
应用迁移技术
定义:
应用迁移是指将现有的软件应用从一个计算环境(如硬件、操作系统、云平台等)迁移到另一个环境的过程。
主要挑战:
- 兼容性:确保应用在新环境中与硬件、操作系统、数据库和其他依赖项兼容。
- 数据迁移:迁移应用所依赖的数据,并确保数据的完整性和安全性。
- 性能优化:优化应用在新环境中的性能,以确保其满足业务需求。
- 业务连续性:在迁移过程中确保业务的连续性,尽量减少停机时间。
迁移策略:
- 离线迁移:在关闭应用后进行迁移,通常适用于对停机时间要求不高的场景。
- 在线迁移:在保持应用运行的同时进行迁移,对技术要求较高,但可确保业务的连续性。
实施步骤:
- 评估:评估现有环境和目标环境,确定迁移的可行性和风险。
- 规划:制定详细的迁移计划,包括时间表、资源分配、风险应对措施等。
- 测试:在迁移前对应用进行充分的测试,确保其在目标环境中能够正常运行。
- 执行:按照计划执行迁移操作。
- 验证:在迁移完成后对应用进行验证,确保其在新环境中满足业务需求。
应用适配技术
定义:
应用适配是指根据目标环境的特点和用户需求,对软件应用进行调整和优化,以确保其能够在不同环境中正常运行并满足用户需求。
主要挑战:
- 硬件适配:确保应用能够在不同的硬件设备上正常运行。
- 软件适配:确保应用能够与不同的操作系统、数据库、中间件等软件环境兼容。
- 用户界面适配:确保应用的用户界面能够在不同的设备(如手机、平板、电脑等)和操作系统上正确显示和操作。
适配策略:
- 使用标准接口:通过采用标准的硬件和软件接口,减少适配的复杂性。
- 多版本支持:为不同的操作系统和硬件平台提供不同的应用版本。
- 响应式设计:使用响应式设计技术,使应用的用户界面能够自动适应不同的设备和屏幕尺寸。
实施步骤:
- 需求分析:了解目标环境的特点和用户需求。
- 设计适配方案:根据需求分析结果,设计适配方案,包括硬件适配、软件适配和用户界面适配等。
- 开发:按照适配方案进行开发,实现应用的适配功能。
- 测试:在目标环境中对应用进行测试,确保其能够适应不同的环境和满足用户需求。
- 发布:将适配后的应用发布到目标环境,供用户使用。