Golang也就是Go语言,现在已经发行到1.4.1版本了,语言特性优越性和背后Google强大靠山什么的就不多说了。Golang的官方提供了多个平台上的二进制安装包,遗憾的是并非没有发布ARM平台的二进制安装包。ARM平台没办法直接从官网下载二进制安装包来安装,好在Golang是支持多平台并且开源的语言,因此可以通过直接在ARM平台上编译源代码来安装。整个过程主要包括编译工具配置、获取Golang源代码、设置Golang编译环境变量、编译、配置Golang行环境变量等步骤。
注:本文选用树莓派做测试,因为树莓派是基于ARM平台的。
1、编译工具配置
据说下个版本的golang编译工具要使用golang自己来写,但目前还是使用C编译工具的。因此,首先要配置好C编译工具:
1.1 在Ubuntu或Debian平台上可以使用sudo apt-get install gcc libc6-dev命令安装,树莓派的RaspBian系统是基于Debian修改的,所以可以使用这种方法安装。
1.2 在RedHat或CentOS 6平台上可以使用sudo yum install gcc libc-devel命令安装。
安装完成后可以输入 gcc --version命令验证是否成功安装。
2、获取golang源代码
2.1 直接从官网下载源代码压缩包。
golang官网提供golang的源代码压缩包,可以直接下载,最新的1.4.1版本源代码链接:https://storage.googleapis.com/golang/go1.4.1.src.tar.gz
2.2 使用git工具获取。
golang使用git版本管理工具,也可以使用git获取golang源代码。推荐使用这个方法,因为以后可以随时获取最新的golang源代码。
2.2.1 首先确认ARM平台上已经安装了git工具,可以使用git --version命令确认。一般linux平台都安装了git,没有的话可以自行安装,不同平台的安装方法可以参考:http://git-scm.com/download/linux
2.2.2 克隆远程golang的git仓库到本地
在终端cd到你想要安装golang的目录,确保该目录下没有名为go的目录。然后以下命令获取代码仓库:
git clone https://go.googlesource.com/go
大陆地区可能会获取失败,在不翻墙的情况下我试了几次都没成功,原因大家都懂的。好在google已经将golang也托管到github上面,所以也可以通过下面命令获取:
git clone https://github.com/golang/go.git
视网络情况,下载可能需要不少时间。我2M的带宽花了将近两个小时才下载完,虽然整个项目不过几十兆= =
下载完成后,可以看到目录下多了一个go目录,里面即为golang的源代码,在终端上执行cd go命令进入该目录。
执行下面命令检出go1.4.1版本的源代码,因为现在已经有新的代码提交上去了,最新的代码可能不是最稳定的:
git checkout go1.4.1
至此,最新1.4.1发行版的源代码获取完毕
3、设置golang的编译环境变量
主要有GOROOT、GOOS、GOARCH、GOARM四个环境变量需要设置,先解释四个环境变量的意义。
3.1 GOROOT
主要代表golang树结构目录的路径,也就是上面git检出的go目录。一般可以不用设置这个环境变量,因为编译的时候默认会以go目录下src子目录中的all.bash脚本运行时的父目录作为GOROOT的值。为了保险起见,可以直接设置为go目录的路径。
3.2 GOOS和GOARCH
分别代表编译的目标系统和平台,可选值如下:
GOOS GOARCH
darwin 386
darwin amd64
dragonfly 386
dragonfly amd64
freebsd 386
freebsd amd64
freebsd arm
linux 386
linux amd64
linux arm
netbsd 386
netbsd amd64
netbsd arm
openbsd 386
openbsd amd64
plan9 386
plan9 amd64
solaris amd64
windows 386
windows amd64
需要注意的是这两个值代表的是目标系统和平台,而不是编译源代码的系统和平台。树莓派的RaspBian是linux系统,所以这些GOOS设置为linux,GOARCH设置为arm。
3.3 GOARM
表示使用的浮点运算协处理器版本号,只对arm平台有用,可选值有5,6,7。如果是在目标平台上编译源代码,这个值可以不设置,它会自动判断需要使用哪一个版本。
总结下来,在树莓派上设置golang的编译环境变量,可编辑$HOME/.bashrc文件,在末尾添加下面内容:
export GOROOT=你的go目录路径
export GOOS=linux
export GOARCH=arm
编辑完后保存,执行source ~/.bashrc命令让修改生效。
4、编译源代码
环境变量配置完成自后就可以开始编译源代码。在go目录下的src子目录中,主要有all.bash和make.bash两个脚本(另外还有两个all.bat和make.bat脚本适用于window平台)。编译实际上就是执行其中一个脚本,两者的区别在于all.bash在编译完成后还会执行一些测试套件。如果希望只编译不测试,可以运行make.bash脚本。使用cd命令进入go下src目录,执行./all.bash或者./make.bash命令即可开始编译。由于硬件情况不同,编译耗费的时间不同。在我的B型树莓派编译过程花费了将近半个小时,编译完成后执行的测试套件又花费了差不多一个小时,总共花费了一个半小时左右。
5、配置golang运行环境变量
编译完成后,go目录下会生成bin目录,里面就是go的运行脚本。为了以后使用方法,可以将这个bin路径添加到PATH环境变量中。同样编辑~/.bashrc文件,因为前面设置过GOROOT环境变量指向go目录了,所以只需要在末尾加上
export PATH=$PATH:$GOROOT/bin
保存后同样执行source ~/.bashrc命令让环境变量生效。
至此,golang源代码编译安装成功。执行go version应该就能看到当前golang的版本信息,表示编译安装成功。
⑵ iOS底层原理08-dyld加载流程
本文的目的主要是分析dyld的加载流程,了解在main函数之前,底层还做了什么
引子创建一个project,在ViewController中重写了load方法,请问它们的打印先后顺序是什么?
运行程序,查看 load、main的打印顺序,下面是打印结果,通过结果可以看出其顺序是 load ?--> main
为什么是这么一个顺序?按照常规的思维理解,main不是入口函数吗?为什么不是main最先执行?
下面根据这个问题,我们来探索在走到main之前,到底还做了什么。
编译过程及库在分析app启动之前,我们需要先了解iOSapp代码的编译过程以及动态库和静态库。
编译过程其中编译过程如下图所示,主要分为以下几步:
源文件:载入.h、.m、.cpp等文件
预处理:替换宏,删除注释,展开头文件,产生.i文件
编译:将.i文件转换为汇编语言,产生.s文件
汇编:将汇编文件转换为机器码文件,产生.o文件
链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
静态库 和 动态库静态库:在链接阶段,会将可汇编生成的目标程序与引用的库一起链接打包到可执行文件当中。此时的静态库就不会在改变了,因为它是编译时被直接拷贝一份,复制到目标程序里的
好处:编译完成后,库文件实际上就没有作用了,目标程序没有外部依赖,直接就可以运行
缺点:由于静态库会有两份,所以会导致目标程序的体积增大,对内存、性能、速度消耗很大
动态库:程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入
减少打包之后app的大小:因为不需要拷贝至目标程序中,所以不会影响目标程序的体积,与静态库相比,减少了app的体积大小
共享内存,节约资源:同一份库可以被多个程序使用
通过更新动态库,达到更新程序的目的:由于运行时才载入的特性,可以随时对库进行替换,而不需要重新编译代码
优势:
缺点:动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境,如果环境缺少了动态库,或者库的版本不正确,就会导致程序无法运行
静态库和动态库的图示如图所示
dyld加载流程分析根据dyld源码,以及libobjc、libSystem、libdispatch源码协同分析
什么是dyld?
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的重要组成部分,在app被编译打包成可执行文件格式的Mach-O文件后,交由dyld负责连接,加载程序
所以 App的启动流程图如下
app启动的起始点在前文的demo中,在load方法处加一个断点,通过bt堆栈信息查看app启动是从哪里开始的
【app启动起点】:通过程序运行发现,是从dyld中的_dyld_start开始的,所以需要去OpenSource下载一份dyld的源码来进行分析
也可以通过xcode左侧的堆栈信息来找到入口
dyld::_main函数源码分析在dyld-750.6源码中查找_dyld_start,查找arm64架构发现,是由汇编实现,通过汇编注释发现会调用dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)方法,是一个C++方法(以arm64架构为例)
源码中搜索dyldbootstrap找到命名作用空间,再在这个文件中查找start方法,其核心是返回值的调用了dyld的main函数,其中macho_header是Mach-O的头部,而dyld加载的文件就是Mach-O类型的,即Mach-O类型是可执行文件类型,由四部分组成:Mach-O头部、Load Command、section、Other Data,可以通过MachOView查看可执行文件信息
进入dyld::_main的源码实现,特别长,大约900多行,如果对dyld加载流程不太了解的童鞋,可以根据_main函数的返回值进行反推,这里就多作说明。在_main函数中主要做了一下几件事情:
【第一步:环境变量配置】:根据环境变量设置相应的值以及获取当前运行架构
【第二步:共享缓存】:检查是否开启了共享缓存,以及共享缓存是否映射到共享区域,例如UIKit、CoreFoundation等
【第三步:主程序的初始化】:调用instantiateFromLoadedImage函数实例化了一个ImageLoader对象
【第四步:插入动态库】:遍历DYLD_INSERT_LIBRARIES环境变量,调用loadInsertedDylib加载
【第五步:link 主程序】
【第六步:link 动态库】
【第七步:弱符号绑定】
【第八步:执行初始化方法】
【第九步:寻找主程序入口即main函数】:从Load Command读取LC_MAIN入口,如果没有,就读取LC_UNIXTHREAD,这样就来到了日常开发中熟悉的main函数了
下面主要分析下【第三步】和【第八步】
第三步:主程序初始化sMainExecutable表示主程序变量,查看其赋值,是通过instantiateFromLoadedImage方法初始化
进入instantiateFromLoadedImage源码,其中创建一个ImageLoader实例对象,通过instantiateMainExecutable方法创建
进入instantiateMainExecutable源码,其作用是为主可执行文件创建映像,返回一个ImageLoader类型的image对象,即主程序。其中sniffLoadCommands函数时获取Mach-O类型文件的Load Command的相关信息,并对其进行各种校验
第八步:执行初始化方法进入initializeMainExecutable源码,主要是循环遍历,都会执行runInitializers方法
全局搜索runInitializers(cons,找到如下源码,其核心代码是processInitializers函数的调用
进入processInitializers函数的源码实现,其中对镜像列表调用recursiveInitialization函数进行递归实例化
在这里,需要分成两部分探索,一部分是notifySingle函数,一部分是doInitialization函数,首先探索notifySingle函数
notifySingle 函数全局搜索notifySingle(函数,其重点是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());这句
全局搜索sNotifyObjCInit,发现没有找到实现,有赋值操作
搜索registerObjCNotifiers在哪里调用了,发现在_dyld_objc_notify_register进行了调用
注意:_dyld_objc_notify_register的函数需要在libobjc源码中搜索
在objc4-781源码中搜索_dyld_objc_notify_register,发现在_objc_init源码中调用了该方法,并传入了参数,所以sNotifyObjCInit的赋值的就是objc中的load_images,而load_images会调用所有的+load方法。所以综上所述,notifySingle是一个回调函数
load函数加载
下面我们进入load_images的源码看看其实现,以此来证明load_images中调用了所有的load函数
通过objc源码中_objc_init源码实现,进入load_images的源码实现
进入call_load_methods源码实现,可以发现其核心是通过do-while循环调用+load方法
进入call_class_loads源码实现,了解到这里调用的load方法证实我们前文提及的类的load方法
所以,load_images调用了所有的load函数,以上的源码分析过程正好对应堆栈的打印信息
【总结】load的源码链为:_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> dyld::notifySingle(是一个回调处理) --> sNotifyObjCInit --> load_images(libobjc.A.dylib)
那么问题又来了,_objc_init是什么时候调用的呢?请接着往下看
doInitialization 函数走到objc的_objc_init函数,发现走不通了,我们回退到recursiveInitialization递归函数的源码实现,发现我们忽略了一个函数doInitialization
进入doInitialization函数的源码实现
这里也需要分成两部分,一部分是doImageInit函数,一部分是doModInitFunctions函数
进入doImageInit源码实现,其核心主要是for循环加载方法的调用,这里需要注意的一点是,libSystem的初始化必须先运行
进入doModInitFunctions源码实现,这个方法中加载了所有Cxx文件
走到这里,还是没有找到_objc_init的调用?怎么办呢?放弃吗?当然不行,我们还可以通过_objc_init加一个符号断点来查看调用_objc_init前的堆栈信息,
_objc_init加一个符号断点,运行程序,查看_objc_init断住后的堆栈信息
在libsystem中查找libSystem_initializer,查看其中的实现
根据前面的堆栈信息,我们发现走的是libSystem_initializer中会调用libdispatch_init函数,而这个函数的源码是在libdispatch开源库中的,在libdispatch中搜索libdispatch_init
进入_os_object_init源码实现,其源码实现调用了_objc_init函数
结合上面的分析,从初始化_objc_init注册的_dyld_objc_notify_register的参数2,即load_images,到sNotifySingle --> sNotifyObjCInie=参数2 到sNotifyObjcInit()调用,形成了一个闭环
所以可以简单的理解为sNotifySingle这里是添加通知即addObserver,_objc_init中调用_dyld_objc_notify_register相当于发送通知,即push,而sNotifyObjcInit相当于通知的处理函数,即selector
【总结】:_objc_init的源码链:_dyld_start --> dyldbootstrap::start --> dyld::_main --> dyld::initializeMainExecutable --> ImageLoader::runInitializers --> ImageLoader::processInitializers --> ImageLoader::recursiveInitialization --> doInitialization -->libSystem_initializer(libSystem.B.dylib) --> _os_object_init(libdispatch.dylib) --> _objc_init(libobjc.A.dylib)
第九步:寻找主入口函数跑完了整个流程,会回到_dyld_start,然后调用main()函数,通过汇编完成main的参数赋值等操作
所以,综上所述,最终dyld加载流程,如下图所示,图中也诠释了前文中的问题:为什么是load-->main的调用顺序
原文:https://juejin.cn/post/7096170825136472078⑶ zlib开发笔记(三):zlib库介绍、在ubuntu上进行arm平台交叉编译
本文主要介绍了zlib库的特性、下载方法,以及在ubuntu上进行arm平台的交叉编译步骤。zlib是一个免费、通用且不受专利保护的数据压缩库,适用于各种硬件和操作系统。以下是具体的操作流程:
首先,确保你的交叉编译环境已准备就绪,包括制作交叉编译链工具并将其添加到环境变量中。在终端中,创建环境文件`env.sh`,并编辑它以包含交叉编译器的路径。启动控制台并运行`source ../tool/env.sh`来引入这些设置。
接下来,解压zlib库(如zlib-1.2.11.tar.gz),并使用交叉编译器进行配置和编译。在`makefile`中可能需要修改CC的设置以确保正确连接库。执行`make`进行编译,然后用`sudo make install`进行安装。
模块化部分,虽然Ubuntu上对库的模块化不包含库,你可以参考《zlib开发笔记(一)》了解更多信息。此外,工程模板zipDemo_v1.0.0_基础模板可供参考,但可能需要根据ubuntu系统的问题进行适当调整。
以上就是在ubuntu上进行arm平台zlib库交叉编译的详细步骤,如果你对其他平台或版本有疑问,可以查看《zlib开发笔记(二)》或期待后续的文章更新。
⑷ 【图文】鲲鹏916-ARM64架构源码gcc编译完整记录
以下是关于ARM64架构源码gcc编译的详细步骤记录:
首先,确保已经准备就绪,如果cmake未安装,需要进行安装。检查cmake版本以确认其是否满足需求。
安装必要的依赖包,如isl、gmp、mpc、mpfr等,检查它们是否已成功安装。
针对gcc版本过低的问题,需下载并更新到7.3版本。下载并解压gcc7.3的安装包。
在gcc-7.3.0目录下,确认已下载和安装了所有依赖包。
利用多核CPU的优势,通过“-j32”参数加速编译过程。原先是按照官方文档使用make -j16,但速度缓慢,后来调整为make -j32以提升效率。
依次执行编译目录创建、gcc编译、安装以及确认“libstdc++.so”软连接在正确的目录(/usr/lib64)。
编译完成后,通过查看gcc版本来确认安装是否成功。
以上就是完整的gcc编译安装流程。如果您觉得这些信息对您有所帮助,欢迎分享和关注我们的更新。更多技术内容敬请期待,感谢您的支持!
⑸ arm汇编跟汇编、C语言的区别
不一样,汇编主要是要了解CPU指令及用法。
我们常说的是PC机的x86汇编,指令是x86的复杂指令集。
arm汇编是arm的精简指令集,比x86容易学,程序格式倒是和x86汇编差不多。你下载一份arm的手册就可以了解了。
C语言ARM的和x86的差不多,除了对硬件寄存器操作不同,其它语法和流程都一样。
回答补充:
arm汇编程序每一行是指定arm
core执行一条指令,每条指令都是硬件相关。
如
LDR
R3,
#1
;用LDR指令将数值1放入R3寄存器准备参与运算
C语言与arm指令无关,只与逻辑运算有关,指定硬件地址的操作才与硬件相关;如果用arm编译器来编译,每行可能编译出1到多条arm指令。
如
i++;
//变量
i
递增1
等效于
LDR
R3,#1
;用LDR指令将数值1放入R3寄存器准备参与运算
ADD
R2,
R2,
R3
;用ADD指令将R2、R3寄存器里的数值相加后放回R2寄存器
以上等效汇编的R2、R3寄存器只是为了举例,C语言不像汇编,不需要由程序员指定用哪个寄存器参与运算,编译器编译时会根据程序结构自动判断选择。
强调mcuos有错,“c语言被编译器编译的时候会最终解释为汇编语言的”,无论是c语言还是汇编语言,编译器编译后的结果是机器执行码,很多人因为汇编语言比较难懂及指令相关,所以以为它就是机器语言,其实它仍是人类设计的编写程序的语言,仍需要编译器编译成机器码才能执行,它只是比C语言更接近硬件而已。
⑹ linuxarm开发需要全部重编译吗
不需要。
根据查询知乎网显示,在LinuxARM开发中,只需要针对新的ARM架构平台进行编译即可,与ARM架构无关的代码并不需要重新编译。