Android筆記.

Android筆記.


Android筆記.
有好一段時間,都在ARM based RTOS通訊產品中開發,很難有機會回到Embedded Linux的領域,而Linux上所擁有的豐富資源,更是凡事都要親力親為的RTOS環境所不能比擬的.
最近,有機會參與移植Android到新的晶片計畫,也藉此機會,把Android的基礎工程做一個Review.
在平台正式取得前,我們可以透過QEMU的環境模擬出Google定義的goldfish處理器平台(核心為ARM926EJ-S  ARMv5TEJ),並可以透過git://android.git.kernel.org/kernel/common.git下載Linux Kernel,編譯出基於這個處理器的核心Image.
而在編譯Android環境時,可以加上 showcommands的參數,幫助我們了解Android編譯過程所進行的相關動作.
本文僅供參考,隨著Android未來的版本迭替可能內容會有所差異,還請以當時所取得的Android環境為依據.
目前所整理的Android內容差異包括
1,Android所屬的Linux核心模組
A, Ashmem (Anonymous Shared Memory)
主要用來提供跨行程的共享記憶體配置,跟PMEM差異在於,PMEM必須在核心中預先把記憶體先規劃出來一塊連續的實體記憶體,專屬於PMEM使用,Ashmem無須事先在核心中設定,可以根據需求動態的配置出來,是屬於虛擬記憶體空間中連續的一塊記憶體,對應到實體記憶體空間可能是不同的區塊.
有在Win32環境下開發人應該對於透過CreateFileMapping/MapViewOfFile產生Shared Memory的機制不陌生,Win32兩個行程可以透過開啟同一個Shared Memory物件的路徑,達成跨行程共享記憶體的目的.
同樣的,在Android應用程式的開發中也可以透過MemoryFile使用Shared Memory物件.如下範例
import android.os.MemoryFile;
MemoryFile vMemoryFile;
try {
vMemoryFile = new MemoryFile(“SharedMemory”, 256);
byte[] vTmpW = new byte[256];
for (int i = 0; i < 255; i++) {
vTmpW[i] = (byte) i;
}
vMemoryFile.writeBytes(vTmpW, 0, 0, vTmpW.length);
byte[] vTmpR = new byte[256];
vMemoryFile.readBytes(vTmpR, 0, 0, vTmpR.length);
vMemoryFile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
核心的Ashmem會在 devfs中註冊一個 ashmem,用來做為對應上層的操作介面,並且有制定以下的IO Control命令,讓管理機制可以施行.
(Source Code in bionic\libc\kernel\common\linux\ashmem.h)
IO Control 命令功能
ASHMEM_SET_NAME設定Shared Memory物件名稱
ASHMEM_SET_SIZE設定Shared Memory區塊大小
ASHMEM_GET_SIZE取得Shared Memory區塊大小
ASHMEM_PIN設定這區塊在記憶體不足時,不被回收
ASHMEM_UNPIN設定這區塊在記憶體不足時,可以被回收
ASHMEM_GET_NAME, ASHMEM_SET_PROT_MASK, ASHMEM_GET_PROT_MASK, ASHMEM_GET_PIN_STATUS, ASHMEM_PURGE_ALL_CACHES…etc
參考system\core\libcutils\ashmem-dev.c中有關ashmem_create_region函式的實作
#define ASHMEM_DEVICE        “/dev/ashmem”
int ashmem_create_region(const char *name, size_t size)
{
int fd, ret;
fd = open(ASHMEM_DEVICE, O_RDWR);
if (fd < 0)
return fd;
if (name) {
char buf[ASHMEM_NAME_LEN];
strlcpy(buf, name, sizeof(buf));
ret = ioctl(fd, ASHMEM_SET_NAME, buf);
if (ret < 0)
goto error;
}
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0)
goto error;
return fd;
error:
close(fd);
return ret;
}
應用程式使用Ashmem作為Shared Memory的作法,也跟一般Linux應用程式透過mmap的作法一樣,只是他有額外定義控制相關的ioctl命令,並且如下支援PIN跟UNPIN的參數,用來讓lowmem_shrinker (Android Low Memory Killer)可以決定是不是在記憶體吃緊時,可以選擇把這塊共享記憶體給強制釋放掉.
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_PIN, &pin);
}
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_UNPIN, &pin);
}
B, Binder (Source Code in drivers/staging/android/binder.c)
Binder所扮演的角色,就像是Android中跨行程的IPC(Inter-Process Communication)機制,
我們可以把Binder看做是核心透過裝置/dev/binder提供的一個跨行程的溝通機制,基於Binder作為IPC的溝通媒介,系統中也實作了一個ServiceManager(程式碼位於frameworks/base/cmds/servicemanager/ service_manager.c),用來讓每個Services可以透過Binder向其註冊,與每個Client應用可以透過Binder去查詢目前系統中有哪些註冊的Service可以使用.
ServiceManager會在Dalvik也就是整個Android Application運作前,就開啟/dev/binder並呼叫函式binder_become_context_manager透過ioctl帶入參數BINDER_SET_CONTEXT_MGR向核心的Binder註冊自已為Binder機制的管理者,之後,呼叫binder_loop進入一個for (;;) {…}無窮迴圈,接收來自其他Binder Client的要求或是Service的註冊動作.
參考frameworks\base\cmds\servicemanager中的service_manager.c和binder.c原始碼,
<frameworks\base\cmds\servicemanager\Binder.c>
void binder_loop(struct binder_state *bs, binder_handler func)
{
int res;
struct binder_write_read bwr;
unsigned readbuf[32];
bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;
readbuf[0] = BC_ENTER_LOOPER;
binder_write(bs, readbuf, sizeof(unsigned));
for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (unsigned) readbuf;
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
if (res < 0) {
LOGE(“binder_loop: ioctl failed (%s)\n”, strerror(errno));
break;
}
res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
if (res == 0) {
LOGE(“binder_loop: unexpected reply?!\n”);
break;
}
if (res < 0) {
LOGE(“binder_loop: io error %d %s\n”, res, strerror(errno));
break;
}
}
}
目前看到ServiceManager行程的實作,是透過ioctl等待來自Binder驅動來的其他行程的要求,ServiceManager由於是處在一個for (;;) {…} Busy Loop中,也因此,只要他被系統排程執行到,就會不斷的透過ioctl帶入參數BINDER_WRITE_READ去讀取相關的訊息,
我們可以參考Linux Kernel “drivers/staging/android/binder.c” 原始碼,
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
…………………..
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//當binder_stop_on_user_error >=2 會讓行程進入睡眠,直到透過wake_up喚醒
if (ret)
return ret;
mutex_lock(&binder_lock);
………………………………
err:
if (thread)
thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
mutex_unlock(&binder_lock);
wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret && ret != -ERESTARTSYS)
printk(KERN_INFO “binder: %d:%d ioctl %x %lx returned %d\n”, proc->pid, current->pid, cmd, arg, ret);
return ret;
}
如果在Binder運作的過程中有呼叫到binder_user_error 巨集,就會導致binder_stop_on_user_error被設定為2,也會讓所有呼叫到Binder ioctl的行程被暫時停止,直到重新Wake up.
從User Mode到Kernel Mode的Code,基本上都是處於一有進入排程,就會不斷的透過ioctl確認是否有來自其他行程的IPC資料,這種處於Busy Loop的Task,跟之前在RTOS手機上,為了考量省電行為,會避免Busy Loop而儘快讓系統掉到Idle Task並確認下一個最短Timer時間點,是在許可值中,就會立刻休眠的想法有所不同.
目前Android系統主要是透過Wake Lock的機制,來判斷系統是否進入休眠,所以雖然行程是被排程到後不斷的透過ioctl確認是否有IPC資料,但只要系統的Wake Lock有被釋放,系統省電機制並不會被影響到.
參考函式 “svcmgr_handler”,我們可以知道ServiceManager本身主要處理以下四個訊息
1, SVC_MGR_GET_SERVICE : 用來取得目前進行IPC的服務Handle
2, SVC_MGR_CHECK_SERVICE (行為與SVC_MGR_GET_SERVICE一致)
3, SVC_MGR_ADD_SERVICE: 用來註冊新增一個新的服務
4, SVC_MGR_LIST_SERVICES: 列出目前有對ServiceManager註冊的所有服務
上層應用程式使用Binder主要透過/system/lib/libbinder.so,對應到的原始碼路徑為”frameworks/base/libs/binder”,應用程式可以透過IServiceManager(in IServiceManager.cpp) 介面開啟 /dev/binder達到跨行程通訊的目的.
C, Logger (Source Code in drivers/staging/android/logger.c)
不同於Linux本身的Console Log(也可透過dmesg顯示Log Buffer內容或是klogd在遠程觀看),Android提供了LogCat工具讓開發者可以觀察由Android系統所產生的Log訊息.
目前Android共支援以下五類Log訊息的顯示(順序為嚴重等級),
1, Verbose: 在LogCat中顏色為黑色,使用的函式為 Log.v
2, Debug: 在LogCat中顏色為藍色的,使用的函式為 Log.d
3, Information: 在LogCat中顏色為綠色,使用的函式為 Log.i
4, Warning: 在LogCat中顏色為橙色,使用的函式為 Log.w
5, Error: 在LogCat中顏色為紅色,使用的函式為 Log.e
依據上述順序,如果在LogCat選擇 “I”,就只會有Information,Warning與Error等級被顯示出來. 如果點選”E”,則只會看到屬於Error等級的訊息.
在Native程式的開發中,可以在C程式碼中透過LOGV,LOGD,LOGI,LOGW與LOGE讓原生的程式碼可以顯示這五種Log訊息到LogCat中,如下範例程式
#include <string.h>
#include <stdlib.h>
#define LOG_TAG “C LOG DEMO”
#undef LOG
#undef NDEBUG
#include <utils/Log.h>
int main()
{
LOGV(“Hello! This color is for Verbose.”);
LOGD(“Hello! This color is for Debug.”);
LOGI(“Hello! This color is for Information”);
LOGW(“Hello! This color is for Warnning.”);
LOGE(“Hello! This color is for Error.”);
return 1;
}
在核心Logger啟動後,會產生以下三個裝置節點
/dev/log/radio
/dev/log/events
/dev/log/main
原生的應用程式,可以透過如下的函式直接對Logger作讀寫的動作
open(“/dev/log/main”, O_WRONLY) (Source Code 在bionic/linker/linker_format.c)
D, Power Management (Source Code in kernel/power)
手持裝置上電池的容量都是有限的,因此每個移動裝置都會有對應的電源管理機制在,可以根據應用程式的狀態,決定每個硬體裝置是不是可以被關閉,進而透過PMIC (Power Management IC)與Clock Gating的機制,達成系統省電的目的.
Android自然也有自己一套基於Application Framework與核心Power 相關驅動的管理機制(也可以參考Google的文件http://www.netmite.com/android/mydroid/development/pdk/docs/power_management.html 與 http://developer.android.com/reference/android/os/PowerManager.html).
簡單來說,如果沒有任何一個應用程式透過 ” Wake Locks”要求處理器不能睡,那處理器就會進入休眠的狀態,一般應用程式可以透過PowerManager要求的Wake Locks大概有以下四類
Flag ValueCPUScreenKeyboard
PARTIAL_WAKE_LOCKOnOffOff
SCREEN_DIM_WAKE_LOCKOnDimOff
SCREEN_BRIGHT_WAKE_LOCKOnBrightOff
FULL_WAKE_LOCKOnBrightBright
例如,現在有一個MP3 Playback應用程式正在執行,應用程式可以要求”Partial Wake Lock”,讓處理器可以持續的解碼MP3進行音樂的播放,同時又可以關閉螢幕與鍵盤的背光,節省電源.
若是目前處理使用者介面互動的狀態,使用者需要透過鍵盤輸入與觀看螢幕上的內容,就可以透過要求”Full Wake Lock”,讓處理器正常運作,並且同時開啟螢幕與鍵盤的背光.
參考” frameworks/base/core/java/android/os/PowerManager.java”原始碼,其實應用程式還有第五個Wake Lock的選擇 “PROXIMITY_SCREEN_OFF_WAKE_LOCK”,不過目前這並不包括在Google SDK的說明文件中.
此外,位於核心的驅動還可以透過函式android_register_early_suspend註冊在系統休眠前,根據平台的差異要做的事情,例如:完全關閉Display裝置.或是透過函式android_register_early_resume註冊系統啟動時,在核心驅動要讓系統正常運作前,必須預先準備完成的事情.
事實上,每個硬體驅動,都還可以在沒有被上層應用程式使用時,透過PMIC關閉電源,或根據SoC的配置透過Clock Gating節省電源的消耗,這些控制的機制,目前並沒有包含在PowerManager機制,應該是我們自己在移植Android環境的人,撰寫周邊驅動與整合時,需要另行實作的.
E, Low Memory Killer(Source Code in drivers/staging/android/lowmemorykiller.c)
Linux Kernel本身有支援在每次透過Page Allocate配置記憶體時(Source Code在mm/page_alloc.c),透過核心的OOM模組(Source Code 在mm/oom_kill.c),評估目前記憶體資源是不是快耗盡,如果是就會透過函式mem_cgroup_out_of_memory呼叫select_bad_process選擇一個透過badness計算後,要被終止的行程,然後呼叫函式oom_kill_process,最後在函式__oom_kill_task中發送SIGKILL Signal強制終止行程執行.
我們可以透過 make menuconfig 進入 “General setup”->”Control Group Support”->”Resource counters”->”Memory Resource Controller for Control Groups” 設定編譯參數 “CONFIG_CGROUP_MEM_RES_CTLR=y”,就可以讓Linux kernel 開啟 OOM的功能.
從arch/arm/configs/goldfish_defconfig來看,目前OOM的功能Android上Linux Kernel預設是不開啟的,如此也可避免跟Android本身的Low Memory Killer行為重疊.
Android的Low Memory Killer會呼叫函式register_shrinker註冊自己的lowmem_shrinker服務, shrinker的機制是由原本Linux Kernel Memory Management的機制所提供的,主要實作在mm/vmscan.c中,如下所示
mm/vmscan.c
/*
* Add a shrinker callback to be called from the vm
*/
void register_shrinker(struct shrinker *shrinker)
{
shrinker->nr = 0;
down_write(&shrinker_rwsem);
list_add_tail(&shrinker->list, &shrinker_list);
up_write(&shrinker_rwsem);
}
register_shrinker會把lowmem_shrinker註冊到shrinker_list中,而shrinker_list被處理的時間點,則是在當系統記憶體要進行回收時,會呼叫函式shrink_slab,進行檔案系統快取的回收時,就會一併處理到lowmem_shrinker的動作,進行Android系統中的記憶體回收流程.
Shrinker呼叫函式lowmem_shrink進行記憶體回收的動作,會找出task_struct-> oomkilladj值最大的行程為標的,若有兩個行程的task_struct-> oomkilladj相同,則選擇佔用記憶體最大的行程,透過SIGKILL Signal強制終止行程執行,
每次找尋行程時,會把min_adj 定義為OOM_ADJUST_MAX + 1 (OOM_ADJUST_MAX=15),並取得NR_FREE_PAGES (目前Free狀態的Pages個數) 與 NR_FILE_PAGES (目前在Cache與Buffer中所佔的Pages個數),我目前的理解,在NR_FILE_PAGES中所包含的Cache個數指的是所暫存的檔案內容,而Buffer指的是所暫存的檔案系統inode內容,當我們在檔案系統中搜尋目錄或是讀寫檔案時,就會透過Buffer加速在檔案系統中inode的查找效率,與透過Cache加速所讀寫的檔案內容(實際會根據檔案系統現況,分布在不同的位置).
int other_free = global_page_state(NR_FREE_PAGES);
int other_file = global_page_state(NR_FILE_PAGES);
定義閒置記憶體大小與所對應的lowmem_adj數值,目前有四種層級的數值,如果閒置記憶體越多,則選擇回收記憶體要求的task_struct-> oomkilladj越大,也就越少行程有機會被回收.
static int lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static size_t lowmem_minfree[6] = {
3*512, // 6MB —-每個Page對應的大小為4kbytes
2*1024, // 8MB
4*1024, // 16MB
16*1024, // 64MB
}
然後根據目前Free與Cache中的Pages個數,去對應預先定義的四個層級,決定lowmem_adj的值.
for(i = 0; i < array_size; i++) {
if (other_free < lowmem_minfree[i] &&
other_file < lowmem_minfree[i]) {
min_adj = lowmem_adj[i];
break;
}
}
如果目前系統中Free與Cache的Page個數,兩者有一個大於64MB大小的話, min_adj 值會等於OOM_ADJUST_MAX + 1,也就不會進行任何行程記憶體的回收動作.
if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {
lowmem_print(5, “lowmem_shrink %d, %x, return %d\n”, nr_to_scan, gfp_mask, rem);
return rem;
}
從/init.rc中可以看到啟動時 init被設定的oom_adj=-16,這可以確保init行程不會被回收.
# Set init its forked children’s oom_adj.
write /proc/1/oom_adj -16
上述的lowmem_adj與lowmem_minfree也可以在啟動過程中透過/init.rc設定,如下所示,由於原本定義的Array個數為6個,最多設定的值也以此為限.
write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144
F, PMEM (Source Code in drivers/misc/pmem.c)
PMEM主要的功能是在實體記憶體中配置一塊連續的記憶體,這塊記憶體將不會納入Linux Kernel Memory Management管理的範圍中,這類連續的實體記憶體配置適合用在Display Buffer或是ARM跟DSP需要搬移區塊資料的應用上.
PMEM使用時,可註冊所配置的這塊記憶體是不是要開ARM的Cache,以上述例子而言,如果是屬於硬體會直接讀寫的記憶體區塊,為了要避免在ARM上執行的程式因為Cache中的暫存與被硬體直接修改的記憶體內容不一致所導致的錯誤,屬於這類的記憶體就會關閉ARM的Cache.
實際的開發中,如果有針對同一個被設定為開Cache的PMEM裝置,希望有不同的應用,可以關閉Cache,可以在開啟該裝置時加上O_SYNC參數,例如
fd = open(“/dev/pmem”, O_RDWR | O_NONBLOCK | O_SYNC);
….
參考drivers/misc/pmem.c,在開啟 PMEM裝置後,進行MMAP
static int pmem_mmap(struct file *file, struct vm_area_struct *vma)
{
……
vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_page_prot);
……
}
static pgprot_t phys_mem_access_prot(struct file *file, pgprot_t vma_prot)
{
int id = get_id(file);
#ifdef pgprot_noncached
if (pmem[id].cached == 0 || file->f_flags & O_SYNC)
return pgprot_noncached(vma_prot);
#endif
#ifdef pgprot_ext_buffered
else if (pmem[id].buffered)
return pgprot_ext_buffered(vma_prot);
#endif
return vma_prot;
}
在開啟PMEM裝置時,如果有設定O_SYNC,在MMAP時就會透過pgprot_noncached回傳關閉Cache的狀態.
如果該區塊記憶體只有ARM上的程式會讀寫,開啟Cache就可以減少每次都要去外部記憶體讀寫的延遲,又可以達到加速的目的,不過,如果該區塊記憶體只有ARM會讀寫,其實也沒有必要獨立於Linux Kernel Memory Management外配置一塊連續的實體記憶體,若有Shared Memory需求就透過Ashmem,或是由行程直接配置就好,不必使用時就可以釋出,以便基於Memory Management機制,提高記憶體的使用率.
PMEM可以透過make menuconfig的選項, Device Drivers ->Misc devices –> Android pmem allocator決定是否開啟,並且要加入相關的Source Code才讓PMEM產生作用.
以網路上目前可以抓到的HTC 在Qualcomm平台上的配置來說 (Source Code in  http://neophyte.vipme.com/gpl/bravo-2.6.32.24/arch/arm/mach-msm/devices_htc.c)
定義兩個Android PMEM的裝置,分別為 /dev/pmem與/dev/pmem_adsp,,pmem開啟Cache,而pmem_adsp關閉Cache.
static struct android_pmem_platform_data pmem_pdata = {
.name = “pmem”,
.no_allocator = 1,
.cached = 1,
};
static struct android_pmem_platform_data pmem_adsp_pdata = {
.name = “pmem_adsp”,
.no_allocator = 0,
.cached = 0,
};
static struct platform_device pmem_device = {
.name = “android_pmem”,
.id = 0,
.dev = { .platform_data = &pmem_pdata },
};
static struct platform_device pmem_adsp_device = {
.name = “android_pmem”,
.id = 1,
.dev = { .platform_data = &pmem_adsp_pdata },
};
透過platform_device_register註冊上述兩個PMEM裝置
void __init msm_add_mem_devices(struct msm_pmem_setting *setting)
{
if (setting->pmem_size) {
pmem_pdata.start = setting->pmem_start; //設定對應到實體記憶體的位置
pmem_pdata.size = setting->pmem_size; //設定對應到實體記憶體的大小
platform_device_register(&pmem_device);
}
if (setting->pmem_adsp_size) {
pmem_adsp_pdata.start = setting->pmem_adsp_start; //設定對應到實體記憶體的位置
pmem_adsp_pdata.size = setting->pmem_adsp_size; //設定對應到實體記憶體的大小
platform_device_register(&pmem_adsp_device);
}
………
}
根據我們把實體記憶體最後的區塊設定給PMEM使用的大小,也要同步修改傳入核心的bootargs,把MEM值對應修改,調整實體記憶體納入Linux Kernel Memory Management的大小.
其他的改變還包括,
1,支援RAM Console(Source Code in drivers/staging/android/ram_console.c)
可以透過make menuconfig 選單,”Device Drivers”->”Staging Drivers”->”Android”->”Android RAM buffer console”設定是否開啟RAM Control,以及設定記憶體位置與大小,讓開發核心相關的模組時,可以依據需求決定相關參數. 其實Linux Kernel本身就有維持一個Log Ring-Buffer的機制,根據arch/arm/configs/goldfish_defconfig中的設定CONFIG_LOG_BUF_SHIFT=16,在編譯時會指定 kernel/printk.c中的變數 log_buf_len  (__LOG_BUF_LEN) = 64kbytes (1 << CONFIG_LOG_BUF_SHIFT),也就是我們透過dmesg可以在本機看到的訊息來源,我目前的理解,這兩者功能上是有重疊的. 也可能是因為如此,所以RAM Console在goldfish_defconfig 中設預是不開啟的,只用於開發階段需要比較大的Log紀錄輔助除錯時.
//來自 IRead  => ram console 主要的功能是在系統掛掉後,reboot時能保留前次開機最後的 kernel log
2,Alarm (Source Code in drivers/rtc/alarm.c)
在系統休眠時,只有RTC石英振盪器還維持運作,基於PLL振盪器的Timer,都會沒有作用,在Feature Phone上,會把下次Modem或是人機介面要醒來的時間,透過RTC喚醒.同樣的在Android這樣的平台上,雖然沒有關於Modem醒來時間需要處理,但有關鬧鐘,事件提醒或是到特定時間需要軟體處理的工作,在系統休眠時,除非正好使用者透過按鍵把手機喚醒回到正常執行的狀態,不然就只有透過RTC這類外部的中斷,讓處理器可以到指定的時間被喚醒,執行預定的工作.
Android也基於RTC提供了Alarm的驅動,並在/dev/alarm中註冊裝置,上層應用程式可以透過AlarmManagerService或是libutils中System Clcok的函式setCurrentTimeMillis/elapsedRealtime,開起該裝置檔案,透過ioctl設定/讀取相關RTC的時間參數與動作.
3,Timed Device (Source Code in drivers/staging/android/timed_gpio.c and drivers/staging/android/timed_output.c)
主要用於手機上需要有時間周期控制的GPIO裝置,例如Service LED的周期明亮,或是一次性的時間Timer動作,例如鍵盤LED背光,或是震動.
註冊Timed GPIO的動作可以透過函式timed_gpio_probe,從timed_gpio_platform_data中可以取得目前要動作的GPIO個數,之後再透過timed_gpio結構取得每個GPIO所對應的位置,周期結束時間,以及該GPIO輸出的高或低電位. 當該GPIO TimeOut的時間到時,就會呼叫gpio_timer_func,並在該函式中呼叫gpio_direction_output對該GPIO進行輸出電位的調整. 每一次TimeOut後,如果要讓該GPIO可以週期性的作用,就要重新呼叫函式gpio_enable,以便成為一個週期性的GPIO動作.
2, 支援CPIO Ramdisk
利用Linux Kernel 2.6之後導入的TmpFS,以CPIO的Ramdisk格式取代過去透過Memory Block Device搭配Ext2的做法(至少,我是這樣子做滴…),如下所示為之前產生Ramdisk與手動載入Ramdisk的範例
.
[root@loda ~]# dd if=/dev/zero of=/temp/initrd bs=1024 count=4096 (產生4MB的檔案)
4096+0 records in
4096+0 records out
4194304 bytes (4.2 MB) copied, 0.188288 seconds, 22.3 MB/s
[root@loda ~]# losetup /dev/loop0 /temp/initrd (對應 /temp/initrd到loop裝置)
[root@loda ~]# mkfs.ext2 /dev/loop0 (格式化 /temp/initrd 為ext2 檔案系統)
[root@loda ~]# mount /dev/loop0 /mnt/ (把產生的4MB  /temp/initrd 載入到目錄 /mnt)
[root@loda ~]# vi /mnt/hello  (在/mnt中產生一個範例檔案hello)
[root@loda ~]# umount /mnt  (卸載目錄 /mnt)
[root@loda ~]# losetup -d /dev/loop0 (移除 loop0裝置)
[root@loda ~]# gzip -9 /temp/initrd (gzip壓縮Ramdisk檔案)
[root@loda ~]# ls /temp
initrd.gz
[root@loda ~]# gzip -dc /temp/initrd.gz > initrd (解壓縮 Ramdisk)
[root@loda ~]# mkdir test (產生測試目錄)
[root@loda ~]# mount -o loop ./initrd test  (以loop裝置把Ramdisk 載入到test目錄)
[root@loda ~]# ls test
hello  lost+found
[root@loda ~]#
我們可以發現原本的做法,需要預估一個Ramdisk可能需要的空間,如果沒有預估好,可能就會導致有一些記憶體空間浪費,而且在整個操作流程中,也相形比較複雜.
如下為透過CPIO的機制,來取代過去Ramdisk的做法.
[root@loda generic]# cd root
[root@loda root]# du
8       ./sys
8       ./proc
152     ./sbin
8       ./system
8       ./dev
8       ./data
352     .
[root@loda root]# find ./ | cpio -H newc -o > ../root.img
512 blocks
[root@loda root]# ls -l ../root.img
-rw-r–r– 1 root root 262144 Jan 18 20:14 ../root.img
[root@loda root]# cd ..
[root@loda generic]# gzip -9 root.img
[root@loda generic]# ls -l root.img.gz
-rw-r–r– 1 root root 165918 Jan 18 20:17 root.img.gz
以作為開機檔案系統的根而言,兩者都會透過gz壓縮節省儲存的空間,不過早期Ramdisk的做法,在記憶體空間使用上,與載入的動作,流程上比較多,但優點是可以寫入暫存資料,當作一個效率很高的可讀/寫檔案系統. CPIO Ramdisk格式可以直接由TmpFS載入,唯讀,儲存空間直接對應到實際資料的空間,可以避免記憶體浪費的問題. 並且,不需要經過Memory Block Device額外一層的動作,直接對應到記憶體中,對於初始化的效率與空間使用上都有比較好的效果.
3, 支援新的Prelink機制
Linux環境中的Prelink是由在RedHat的Jakub Jelinek所開發的(Wiki http://en.wikipedia.org/wiki/Prelink),基於這個機制,應用程式啟動時,所使用到的動態函式庫會根據預先規劃好的記憶體位置透過ld.so擺放,也因為是預先規劃好的擺放位置,在記憶體的使用上也可以比較節省.而有經過Prelink處理的執行檔,也會根據預先規劃好的動態函式庫記憶體位置,直接修改ELF執行檔Symbol對應的位址,加速載入後,每個呼叫外部函式庫Symbol重定位的成本.
如果要針對所在環境進行Prelink,可以執行
prelink –avmR
要取消Prelink可以執行
prelink –au
以如下的C Code 說明Linux上Prelink的作用,
int main()
{
char *vTmp;
vTmp=(char *)malloc(32);
if(vTmp==0)
return 0;
memset(vTmp,0x00,32);
strcpy(vTmp,”123″);
memcpy(&vTmp[3],”456″,3);
printf(“%s\n”,vTmp);
free(vTmp);
return 1;
}
以筆者電腦為例,編譯完成後,我們透過objdump可以看到_GLOBAL_OFFSET_TABLE_中 free的位址是指向0x080482da,malloc的位址是指向0x080482ea,puts的位址是指向0x080482fa.
對編譯出來的執行檔下 prelink –vm 指令後,再透過objdump查看_GLOBAL_OFFSET_TABLE_, free的位址是指向0x0018d990,malloc的位址是指向0x0018fe30,puts的位址是指向0x0017de80.
要驗證prelink是不是真的把用到libc的函式指到正確的記憶體位置,而不是在程式運作時才動態的進行Relocate,我們再透過objdump反組譯/lib/libc-2.5.so,找到上面三個位址所對應到的程式碼,如下所示
0018d990 <__libc_free>:
18d990:       55                         push   %ebp
18d991:       89 e5                       mov    %esp,%ebp
18d993:       83 ec 30              sub    $0x30,%esp
18d996:       89 5d f4                     mov    %ebx,0xfffffff4(%ebp)
18d999:       8b 55 08                     mov    0x8(%ebp),%edx
18d99c:       e8 3f c3 fa ff            call   139ce0 <__i686.get_pc_thunk.bx>
18d9a1:       81 c3 53 86 0d 00       add    $0xd8653,%ebx
…….
0018fe30 <__libc_malloc>:
18fe30:        55                         push   %ebp
18fe31:        89 e5                       mov    %esp,%ebp
18fe33:        83 ec 18             sub    $0x18,%esp
18fe36:        89 5d f4                     mov    %ebx,0xfffffff4(%ebp)
18fe39:        e8 a2 9e fa ff            call   139ce0 <__i686.get_pc_thunk.bx>
18fe3e:        81 c3 b6 61 0d 00       add    $0xd61b6,%ebx
……
0017de80 <_IO_puts>:
17de80:       55                         push   %ebp
17de81:       89 e5                       mov    %esp,%ebp
17de83:       83 ec 1c              sub    $0x1c,%esp
17de86:       89 5d f4                     mov    %ebx,0xfffffff4(%ebp)
17de89:       8b 45 08                     mov    0x8(%ebp),%eax
17de8c:       e8 4f be fb ff            call   139ce0 <__i686.get_pc_thunk.bx>
17de91:       81 c3 63 81 0e 00        add    $0xe8163,%ebx
……
然後,讓程式實際的跑起來查看/proc/7345/maps (7345是我程式執行的PID)
00101000-0011b000 r-xp 00000000 fd:00 12190991   /lib/ld-2.5.so
0011b000-0011c000 r-xp 00019000 fd:00 12190991   /lib/ld-2.5.so
0011c000-0011d000 rwxp 0001a000 fd:00 12190991   /lib/ld-2.5.so
00124000-00263000 r-xp 00000000 fd:00 12191004   /lib/libc-2.5.so
00263000-00264000 –xp 0013f000 fd:00 12191004   /lib/libc-2.5.so
00264000-00266000 r-xp 0013f000 fd:00 12191004   /lib/libc-2.5.so
00266000-00267000 rwxp 00141000 fd:00 12191004   /lib/libc-2.5.so
00267000-0026a000 rwxp 00267000 00:00 0
可以知道/lib/libc-2.5.so有正確的被載入到上述記憶體空間中,而執行檔經過Prelink重定位的記憶體位置也跟/lib/libc-2.5.so實際被載入到的記憶體位置一致,驗證Prelink確實把執行檔ELF中的_GLOBAL_OFFSET_TABLE_修正正確,達到程式載入加速的目的.
如果在程式載入時,發現有建立Prelink資訊的動態函式庫在記憶體的相關位置改變了,ld.so就會進行原本的動態函式庫Symbol重定位的流程,確保應用程式還是可以正常的執行.
Prelink所依據的資訊,主要來自於/etc/prelink.conf中所記載的應用程式路徑與動態函式庫路徑,他會去統計這些路徑中所有的執行檔與動態函式庫,幫每個動態函式庫配置一個記憶體位置,然後再根據上述動態函式庫所備配置的記憶體位置,去修改每個執行檔中所參考到相關動態函式庫函式的記憶體位置,每個動態函式庫所配置的記憶體位置,也會紀錄到ELF檔案中,以便在下次執行時,可以透過Prelink時對應動態函式庫所配置的記憶體位置查核在實際執行時是不是有正確對應,以便判斷是不是要進行動態的函式庫Symbol重定位的流程.
說完了Linux原本支援的Prelink機制,接下來我們再來看Android 的Prelink機制Apriori. (Source Code in build/tools/apriori) ,Google一下Apriori的命名意義,在演算法中Apriori是一種關聯演算法,主要用於Data Mining,例如可用來分析使用者購物的行為關係,找出有意義的資訊. 這跟Prelink必須要先蒐集動態函式庫的資訊後,再來幫助ELF執行檔預先解決記憶體中Symbol關聯的問題,有類似的含意.
Apriori這工具所參考的動態函式庫記憶體位址是透過預先定義好的Maps檔案支援的,參考Android編譯環境中的build/core/definitions.mk我們可以知道Apriori所參考的Maps檔案位置在build/core/prelink-linux-arm.map.在這檔案中就記載了每個動態函式庫在執行檔載入時,所預先規劃好對應的記憶體位址,如下所示
# core system libraries
libdl.so                0xAFF00000 # [<64K]
libc.so                 0xAFD00000 # [~2M]
libstdc++.so            0xAFC00000 # [<64K]
libm.so                 0xAFB00000 # [~1M]
liblog.so               0xAFA00000 # [<64K]
libcutils.so            0xAF900000 # [~1M]
libthread_db.so         0xAF800000 # [<64K]
libz.so                 0xAF700000 # [~1M]
libevent.so             0xAF600000 # [???]
libssl.so               0xAF400000 # [~2M]
libcrypto.so            0xAF000000 # [~4M]
libsysutils.so          0xAEF00000 # [~1M]
…..
接下來我們還是以這段C Code進行編譯,驗證Apriori的作用
int main()
{
char *vTmp;
vTmp=(char *)malloc(32);
if(vTmp==0)
return 0;
memset(vTmp,0x00,32);
strcpy(vTmp,”123″);
memcpy(&vTmp[3],”456″,3);
printf(“%s\n”,vTmp);
free(vTmp);
return 1;
}
透過Android.mk以BUILD_EXECUTABLE方式編譯出執行檔,之後把執行檔放到Android環境中執行,查核Memory Map中的內容如下
# cat /proc/306/maps
cat /proc/306/maps
00008000-00009000 r-xp 00000000 1f:00 381        /system/bin/Hello
00009000-0000a000 rwxp 00000000 1f:00 381        /system/bin/Hello
0000a000-0000b000 rwxp 0000a000 00:00 0          [heap]
40000000-40008000 r-xs 00000000 00:07 189        /dev/ashmem/system_properties (
deleted)
40008000-40009000 r-xp 40008000 00:00 0
afd00000-afd3f000 r-xp 00000000 1f:00 546        /system/lib/libc.so
afd3f000-afd42000 rwxp 0003f000 1f:00 546        /system/lib/libc.so
afd42000-afd4d000 rwxp afd42000 00:00 0
b0001000-b000c000 r-xp 00001000 1f:00 413        /system/bin/linker
b000c000-b000d000 rwxp 0000c000 1f:00 413        /system/bin/linker
b000d000-b0016000 rwxp b000d000 00:00 0
bed27000-bed3c000 rwxp befeb000 00:00 0          [stack]
#
跟prelink-linux-arm.map中libc對應的記憶體位址相比,我們可以看到/system/bin/linker有正確的參考Apriori根據prelink-linux-arm.map在函式庫 lib*.so ELF 檔案中記錄的記憶體位址,把這些動態函式庫對應到指定的記憶體位置中.
觀察Android的編譯流程,在動態函式庫編譯的過程中,會執行如下的指令/android/froyo/out/host/linux-x86/bin/apriori –prelinkmap /android/froyo/build/core/prelink-linux-arm.map –locals-only –quiet libxxx.so –output libxxx_a.so
根據prelink-linux-arm.map修改lib*.so檔案,並複製到最後產生的system image中.
此外,Android上Native執行檔(非動態函式庫)的編譯過程中,並沒有像Linux原本的Prelink機制,會去修改ELF執行檔的_GLOBAL_OFFSET_TABLE_,讓執行檔的Symbol跟動態函式庫可以預作定位,Apriori只有針對動態函式庫的部分,參考prelink-linux-arm.map把動態函式庫放到對應的位置上,分析編譯出來的LINKED ELF執行檔,也可確認編譯流程中並沒有針對執行檔參考到的Symbol預作定位.
從分析的結果來看,其實原本Linux的Prelink作的還是比較完善,包含有亂數定址分配,動態根據函式庫的大小調配優化後的記憶體排列節省空間, prelink-linux-arm.map 本身是透過人為手動設定的,為了確保不要有重疊,相鄰的兩個函式庫就算Size只有幾百kbytes,還是會配置Mbytes以上的記憶體間隔,當然,因為系統上有MMU,並不會造成這些記憶體間隔的浪費,只是,類似Linux Prelink動態根據所有函式庫的大小,來排列優化結果,對Embedded System還是位比較有效益的.
4, 支援新的動態連結核心
每當使用者選擇一個檔案執行時,Linux Kernel會分析檔案的格式,並決定要透過load_aout_binary,load_elf_binary 與load_misc_binary進行後續載入的流程,我們在Android系統上所編譯的Native執行檔的檔案格式就是ELF,當我們透過gcc編譯ELF執行檔時,可以透過 ”-dynamic-linker” 連結選項,設定所產生的ELF執行檔的動態函式庫連結機制,是不是有自己客製的實作,如果我們沒有設定這個參數,預設執行檔的動態函式庫連結機制會採用 ld-linux.so,並透過這個動態函式庫連結機制,載入其他執行檔所需的動態函式庫.
Android支援了自己的Apriori Prelink機制,讓應用程式與動態函式庫的載入過程可以得到加速,並且也實作了自己的動態函式庫連結核心linker (在System的路徑為 /system/bin/linker, Source Code in bionic/linker),以便在程式執行時,可以由linker判斷該動態函式庫是不是被Apriori處理過的.
在系統啟動的過程中,由於Android的Linker是被放在CPIO的TmpFS中,因此在root下的/init,會在 system.img載入前就被執行,比Linker能作用的環境更早,因此我們可以看到 /init本身為static link的執行檔.
[root@loda root]# file init
init: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, stripped
由於linker機制的改變,原本在 glibc環境中ld-linux.so支援的Lazy Binding,或像是ld.so.cache動態函式庫搜尋快取也因為有Prelink機制的存在,也沒有存在的必要.
我們也可以試著進行如下的編譯,透過指定dynamic linker,產生出一個可在Android環境中執行的ELF檔案 (還請包到 system.img中進行測試)
arm-eabi-gcc -g -o Hello Hello.c -Wl,-rpath-link=/android/froyo/out/target/product/generic/obj/lib,-dynamic-linker=/system/bin/linker -I/android/froyo/ndk/build/platforms/android-8/arch-arm/usr/include -L/android/froyo/out/target/product/generic/obj/lib -nostdlib /android/froyo/out/target/product/generic/obj/lib/crtbegin_dynamic.o –lc -fno-short-enums
基於上述的說明,我們可以知道新的動態函式庫linker的支援,其實是透過ELF編譯時指定的,我們也可以把原本的libc.so與ld-linux.so複製到Android執行環境中,就可以透過Linux Kernel load_elf_binary原本的機制,讓ld-linux.so可以執行跟Android linker一樣的動態函式庫連結工作,並在Android系統中順利的運作.(只是這樣的話就不能使用到 Apriori Prelink的優點了.)
我們可以從Code Sourcery下載編譯好的Toolchain 網址是http://www.codesourcery.com/sgpp/lite/arm/portal/package7851/public/arm-none-linux-gnueabi/arm-2010.09-50-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2 (網站http://www.codesourcery.com/sgpp/lite/arm/portal/release1600) 下載ARM  eabi gcc Cross Compiler環境,解開後,用以下程式碼進行編譯
int main()
{
char *vTmp;
vTmp=(char *)malloc(32);
if(vTmp==0)
return 0;
memset(vTmp,0x00,32);
strcpy(vTmp,”123″);
memcpy(&vTmp[3],”456″,3);
printf(“%s\n”,vTmp);
free(vTmp);
return 1;
}
[root@loda ~]# arm-none-linux-gnueabi-gcc  -g -o hello hello.c
若各位用的Cross Compiler跟筆者不同,建議可以加上 “-Wl,-dynamic-linker
=/lib/ld-linux.so.3” 指定動態函式庫linker到/lib/ld-linux.so.3. (或所使用glibc對應的ld-linux檔案名稱)
驗證檔案格式
[root@loda ~]# file hello
hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.6.16, dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped
然後用
[root@loda ~]# readelf -d pig6|more
Dynamic section at offset 0x6f4 contains 25 entries:
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]
…..
得知除了ld-linux.so.3外,還需要有libgcc_s.so.1與libc.so.6,因此我們把這三個檔案複製到最後運作時,位於root檔案系統 /lib 的目錄下
[root@loda glibc]# cd /android/froyo/out/target/product/generic/root/lib
[root@loda glibc]# cp /home/loda/arm-2010.09/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3 ./
[root@loda glibc]# cp /home/loda/arm-2010.09/arm-none-linux-gnueabi/libc/lib/libc.so.6 ./
[root@loda glibc]# cp /home/loda/arm-2010.09/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1 ./
複製完成後,重新產生 root 與 system檔案系統
[root@loda generic]# cd /android/froyo/out/target/product/generic/root
[root@loda root]# find ./ | cpio -H newc -o > ../ramdisk.img
4775 blocks
[root@loda root]# cd ..
[root@loda generic]# gzip -9 ramdisk.img
[root@loda generic]# mkyaffs2image  system/ system.img
啟動Android系統後,就可以發現基於 glibc環境的執行檔可以正確運作了,不過由於這個執行檔不是基於Apriori與Android Linker的環境,我們查核/proc下對應程序的Memory Map,結果如下
# cat /proc/73/maps
cat /proc/73/maps
00008000-00009000 r-xp 00000000 1f:00 463        /system/bin/pig8
00010000-00011000 rwxp 00000000 1f:00 463        /system/bin/pig8
00011000-00032000 rwxp 00011000 00:00 0          [heap]
40000000-4001f000 r-xp 00000000 00:01 21         /lib/ld-linux.so.3
4001f000-40022000 rwxp 4001f000 00:00 0
40026000-40027000 r-xp 0001e000 00:01 21         /lib/ld-linux.so.3
40027000-40028000 rwxp 0001f000 00:01 21         /lib/ld-linux.so.3
40028000-40033000 r-xp 00000000 00:01 24         /lib/libgcc_s.so.1
40033000-4003a000 —p 0000b000 00:01 24         /lib/libgcc_s.so.1
4003a000-4003b000 rwxp 0000a000 00:01 24         /lib/libgcc_s.so.1
4003b000-40174000 r-xp 00000000 00:01 22         /lib/libc.so.6
40174000-4017c000 —p 00139000 00:01 22         /lib/libc.so.6
4017c000-4017e000 r-xp 00139000 00:01 22         /lib/libc.so.6
4017e000-4017f000 rwxp 0013b000 00:01 22         /lib/libc.so.6
4017f000-40182000 rwxp 4017f000 00:00 0
be908000-be91d000 rw-p befeb000 00:00 0          [stack]
可以得知,對應函式庫的記憶體位置,跟prelink-linux-arm.map相比,是由/lib/ld-linux.so.3動態決定的.
5,支援 Toolbox
一般的Embedded Linux 通常會考慮用Busybox來減少要支援Linux眾多的指令集檔案,Android本身也有類似Busybox的機制,但實作的方式是另外自己開發Toolbox,原始碼所在目錄為system/core/toolbox,目前共支援以下指令
mvdfmkdirlogdmesg
idchmodioctlcatnewfs_msdos
renicemountprintenvsmdlsmod
iftopsetpropnotifywatchpropsrmmod
hdinsmodnetstatcmpdd
killionicedatestartstop
sleepgetpropsendeventvmstatln
geteventwipesyncschedtoptop
ifconfigrebootsetconsolerouterm
nandreadlschownrmdirps
umount
6, soslim ELF檔案Symbol Strip工具
Android 系統中沒有使用prebuilt目錄下所帶的arm-eabi-strip,而是使用soslim(Source Code in build/tools/soslim)用來刪除ELF執行檔中所帶Symbol資訊,減少執行檔所佔的儲存空間.
如下所示,我用原本的arm-eabi-strip 針對一個10146 bytes的檔案進行strip,最後的檔案大小為5468bytes.
[root@loda froyo]# ls -l NonStripFile
-rwxr-xr-x 1 root root 10146 Jan 20 11:50 NonStripFile
[root@loda froyo]# arm-eabi-strip NonStripFile
[root@loda froyo]# ls -l NonStripFile
-rwxr-xr-x 1 root root 5468 Jan 20 11:51 NonStripFile
同樣的,我透過soslim對同一個檔案進行strip,最後的檔案大小也為5468bytes.
[root@loda froyo]# ls -l NonStripFile
-rwxr-xr-x 1 root root 10146 Jan 20 11:51 NonStripFile
[root@loda froyo]# /android/froyo/out/host/linux-x86/bin/soslim –strip –shady
–quiet NonStripFile –outfile NonStripFile_soslim
[root@loda froyo]# ls -l NonStripFile_soslim
-rwxr-xr-x 1 root root 5468 Jan 20 11:53 NonStripFile_soslim
兩者都有透過刪除Symbol減少檔案大小的功能,主要差異在於soslim有支援Prelink機制的Tags.
參考build/tools/soslim/prelink_info.c
typedef struct {
uint32_t mmap_addr;
char tag[4]; /* ‘P’, ‘R’, ‘E’, ‘ ‘ */
} prelink_info_t __attribute__((packed));
Apriori的Prelink機制,會把動態函式庫.so檔要對應到記憶體的位址與Tag共8 bytes,儲存到so ELF檔的最後面,如下所示如果是用原本的arm-eabi-strip處理的話,會連帶把Prelink Tag給刪除.
[root@loda ~]# grep “PRE” libbinder.so
Binary file libbinder.so matches
[root@loda ~]# arm-eabi-strip libbinder.so
[root@loda ~]# grep “PRE” libbinder.so
[root@loda ~]#
如果是透過soslim,參考build/tools/soslim/prelink_info.c
void setup_prelink_info(const char *fname, int elf_little, long base)
{
….
int fd = open(fname, O_WRONLY);
FAILIF(fd < 0,
“open(%s, O_WRONLY): %s (%d)\n” ,
fname, strerror(errno), errno);
prelink_info_t info;
off_t sz = lseek(fd, 0, SEEK_END);
….
if (!(elf_little ^ is_host_little())) {
/* Same endianness */
INFO(“Host and ELF file [%s] have same endianness.\n”, fname);
info.mmap_addr = base;
}
else {
/* Different endianness */
INFO(“Host and ELF file [%s] have different endianness.\n”, fname);
info.mmap_addr = switch_endianness(base);
}
strncpy(info.tag, “PRE “, 4);
int num_written = write(fd, &info, sizeof(info));
…..
}
會在把不必要的Symbol刪除後,再把Apriori Prelink 資訊寫回動態函式庫 so ELF檔案的最後面.
7, acp Android提供的CP檔案複製工具.
Android 也提供自己的檔案複製工具acp, 在此僅附上Source Code build/tools/acp 前面的說明,供參考
The GNU/Linux “cp” uses O_LARGEFILE in its open() calls, utimes() instead of utime(), and getxattr()/setxattr() instead of chmod().  These are probably “better”, but are non-portable, and not necessary for our
purposes.