Android 文件系統移植

主要介紹linux 內核啓動過程以及掛載android 根文件系統的過程,以及介紹android 源代碼中文件系統部分的淺析。

      主要源代碼目錄介紹
Makefile (全局的Makefile)
bionic (Bionic 含義爲仿生,這裏面是一些基礎的庫的源代碼)
bootable (引導加載器)
build (build 目錄中的內容不是目標所用的代碼,而是編譯和配置所需要的腳本和工具)
dalvik (JAVA 虛擬機)
development (程序開發所需要的模板和工 具)                                                                                     external (目標機器使用的一些庫)
frameworks (應用程序的框架層)
hardware (與硬件相關的庫)
packages (Android 的各種應用程序)
prebuilt (Android 在各種平臺下編譯的預置腳本)
recovery (與目標的恢復功能相關)
system (Android 的底層的一些庫)
out (編譯完成後產生的目錄,也就是我們移植文件系統需要的目錄)

host 目錄的結構如下所示:
out/host/
|-- common
| `-- obj (JAVA 庫)
`-- linux-x86
|-- bin (二進制程序)
|-- framework (JAVA 庫,*.jar 文件)
|-- lib (共享庫*.so)
`-- obj (中間生成的目標文件)
host 目錄是一些在主機上用的工具,有一些是二進制程序,有一些是JAVA 的程序。

target 目錄的結構如下所示:
out/target/
|-- common
| |-- R (資源文件)
| |-- docs
| `-- obj (目標文件)
`-- product
`-- generic
其中common 目錄表示通用的內容,product 中則是針對產品的內容。
在common 目錄的obj 中,包含兩個重要的目錄:
APPS 中包含了JAVA 應用程序生成的目標,每個應用程序對應其中一個子目錄,將結合每個應用程序的原始文件生成Android 應用程序的APK 包。                                                                                       JAVA_LIBRARIES 中包含了JAVA 的庫,每個庫對應其中一個子目錄。

所以,我們提取文件系統主要是在/out/target/product/generic 目錄下,我們可以看到裏面有obj 目錄,進入obj 目錄看看,裏面是android 文件系統非常重要的內容:

/obj
APPS (文件系統下/system/apps 目錄下的各種應用程序)
SHARED_LIBRARIES (存放所有動態庫)
STATIC_LIBRARIES(存放所有靜態庫)
EXECUTABLES (存放各種可執行文件)

 

Linux 內核啓動掛載android根文件系統過程分析

順便羅列一下內核啓動流程:

/arch/arm/boot/compressed/head.S:

Start:
Decompressed_kernel()             //在/arch/arm/boot/compressed/misc.c 中
Call_kernel()


Stext:
/init/main.c
Start_kernel()
Setup_arch()

Rest_init()
Init()
Do_basic_setup()
Prepare_namespace()

看到了這裏,我已激動得說不出話了,因爲來到我與掛載根文件系統最重要的接口函數。

