好一段時間 , 都在 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)
Logger 本身為一個 Ring-Buffer, 如果寫入的訊息超過 Buffer 的空間 , 就會覆蓋最舊的訊息 .
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 Value |
CPU |
Screen |
Keyboard |
PARTIAL_WAKE_LOCK |
On |
Off |
Off |
SCREEN_DIM_WAKE_LOCK |
On |
Dim |
Off |
SCREEN_BRIGHT_WAKE_LOCK |
On |
Bright |
Off |
FULL_WAKE_LOCK |
On |
Bright |
Bright |
例如 , 現在有一個 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 個數 , 兩者有一個大於 64M B 大小的話 , 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
從這也可以看到 Low Memory Killer 計算原則的改變 https://www.codeaurora.org/gitweb/quic/la/?p=kernel/common.git;a=commitdiff;h=ff7229900f4d265e9aac633d845e0a7c52b48bf0 .
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 紀錄輔助除錯時 .
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
0011c 000-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
0000a 000-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
4001f 000-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
4003a 000-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
4017c 000-4017e000 r-xp 00139000 00:01 22 /lib/libc.so.6
4017e000-4017f000 rwxp 0013b000 00:01 22 /lib/libc.so.6
4017f 000-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, 目前共支援以下指令
mv |
df |
mkdir |
log |
dmesg |
id |
chmod |
ioctl |
cat |
newfs_msdos |
renice |
mount |
printenv |
smd |
lsmod |
iftop |
setprop |
notify |
watchprops |
rmmod |
hd |
insmod |
netstat |
cmp |
dd |
kill |
ionice |
date |
start |
stop |
sleep |
getprop |
sendevent |
vmstat |
ln |
getevent |
wipe |
sync |
schedtop |
top |
ifconfig |
reboot |
setconsole |
route |
rm |
nandread |
ls |
chown |
rmdir |
ps |
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.
http://blog.csdn.net/hlchou/archive/2011/01/22/6158379.aspx