Kernel怎么跳轉(zhuǎn)到Android:linux與安卓的交界
上一篇寫了Uboot怎么到Linux kernel,這一章來看看linux kernel怎么到Android的。
雖然是零零碎碎的學習了一些關于Linux的知識,但是對于這個部分基本上沒有站在系統(tǒng)的角度去看過。
1、前言
kernel的啟動主要分為兩個階段。
1、階段一
從入口跳轉(zhuǎn)到start_kernel之前的階段。
對應代碼arch/arm/kernel/head.S中stext的實現(xiàn):
ENTRY(stext)
這個階段主要由匯編語言實現(xiàn)。
這個階段主要負責MMU打開之前的一些操作,以及打開MMU的操作。
由于這個階段MMU還沒有打開,并且kernel加載地址和連接地址并一致,所以需要使用位置無關設計。在運行過程中運行地址和加載地址一致(如果不明白的話建議先參考一下《[kernel 啟動流程] 前篇——vmlinux.lds分析》)。
(上一篇從uboot到kernel的地方,講了kernel啟動后的幾個階段,停在start_kernel部分)
2、階段二
start_kernel開始的階段。
2、正題-kernel-uboot
Android生在linux內(nèi)核基礎上,linux內(nèi)核啟動的最后一步,一定是啟動的android的進程。
然后我們也知道了內(nèi)核啟動分為三個階段,第一二是運行head.S文件和head-common.S,第三個階段是允許第二是運行main.c文件。
對于ARM的處理器,內(nèi)核第一個啟動的文件是arc/arm/kernel下面的head.S文件。、
當然arc/arm/boot/compress下面 也有這個文件,這個文件和上面的文件略有不同,當要生成壓縮的內(nèi)核時zImage時,啟動的是后者,后者與前者不同的時,它前面的代碼是做自解壓的,后面 的代碼都相同。
我們這里這分析arc/arm/kernel下面的head.S文件。當head.S所作的工作完成后它會跳到init/目錄下跌的 main.c的start_kernel函數(shù)開始執(zhí)行。
因為我們要研究的是過渡階段,而不是整個啟動流程。(后面會研究的。)這里直接看第三個--start_kernel階段。
asmlinkage?void?__init?start_kernel(void)??
{??
???????…………………….??
???????……………………..??
???????printk(KERN_NOTICE);??
???????printk(linux_banner);??
???????setup_arch(&command_line);??
???????setup_command_line(command_line);??
???????parse_early_param();??
???????parse_args("Booting?kernel",static_command_line,?__start___param,??
????????????????__stop___param?-?__start___param,??
????????????????&unknown_bootoption);??
……………………??
…………………………????????
???????init_IRQ();??
???????pidhash_init();??
???????init_timers();??
???????hrtimers_init();??
???????softirq_init();??
???????timekeeping_init();??
???????time_init();??
???????profile_init();??
…………………………??
……………………………??
???????console_init();??
………………………………??
………………………………??
???????rest_init();??
}??
從上面可以看出start_kernel首先是打印內(nèi)核信息,然后對bootloader傳進來的一些參數(shù)進行處理,再接著執(zhí)行各種各樣的初始化,在這其中會初始化控制臺。最后會調(diào)用rest_init();
我們再來看rest_init()函數(shù)
static?void?noinline?__init_refok?rest_init(void)??
????__releases(kernel_lock)??
{??
????int?pid;??
????kernel_thread(kernel_init,?NULL,?CLONE_FS?|?CLONE_SIGHAND);??
????............??????
}
他啟動了kernel_init這個函數(shù),再來看kerne_init函數(shù)
static?int?__init?kernel_init(void?*?unused)??
{??
????..............................??
????if?(!ramdisk_execute_command)??
????????ramdisk_execute_command?=?"/init";??
????if?(sys_access((const?char?__user?*)?ramdisk_execute_command,?0)?!=?0)?{??
????????ramdisk_execute_command?=?NULL;??
????????prepare_namespace();??
????}??
????/*??
?????*?Ok,?we?have?completed?the?initial?bootup,?and??
?????*?we're?essentially?up?and?running.?Get?rid?of?the??
?????*?initmem?segments?and?start?the?user-mode?stuff..??
?????*/??
????init_post();??
????return?0;??
}??
kernel_init先調(diào)用了prepare_namespace();然后調(diào)用了init_post函數(shù)
void?__init?prepare_namespace(void)??
{??
????..........................??
????mount_root();??
????.....................??
}??
可以看出prepare_namespace調(diào)用了mount_root掛接根文件系統(tǒng)。接著kernel_init再執(zhí)行init_post
static?int?noinline?init_post(void)??
{??
????.......................................??
????/*打開dev/console控制臺,并設置為標準輸入、輸出*/??
????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);??
????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.??
?????*/??
????//如果bootloader指定了init參數(shù),則啟動init參數(shù)指定的進程??
????if?(execute_command)?{??
????????run_init_process(execute_command);??
????????printk(KERN_WARNING?"Failed?to?execute?%s.??Attempting?"??
????????????????????"defaults...\n",?execute_command);??
????}??
????//如果沒有指定init參數(shù),則分別帶sbin、etc、bin目錄下啟動init進程??
????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的會等待init進程返回才往后面執(zhí)行,所有它一旦找到一個init可執(zhí)行的文件它將一去不復返。
綜上,內(nèi)核啟動的過程大致為以下幾步:
1.檢查CPU和機器類型
2.進行堆棧、MMU等其他程序運行關鍵的東西進行初始化
3.打印內(nèi)核信息
4.執(zhí)行各種模塊的初始化
5.掛接根文件系統(tǒng)
6.啟動第一個init進程
7.android啟動
說明一
總結(jié)一個圖:kernel 到android核心啟動過程

