1. 原理简介
1)
Prelink
Prelink
即预链接技术是利用事先链接以代替运行时链接的技术,以加快共享库的加载速度,它不仅能加快程序启动时间,还可以减少部分内存开销(它能使
KDE
的启动时间减少
50%
)。每次程序执行时,进行的链接动作都是一样的,链接相对来说开销很大,尤其是嵌入式系统。
2)
普通
Linux
系统的
Prelink
Redhat
系统中
prelink
工具
(/etc/cron.dialy/prelink)
会修改可执行程序,把它与所需库的链接信息加入可执行程序。在程序运行时,使用
glibc(glibc > 2.3.1-r2)
中的
ld-linux.so
来进行链接。用此方式,每次更新动态库后,使用它的程序都需要重新
prelink
,因为新库中的符号信息,地址等很可能与原来不同了。
3)
Android
的
Prelink
Android
源码中有一组
map
文件,其中定义了需要预连接的动态库,其
Prelink
信息以及对应的逻辑地址(
4G
地址空间中位置),在动态库编译时,预处理程序
apriori
根据
map
文件中的定义,生成预链接信息重定向信息,并加入这些二进制文件
lib*.so
的末尾。它主要节约了查询函数地址等工作所用的时间,动态库重定位的开销。在运行程序,动态库加载时,加载程序
linker
判断动态库是否为
Prelink
的,如果是的话,就在首次使用时将其加载到指定的内存空间,直接使用预编译信息。
2. 源码分析
1) 动态库的编译脚本
a)
源代码
frameworks/base/media/libmedia/Android.mk
等库的编译选项文件
b)
配置
可在
Android.mk
中设置该库是否需要
Prelink
,默认是使用
Prelink
的,也可设置成否,方法如下:
LOCAL_PRELINK_MODULE := false
c)
分析
此设置只用于动态库的编译,编译时用
showcommands
参数即可看到具体编译使用到的命令行,如在某个库的目录中运行
mm showcommands
,即可看到相应的
Prelink
操作,示例如下:
target Prelink: libxxx
out/host/linux-x86/bin/apriori --prelinkmap build/core/prelink-linux-arm-2G.map --locals-only --quiet xxx.so --output xxx.so
如果该库设置为需要
prelink
,则也需要在
map
文件中加入相应项,否则编译不通过
2) 内核
a)
源码
kernel/arch/arm/configs/xxx_defconfig
arch/arm/mach-msm/include/mach/vmalloc.h
b)
配置
CONFIG_VMSPLIT_2G=y
一般默认为
3G
/1G
,此项
即设置为
2G
/2G
c)
分析
xxx_defconfig
为默认的内核配置文件
(
修改其中的
CONFIG_VMSPLIT_*)
,也可通过
make menuconfig
配置,与
Prelink
相关的主要是指定用户空间和内核空间内存如何分配
4G
的虚拟内存空间(
Memory split
),一般有三种方式:
3G
/1G
,
2G
/2G
,
1G
/3G(user/kernel)
,一般默认的是用户空间
3G
(0x0-0xBFFFFFFF)
,内核空间
1G
(0xC0000000 - 0xFFFFFFFF)
d)
内存分析示例,以
3G
/1G
为例
(
见
build/core/prelink-linux-arm.map)
0xC0000000 - 0xFFFFFFFF Kernel
0xB0100000 - 0xBFFFFFFF Thread 0 static
0xB0000000 - 0xB00FFFFF Linker
0xA0000000 - 0xBFFFFFFF Prelinked System Libraries
0x90000000 - 0x9FFFFFFF Prelinked App Libraries
0x80000000 - 0x8FFFFFFF Non-prelinked Libraries
0x40000000 - 0x7FFFFFFF mmap’s stuff
0x10000000 - 0x3FFFFFFF Thread Stacks
0x00000000 - 0x0FFFFFFF .text / .data / heap
3) map 文件
a)
源代码
build/core/prelink-linux-<arch>*.map
b)
配置
编译时有多个
map
文件可先,根据不同的硬件平台及内存分配
(3G/1G, 2G/2G)
修改系统配置
device/*/BoardConfig.mk
选择使用不同的
map
文件
.
如
prelink-linux-arm-2G.map, prelink-linux-arm.map
系统中每加入一个需要
prelink
的动态库,需要在
map
文件添加相应的项
c)
分析
在用户空间,共定义了三块与链接相关地址间空
系统库分配的内存:
Prelinked System Libraries
应用库分配的内存:
Prelinked App Libraries
无
prelink
库分配的内存:
Non-prelinked Libraries
d)
map
文件规则
需要在
build/core/prelink-linux-arm.map
中加入形如
libhellod.so 0x96000000
。
手工指定某个库相应的
prelink
地址范围,库应用对齐
1M
边界,注意库与库之间的间隔数,如果指定不好编译的时候会提示说地址空间冲突的问题。另外,注意排序,这里要把数大的放到前面去,按照大小降序排序。
4) apriori 程序
a)
源代码
build/tools/apriori/*
b)
配置
device/*/BoardConfig.mk
中设置
TARGET_USES_2G_VM_SPLIT := true
以配置
Prelink
的地址空间
c)
分析
apriori
中的
prelinkmap.c
它用根据整个系统设置
device/*/BoardConfig.mk
的内存分配规则(
3G
/1G, 2G/2G
)来判断
map
中指定地址是否符合
Prelink
的地址空间范围,如果正常,则在
so
的末尾加入
prelink
信息和标识
(
文件以
PRE
结束
)
apriori
可以预先为若干共享库确定加载地址,并为有依赖关系的共享库做静态重定位和连接
,
该命令加入参数
--verbose
,即可显示出
prelink
的细节。
5) linker 程序
a)
源代码
bionic/linker/*
(bionic
目录中存放一些基础的库,如
libc, libstdc++, libthread_db, linker
等
)
b)
分析
linker
是
Android
的专用动态链接库键接器,
Linker
和传统
Linux
使用的
linker(ld.so,ld-linux.so.2,ld-linux.so.3)
有所不同。库的编译参数
-dynamic-linker
指定了键接器为
/system/bin/linker(
也可以手动换成别的
)
,
该信息将被存放在
ELF
文件的
.interp
节中,内核执行目标映像文件前将通过该信息加载并运行相应的解释器程序
linker
,并链接相应的共享库,
共享库以
ELF
文件的形式保存在文件系统中
。
核心的
load_elf_binary
会首先将其映像文件映射到内存,然后映射并执行其解释器也就是
linker
的代码。
linker
的代码段是进程间共享的,但数据段为各进程私有。
所有外部过程引用都在映像执行之前解析
,
Android
中的共享库和可执行映像都默认采用
ELF
格式的文件
.
程序头表包含了加载到内存中的各种段的索引及属性信息,它将告诉加载器如何加载映像,初始化时,动态链接器首先解析出外部过程引用的绝对地址,一次性的修改所有相应的
GOT
表项。
linker
会在共享库加载时,调用
is_prelinked
查看该库是否是
prelink
的,并在
alloc_mem_region
中检查目的地址是否被占用。
如果该库不是
prelink
的,则库加载的起始地址为零。
3. 参考文档:
1)
动态库优化——
Prelink
(预连接)技术
http://www.eefocus.com/article/09-04/71629s.html
2)
Android build system note -
一醉千年
- CSDN
博客
http://blog.csdn.net/yili_xie/archive/2009/12/01/4906865.aspx
3)
Linux, Android
基础知识总结
http://wenku.baidu.com/view/f68fc029647d27284b735100.html
4)
android Linker
浅析
http://blog.csdn.net/dinuliang/archive/2010/04/20/5509009.aspx
http://blog.csdn.net/liusiyun/archive/2011/05/13/6418171.aspx