/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static int noinline init_post(void)
{
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console./n");
(void) sys_dup(0);
(void) sys_dup(0);
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s/n",ramdisk_execute_command);
}
/*
* We try each of these until one succeeds. *
* The Bourne shell can be used instead of init if we are*
* trying to recover a really broken machine.*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting ""defaults.../n",
execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}

其中,我們看到行代碼run_init_process(execute_command);
execute_command
是從UBOOT 傳遞過來的參數,一般爲/init,也就是調用文件系統裏的init 初始化進程。如果找不到init 文件就會在
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
中找,否則報錯。

在這裏由於我們的根文件系統是從/linuxrc 開始的,所以我硬性把它改爲
if (execute_command) {
run_init_process("/linuxrc");
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults.../n", execute_command);
}

Android 文件系統初始化核心Init.c文件分析

       上面我們說的init 這個文件是由android 源代碼編譯來的,編譯後在/out/target/product/generic/root/

其源碼在/system/core/init/init.c

Init.c 主要功能:

(1)安裝SIGCHLD 信號。(如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。因此需要對SIGCHLD 信號做出處理,回收殭屍進程的資源,避免造成不必要的資源浪費。)
(2)對umask 進行清零。
        何爲umask,請看http://www.szstudy.cn/showArticle/53978.shtml
(3)爲rootfs 建立必要的文件夾,並掛載適當的分區。
/dev (tmpfs)
/dev/pts (devpts)
/dev/socket
/proc (proc)
/sys (sysfs)
(4)創建/dev/null 和/dev/kmsg 節點。
(5)解析/init.rc,將所有服務和操作信息加入鏈表。
(6)從/proc/cmdline 中提取信息內核啓動參數,並保存到全局變量。
(7)先從上一步獲得的全局變量中獲取信息硬件信息和版本號,如果沒有則從/proc/cpuinfo 中提取,並保存到全局變量。
(8)根據硬件信息選擇一個/init.(硬件).rc,並解析,將服務和操作信息加入鏈表。
在G1 的ramdisk 根目錄下有兩個/init.(硬件).rc:init.goldfish.rc 和init.trout.rc,init 程序會根據上一步獲得的硬件信息選擇一個解析。
(9)執行鏈表中帶有“early-init”觸發的的命令。
(10)遍歷/sys 文件夾,是內核產生設備添加事件(爲了自動產生設備節點)。
(11)初始化屬性系統,並導入初始化屬性文件。
(12)從屬性系統中得到ro.debuggable,若爲1,則初始化keychord 監聽。
(13)打開console,如果cmdline 中沒有指定console 則打開默認的 /dev/console
(14)讀取/initlogo.rle(一張565 rle 壓縮的位圖),如果成功則在
/dev/graphics/fb0 顯示Logo,如果失敗則將/dev/tty0 設為TEXT 模式並打開/dev/tty0,輸出文“ANDROID”字樣。
(15)判斷cmdline 中的參數,並設置屬性系統中的參數:
1、 如果 bootmode 為
- factory,設置ro.factorytest 值為1
- factory2,設置ro.factorytest 值為2
- 其他的設ro.factorytest 值為0
2、如果有serialno 參數,則設置ro.serialno,否則為""
3、如果有bootmod 參數,則設置ro.bootmod,否則為"unknown"
4、如果有baseband 參數,則設置ro.baseband,否則為"unknown"
5、如果有carrier 參數,則設置ro.carrier,否則為"unknown"
6、如果有bootloader 參數,則設置ro.bootloader,否則為"unknown"
7、通過全局變量(前面從/proc/cpuinfo 中提取的)設置ro.hardware 和
ro.version。
(16)執行所有觸發標識爲init 的action。
(17)開始property 服務,讀取一些property 文件,這一動作必須在前面
那些ro.foo 設置後做,以便/data/local.prop 不能幹預到他們。
- /system/build.prop
- /system/default.prop
- /data/local.prop
- 在讀取默認的 property 後讀取 presistent propertie,在 /data/property 中
(18)為 sigchld handler 創建信號機制
(19)確認所有初始化工作完成:
device_fd(device init 完成)
property_set_fd(property server start 完成)
signal_recv_fd (信號機制建立)
(20) 執行所有觸發標識爲early-boot 的action
(21) 執行所有觸發標識爲boot 的action
(22)基於當前property 狀態,執行所有觸發標識爲property 的action
(23)註冊輪詢事件:
- device_fd
- property_set_fd
-signal_recv_fd
-如果有keychord,則註冊keychord_fd
(24)如果支持BOOTCHART,則初始化BOOTCHART
(25)進入主進程循環:
- 重置輪詢事件的接受狀態,revents 為0
- 查詢action 隊列,並執行。
- 重啟需要重啟的服務
- 輪詢註冊的事件
- 如果signal_recv_fd 的revents 為POLLIN,則得到一個信號,獲取並處

- 如果device_fd 的revents 為POLLIN,調用handle_device_fd
- 如果property_fd 的revents 為POLLIN,調用handle_property_set_fd
- 如果keychord_fd 的revents 為POLLIN,調用handle_keychord
到了這裏,整個android 文件系統已經起來了。

初始化核心的核心init.rc文件分析

在上面紅色那一行(5)解析/init.rc,將所有服務和操作信息加入鏈表。

       parse_config_file("/init.rc");//在init.c 中代碼 (有關 /init.rc的腳本我就不貼出來了)

名詞解釋:
       Android 初始化語言由四大類聲明組成:行爲類(Actions)、命令類(Commands)、服務類(Services)、選項類(Options)。
       初始化語言以行爲單位,由以空格間隔的語言符號組成。C 風格的反斜槓轉義符可以用來插入空白到語言符號。雙引號也可以用來防止文本被空格分成多個語言符號。當反斜槓在行末時,作爲換行符。

       * 以#開始(前面允許空格)的行爲註釋。
       * Actions 和Services 隱含聲明一個新的段落。所有該段落下Commands 或 Options 的聲明屬於該段落。第一段落前的Commands 或Options 被忽略。
       * Actions 和Services 擁有唯一的命名。在他們之後聲明相同命名的類將被當作錯誤並忽略。
          Actions 是一系列命令的命名。Actions 擁有一個觸發器(trigger)用來決定action 何時執行。當一個action 在符合觸發條件被執行時,如果它還沒被加入到待執行隊列中的話,則加入到隊列最後。隊列中的action 依次執行,action 中的命令也依次執行。

           Init 在執行命令的中間處理其他活動(設備創建/銷燬 ,property 設置,進程重啓)。

          Actions 的表現形式:
          on <trigger>
          <command>
          <command>
          <command>

          重要的數據結構兩個列表,一個隊列。
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);

         *.rc 腳本中所有 service 關鍵字定義的服務將會添加到 service_list 列表中。
         *.rc 腳本中所有 on 關鍵開頭的項將會被會添加到 action_list 列表中。每個action 列表項都有一個列表,此列表用來保存該段落下的 Commands。

腳本解析過程:
parse_config_file("/init.rc")
int parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);
    if (!data)                                                                                                                                                     return -1;
    parse_config(fn, data);
    DUMP();
    return 0;
}
static void parse_config(const char *fn, char *s)                                                                                          {

    ...
   case T_NEWLINE:
   if (nargs) {
       int kw = lookup_keyword(args[0]);
       if (kw_is(kw, SECTION)) {
           state.parse_line(&state, 0, 0);
           parse_new_section(&state, kw, nargs, args);
       } else {
           state.parse_line(&state, nargs, args);
       }
       nargs = 0;
    }
...

parse_config 會逐行對腳本進行解析,如果關鍵字類型爲 SECTION ,那麼將會執行parse_new_section();
類型爲 SECTION 的關鍵字有: on 和 sevice

關鍵字類型定義在 Parser.c (system/core/init) 文件中
Parser.c (system/core/init)
#define SECTION 0x01
#define COMMAND 0x02
#define OPTION 0x04
關鍵字                 屬性
capability,          OPTION, 0, 0)
class,                 OPTION, 0, 0)
class_start,        COMMAND, 1, do_class_start)
class_stop,       COMMAND, 1, do_class_stop)
console,             OPTION, 0, 0)
critical,              OPTION, 0, 0)
disabled,          OPTION, 0, 0)
domainname,    COMMAND, 1, do_domainname)
exec,                 COMMAND, 1, do_exec)
export,              COMMAND, 2, do_export)
group,              OPTION, 0, 0)
hostname,         COMMAND, 1, do_hostname)
ifup,                   COMMAND, 1, do_ifup)
insmod,            COMMAND, 1, do_insmod)
import,               COMMAND, 1, do_import)
keycodes,         OPTION, 0, 0)
mkdir,               COMMAND, 1, do_mkdir)
mount,              COMMAND, 3, do_mount)
on,                   SECTION, 0, 0)
oneshot,           OPTION, 0, 0)
onrestart,         OPTION, 0, 0)
restart,             COMMAND, 1, do_restart)
service,           SECTION, 0, 0)
setenv,             OPTION, 2, 0)
setkey,             COMMAND, 0, do_setkey)
setprop,          COMMAND, 2, do_setprop)
setrlimit,           COMMAND, 3, do_setrlimit)
socket,           OPTION, 0, 0)
start,                COMMAND, 1, do_start)
stop,             COMMAND, 1, do_stop)
trigger,           COMMAND, 1, do_trigger)
symlink,           COMMAND, 1, do_symlink)
sysclktz,         COMMAND, 1, do_sysclktz)
user,             OPTION, 0, 0)
write,             COMMAND, 2, do_write)
chown,             COMMAND, 2, do_chown)
chmod,            COMMAND, 2, do_chmod)
loglevel,          COMMAND, 1, do_loglevel)
device,             COMMAND, 4, do_device)

parse_new_section()中再分別對 service 或者 on 關鍵字開頭的內容進行解
析。
...
case K_service:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
             state->parse_line = parse_line_service;
           return;
        }
        break;
case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
           return;
        }
        break;
...

對 on 關鍵字開頭的內容進行解析
static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    ...
    act = calloc(1, sizeof(*act));
    act->name = args[1];
    list_init(&act->commands);
    list_add_tail(&action_list, &act->alist);
    ...
}

對 service 關鍵字開頭的內容進行解析
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc;
    if (nargs < 3) {
        parse_error(state, "services must have a name and a program/n");
        return 0;
    }
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'/n", args[1]);
        return 0;
     }
//如果服務已經存在service_list 列表中將會被忽略
    svc = service_find_by_name(args[1]);
    if (svc) {
        parse_error(state, "ignored duplicate definition of service '%s'/n", args[1]);
        return 0;
    }
    nargs -= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
     if (!svc) {
        parse_error(state, "out of memory/n");
        return 0;
    }
     svc->name = args[1];
    svc->classname = "default";
    memcpy(svc->args, args + 2, sizeof(char*) * nargs);
    svc->args[nargs] = 0;
    svc->nargs = nargs;
    svc->onrestart.name = "onrestart";
    list_init(&svc->onrestart.commands);
//添加該服務到 service_list 列表
    list_add_tail(&service_list, &svc->slist);
    return svc;
}

服務的表現形式:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...

       申請一個service 結構體,然後掛接到service_list 鏈表上,name 爲服務的名稱,pathname 爲執行的命令,argument爲命令的參數。之後的 option 用來控制這個service 結構體的屬性,parse_line_service 會對 service 關鍵字後的內容進行解析並填充到 service 結構中 ,當遇到下一個service 或者on 關鍵字的時候此service 選項解析結束。
例如:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    socket zygote stream 666
   onrestart write /sys/android_power/request_state wake
服務名稱爲: zygote
啓動該服務執行的命令: /system/bin/app_process
命令的參數: -Xzygote /system/bin --zygote --start-system-server
socket zygote stream 666: 創建一個名爲:/dev/socket/zygote 的 socket ,
類型爲:stream

發佈了1 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章