kernel鏡像執(zhí)行跳轉(zhuǎn)到start_kernel開始執(zhí)行,在rest_init會創(chuàng)建兩個kernel 進程(線程),其分別是為kernel_init 與kthreadd,創(chuàng)建完后系統(tǒng)通過init_idle_bootup_task蛻化為idle進程(cpu_idle)。

調(diào)用kernel_thread()創(chuàng)建1號內(nèi)核線程, 該線程隨后轉(zhuǎn)向用戶空間, 演變?yōu)閕nit進程
調(diào)用kernel_thread()創(chuàng)建kthreadd內(nèi)核線程。
init_idle_bootup_task():當前0號進程init_task最終會退化成idle進程,所以這里調(diào)用init_idle_bootup_task()函數(shù),讓init_task進程隸屬到idle調(diào)度類中。即選擇idle的調(diào)度相關函數(shù)。
調(diào)用cpu_idle(),0號線程進入idle函數(shù)的循環(huán),在該循環(huán)中會周期性地檢查
kernel_init 中會執(zhí)行/init(ramdisk_execute_command的值為"/init")

/init 啟動后執(zhí)行/system/core/init/main.cpp 中main 方法,這里執(zhí)行FirstStageMain()

(看看這到了哪里?這到了咱們的的AVB那個地方啊)
FirstStageMain()中通過execv 執(zhí)行/system/bin/init,參數(shù)為selinux_setup。這里init 跟/init 一樣,因此再次執(zhí)行init 鏡像。這里如果是重啟到bootloader,會執(zhí)行InstallRebootSignalHandlers

SetupSelinux 中再次執(zhí)行init,這里會注冊信號處理函數(shù)
從而參數(shù)second_stage,執(zhí)行SecondStageMain ,在這里解析.rc ,啟動ueventd,并等待其啟動完成。

init 鏡像通過execv會執(zhí)行兩次,分別通過FirstStageMain和SecondStageMain執(zhí)行。


Zygote是Android系統(tǒng)創(chuàng)建新進程的核心進程,負責啟動Dalvik虛擬機,加載一些必要的系統(tǒng)資源和系統(tǒng)類,啟動system_server進程,隨后進入等待處理app應用請求。到這里我們就暫時停下,別走遠了。
說明二
總結(jié)一下整個流程
第一步:手機開機后,引導芯片啟動,引導芯片開始從固化在ROM里的預設代碼執(zhí)行,加載引導程序到到RAM,bootloader檢查RAM,初始化硬件參數(shù)等功能;
第二步:硬件等參數(shù)初始化完成后,進入到Kernel層,Kernel層主要加載一些硬件設備驅(qū)動,初始化進程管理等操作。在Kernel中首先啟動swapper進程(pid=0),用于初始化進程管理、內(nèi)管管理、加載Driver等操作,再啟動kthread進程(pid=2),這些linux系統(tǒng)的內(nèi)核進程,kthread是所有內(nèi)核進程的鼻祖;
第三步:Kernel層加載完畢后,硬件設備驅(qū)動與HAL層進行交互。初始化進程管理等操作會啟動INIT進程 ,這些在Native層中;
第四步:init進程(pid=1,init進程是所有進程的鼻祖,第一個啟動)啟動后,會啟動adbd,logd等用戶守護進程,并且會啟動servicemanager(binder服務管家)等重要服務,同時孵化出zygote進程,這里屬于C++ Framework,代碼為C++程序;
第五步:zygote進程是由init進程解析init.rc文件后fork生成,它會加載虛擬機,啟動System Server(zygote孵化的第一個進程);System Server負責啟動和管理整個Java Framework,包含ActivityManager,WindowManager,PackageManager,PowerManager等服務;
第六步:zygote同時會啟動相關的APP進程,它啟動的第一個APP進程為Launcher,然后啟動Email,SMS等進程,所有的APP進程都由zygote fork生成。
那么到這里我們就把整個系統(tǒng)的啟動串聯(lián)起來了從bootrom-bootloader-kernel。當然真實的系統(tǒng)為了安全,比如說基于ARM框架的,那肯定不止這些步驟,但是大體上也是穿插在這個流程之中的。
這個bridge系列真的蠻有意思,持續(xù)做下去。感謝前輩們的優(yōu)秀blog。
參考資料:
https://blog.csdn.net/yiranfeng/article/details/103549394
https://www.cnblogs.com/littleboy123/p/13208179.html
https://www.cnblogs.com/hzl6255/p/12142762.html
https://blog.csdn.net/lei7143/article/details/114269707
https://www.cnblogs.com/linucos/archive/2012/05/21/2511619.html