【轉載】linux內核啓動android文件系統過程分析

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

      主要源代碼目錄介紹
Makefile (全局的Makefile)
bionic (Bionic 含義爲仿生,這裏面是一些基礎的庫的源代碼)
bootable (引導加載器)
build (build 目錄中的內容不是目標所用的代碼,而是編譯和配置所須要的腳本和工具)
dalvik (JAVA 虛擬機)
development (程序開發所須要的模板和工具)                                                    external (目標機器使用的一些庫)linux

frameworks (應用程序的框架層)
hardware (與硬件相關的庫)
packages (Android 的各類應用程序)
prebuilt (Android 在各類平臺下編譯的預置腳本)
recovery (與目標的恢復功能相關)
system (Android 的底層的一些庫)
out (編譯完成後產生的目錄,也就是咱們移植文件系統須要的目錄)android

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 的庫,每一個庫對應其中一個子目錄。app

因此,咱們提取文件系統主要是在/out/target/product/generic 目錄下,咱們能夠看到裏面有obj 目錄,進入obj 目錄看看,裏面是android 文件系統很是重要的內容:框架

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

 

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

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

/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()

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


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);
}

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 中的參數,並設置屬性系統中的參數:
一、 若是 bootmode 為
- factory,設置ro.factorytest 值為1
- factory2,設置ro.factorytest 值為2
- 其餘的設ro.factorytest 值為0
二、若是有serialno 參數,則設置ro.serialno,否則為""
三、若是有bootmod 參數,則設置ro.bootmod,否則為"unknown"
四、若是有baseband 參數,則設置ro.baseband,否則為"unknown"
五、若是有carrier 參數,則設置ro.carrier,否則為"unknown"
六、若是有bootloader 參數,則設置ro.bootloader,否則為"unknown"
七、經過全局變量(前面從/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

轉自:http://blog.sina.com.cn/s/blog_68bc1cab0100j7mv.html

相關文章
相關標籤/搜索