在Android系統啓動時,內核引導參數上一般都會設置“init=/init”,這樣的話,如果內核成功掛載了這個文件系統之後,首先運行的就是這個根目錄下的init程序。這個程序所了什麼呢? 我們只有RFSC(Read the Fucking Source code)!! init程序源碼在Android官方源碼的system/core/init中,main在init.c裏。我們的分析就從main開始
init:(1)安裝SIGCHLD信號。(如果父進程不等待子進程結束,子進程將成爲殭屍進程(zombie)從而佔用系統資源。因此需要對SIGCHLD信號做出處理,回收殭屍進程的資源,避免造成不必要的資源浪費
static void sigchld_handler(int s)
{
write(signal_fd, &s, 1);
}
int main(int argc, char **argv)
{
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0);
struct sigaction act;
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
………………………………………..
}
Linux進程通過互相發送接收消息來實現進程間的通信,這些消息被稱爲“信號”。每個進程在處理其他進程發送的信號時都需要註冊程序,此程序被稱爲信號處理。當進程的運行狀態改變或者終止時,就會產生某種信號,init進程是所有進程的父進程,當其子進程終止產生SIGCHLD信號時,init進程需要調用信號安裝函數sigaction(),並通過參數傳遞至sigcation結構體中,已完成信號處理器的安裝。
Init進程通過上述代碼註冊與子進程相關的SIGCHLD信號處理器,並把sigcation結構體的sa_flags設置爲SA_NOCLDSTOP,該值表示僅當進程終止時才接收SIGCHLD信號。 sigchld_handler函數用於通知全局變量signal_fd,SIGCHLD信號已發生。對於產生的信號的實際處理,在init進程的事件處理循環中進行。
(2)對umask進行清零。
何爲umask,請看http://www.szstudy.cn/showArticle/53978.shtml
umask是什麼?
當我們登錄系統之後創建一個文件總是有一個默認權限的,那麼這個權限是怎麼來的呢?這就是umask乾的事情。umask設置了用戶創建文件的默認權限,它與chmod的效果剛好相反,umask設置的是權限“補碼”,而chmod設置的是文件權限碼。一般在/etc/profile、$ [HOME]/.bash_profile或$[HOME]/.profile中設置umask值。
如何計算umask值?
umask命令允許你設定文件創建時的缺省模式,對應每一類用戶(文件屬主、同組用戶、其他用戶)存在一個相應的umask值中的數字。對於文件來說,這一數字的最大值分別是6。系統不允許你在創建一個文本文件時就賦予它執行權限,必須在創建後用chmod命令增加這一權限。目錄則允許設置執行權限,這樣針對目錄來說,umask中各個數字最大可以到7。
該命令的一般形式爲:umask nnn
其中nnn爲umask置000 - 777。
如:umask值爲022,則默認目錄權限爲755,默認文件權限爲644。
(3)爲rootfs建立必要的文件夾,並掛載適當的分區。
/dev ( tmpfs )
/dev/pts ( devpts )
/dev/socket
/proc ( proc )
/sys (sysfs)
編譯Android系統源碼時,在生成的根文件系統中,不存在/dev,/proc/,/sys這類目錄,他們是系統運行時的目錄,有init進程在運行中生成,當系統終止時,他們就會消失。
Init進程執行後,生成/dev目錄,包含系統使用的設備,而後調用open_devnull_stdio();函數,創建運行日誌輸出設備。open_devnull_stdio()函數會在/dev目錄下生成__null__設備節點文件,並將標準輸入,標準輸出,標準錯誤,標準錯誤輸出全部重定向__null__設備中。
void open_devnull_stdio(void)
{
int fd;
static const char *name = "/dev/__null__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
fd = open(name, O_RDWR);
unlink(name);
if (fd >= 0) {
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) {
close(fd);
}
return;
}
}
exit(1);
}
(4)創建/dev/null和/dev/kmsg節點。
init進程通過log_init函數,生成"/dev/__kmsg__"設備節點文件。__kmsg__設備調用內核信息輸出函數printk(),init進程即是通過該函數輸出log信息。
void log_init(void)
{
static const char *name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
log_fd = open(name, O_WRONLY);
fcntl(log_fd, F_SETFD, FD_CLOEXEC);
unlink(name);
}
}
Init進程通過__kmsg__設備定義用於輸出信息的宏。關於宏輸出信息,可以使用dmesg實用程序進行確認,dmesg用於顯示內核信息。
#define ERROR(x...) log_write(3, "<3>init: " x)
#define NOTICE(x...) log_write(5, "<5>init: " x)
#define INFO(x...) log_write(6, "<6>init: " x)
(5)解析/init.rc,將所有服務和操作信息加入鏈表
parse_config_file("/init.rc");
parse_config_file()函數用來分析*.rc配置文件,用來指定init.rc文件的路徑。執行parse_config_file函數,讀取並分析init.rc文件後,生成服務列表與動作列表。動作列表與服務列表全部會以鏈表的形式註冊到service_list和action_list中,service_list和action_list是init進程中聲明的全局結構體。
(6) 初始化qemu設備,設置模擬器環境;從/proc/cmdline中提取信息內核啓動參數,並保存到全局變量。
qemu_init();
QEMU模擬器允許Android應用開發者在缺少Android實際設備的情況下運行處於開發中的應用程序。QEMU是面向PC的開源模擬器,能夠模擬具有特定處理器設備,此外還提供虛擬網絡,視頻設備等。Android模擬器是虛擬的硬件平臺,運行在模擬器的軟件可以執行ARM命令集,LCD,相機,SD卡控制器等硬件設備都可以運行在Goldfish這種虛擬平臺上。
import_kernel_cmdline(0);
static void import_kernel_cmdline(int in_qemu)
{
char cmdline[1024];
char *ptr;
int fd;
fd = open("/proc/cmdline", O_RDONLY);
if (fd >= 0) {
int n = read(fd, cmdline, 1023);
if (n < 0) n = 0;
/* get rid of trailing newline, it happens */
if (n > 0 && cmdline[n-1] == '\n') n--;
cmdline[n] = 0;
close(fd);
} else {
cmdline[0] = 0;
}
ptr = cmdline;
while (ptr && *ptr) {
char *x = strchr(ptr, ' ');
if (x != 0) *x++ = 0;
import_kernel_nv(ptr, in_qemu);
ptr = x;
}
(7)先從上一步獲得的全局變量中獲取信息硬件信息和版本號,如果沒有則從/proc/cpuinfo中提取,並保存到全局變量。
(8)根據硬件信息選擇一個/init.(硬件).rc,並解析,將服務和操作信息加入鏈表。
在G1的ramdisk根目錄下有兩個/init.(硬件).rc:init.goldfish.rc和init.trout.rc,init程序會根據上一步獲得的硬件信息選擇一個解析。
(9)執行鏈表中帶有“early-init”觸發的的命令。
action_for_each_trigger("early-init", action_add_queue_tail);觸發在init腳本文件中名字爲early-init的action,並且執行其commands,其實是: on early-init,在我們的init.rc中是沒有的。action_for_each_trigger函數會將第一個參數中的命令保存到action_add_queue_tail,而後通過drain_action_queue()函數將運行隊列中的命令逐一取出執行。
(10)遍歷/sys文件夾, 將這些目錄下的uevent 文件找出,並使kernel 重新生成那些在init 的設備管理器開始前的設備添加事件。 初始化動態設備管理,使內核產生設備添加事件(爲了自動產生設備節點), 設備文件有變化時反應給內核。
device_fd = device_init();
int device_init(void)
{
suseconds_t t0, t1;
int fd;
fd = open_uevent_socket();
if(fd < 0)
return -1;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
t0 = get_usecs();
coldboot(fd, "/sys/class");
coldboot(fd, "/sys/block");
coldboot(fd, "/sys/devices");
t1 = get_usecs();
log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
make_device("/dev/pvrsrvkm", 0, 240, 0);
#if 0
make_device("/dev/bc_example", 0, 242, 0);
#endif
#if 1
make_device("/dev/fb0", 0, 29, 0);
make_device("/dev/fb1", 0, 29, 1);
make_device("/dev/fb2", 0, 29, 2);
make_device("/dev/fb3", 0, 29, 3);
make_device("/dev/fb4", 0, 29, 4);
#endif
return fd;
}
(11)初始化屬性系統,並導入初始化屬性文件。
初始化屬性服務器,Actually the property system is working as share memory.Logically it looks like a registry under windows system。
首先創建一個名字爲system_properties的匿名共享內存區域,對並本init進程做mmap讀寫映射,其餘共享它 的進程只有讀的權限。然後將這個prop_area結構體通過全局變量__system_property_area__傳遞給property services。
接着調用函數load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)從/default.prop文件中加載編譯時生成的屬性。這個有點像Windows 下的註冊表的作用。
在Android系統中,所有的進程共享系統設置值,爲此提供了一個名稱爲屬性的保存空間。Init進程調用property_init()函數,在共享內存區域中,創建並初始化屬性域。而後通過執行中的進程鎖提供的API,訪問屬性中的設置值。但更改屬性值只能在init進程中進行。當修改屬性值時,要預先向init進程提交值變更申請,然後init進程處理該申請,並修改屬性值。
(12)從屬性系統中得到ro.debuggable,如果ro.debuggable 爲1 ,則初始化組合鍵(keychord )監聽
這段代碼是從屬性裏獲取調試標誌,如果是可以調試,就打開組合按鍵輸入驅動程序,初始化keychord 監聽。
// only listen for keychords if ro.debuggable is true
debuggable = property_get("ro.debuggable");
if (debuggable && !strcmp(debuggable, "1")) {
keychord_fd = open_keychord();
}
(13)打開console,如果cmdline中沒有指定console則打開默認的/dev/console。
if (console[0]) {
snprintf(tmp, sizeof(tmp), "/dev/%s", console);
console_name = strdup(tmp);
}
//打開console,如果cmdline 中沒有指定console 則打開默認的/dev/console
fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);
(14)讀取/initlogo.rle, 是一張565 rle 壓縮的位圖,如果成功則在/dev/fb0 顯示Logo, 如果失敗則將/dev/tty0 設爲TEXT 模式並打開/dev/tty0, 輸出文本的ANDROID 字樣。
load_565rle_image(INIT_IMAGE_FILE)函數將加載由參數傳遞過來的圖像文件,而後將該文件顯示在LCD屏幕上。如果想更改logo,只需修改INIT_IMAGE_FILE即可。由於函數只支持rle565格式圖像的顯示,再更改圖像時,注意所選圖像文件的格式。
(15) 這段代碼是用來判斷是否使用模擬器運行,如果是,就加載內核命令行參數。
if (qemu[0])
import_kernel_cmdline(1);
(16)這段代碼是根據內核命令行參數來設置工廠模式測試,比如在工廠生產手機過程裏需要自動化演示功能,就可以根據這個標誌來進行特別處理。
if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2");
else
property_set("ro.factorytest", "0");
//這段代碼是設置手機序列號到屬性裏保存,以便上層應用程序可以識別這臺手機。
property_set("ro.serialno", serialno[0] ? serialno : "");
//這段代碼是保存啓動模式到屬性裏。
property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
//這段代碼是保存手機基帶頻率到屬性裏。
property_set("ro.baseband", baseband[0] ? baseband : "unknown");
//這段代碼是保存手機硬件載波的方式到屬性裏。
property_set("ro.carrier", carrier[0] ? carrier : "unknown");
//保存引導程序的版本號到屬性裏,以便系統知道引導程序有什麼特性。
property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");
//這裏是保存硬件信息到屬性裏,其實就是獲取CPU 的信息。
property_set("ro.hardware", hardware);
//這裏是保存硬件修訂的版本號到屬性裏,這樣可以方便應用程序區分不同的硬件版本。
snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
property_set("ro.revision", tmp);
/*
(17)執行所有觸發標識爲init的action。
/* execute all the boot actions to get us started */
//執行所有觸發標誌爲init 的action
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
(18)開始property服務
/* read any property files on system or data and
* fire up the property service. This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
/*
開始property 服務,讀取一些property 文件,這一動作必須在前面那些ro.foo 設置後做,
以便/data/local.prop 不能干預到他們
- /system/build.prop
- /system/default.prop
- /data/local.prop
- 在讀取認識的property 後讀取presistent propertie,在/data/property 中
這段代碼是加載system 和data 目錄下的屬性,並啓動屬性監聽服務。
(19)為sigchld handler創建信號機制。
property_set_fd = start_property_service();
(20)創建一個全雙工的通訊機制的兩個SOCKET
/*
這段代碼是創建一個全雙工的通訊機制的兩個SOCKET,信號可以在signal_fd
和signal_recv_fd 雙向通訊,從而建立起溝通的管道。其實這個信號管理,就
是用來讓init 進程與它的子進程進行溝通的,子進程從signal_fd 寫入信息,init
進程從signal_recv_fd 收到信息,然後再做處理。
*/
/* create a signalling mechanism for the sigchld handler */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
signal_fd = s[0];
signal_recv_fd = s[1];
fcntl(s[0], F_SETFD, FD_CLOEXEC);
fcntl(s[0], F_SETFL, O_NONBLOCK);
fcntl(s[1], F_SETFD, FD_CLOEXEC);
fcntl(s[1], F_SETFL, O_NONBLOCK);
}
/* make sure we actually have all the pieces we need */
/*
(21)這段代碼是判斷關鍵的幾個組件是否成功初始化,主要就是設備文件系統是否成功初始化,屬性服務是否成功初始化,信號通訊機制是否成功初始化。
if ((device_fd < 0) ||
(property_set_fd < 0) ||
(signal_recv_fd < 0)) {
ERROR("init startup failure\n");
return 1;
}
(22) 執行所有觸發標識爲early-boot的
action action_for_each_trigger("early-boot", action_add_queue_tail);
//執行所有觸發標誌爲boot 的action
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
(23)基於當前property狀態,執行所有觸發標識爲property的action
//這段代碼是根據當前屬性,運行屬性命令。
queue_all_property_triggers();
drain_action_queue();
/* enable property triggers */
// 標明屬性觸發器已經初始化完成。
property_triggers_enabled = 1;
/*
這段代碼是保存三個重要的服務socket,以便後面輪詢使用。
*/
ufds[0].fd = device_fd;
ufds[0].events = POLLIN;
ufds[1].fd = property_set_fd;
ufds[1].events = POLLIN;
ufds[2].fd = signal_recv_fd;
ufds[2].events = POLLIN;
fd_count = 3;
//這段代碼是判斷是否處理組合鍵輪詢。
ufds[3].events = POLLIN;
fd_count++;
} else {
ufds[3].events = 0;
ufds[3].revents = 0;
}
//如果支持BOOTCHART,則初始化BOOTCHART
#if BOOTCHART
/*
這段代碼是初始化linux 程序啓動速度的性能分析工具,這個工具有一個好處,就是圖形化顯示每個進程啓動順序和佔用時間,如果想優化系統的啓動速度,記得啓用這個工具。
*/
bootchart_count = bootchart_init();
if (bootchart_count < 0) {
ERROR("bootcharting init failure\n");
} else if (bootchart_count > 0) {
NOTICE("bootcharting started (period=%d ms)\n",
bootchart_count*BOOTCHART_POLLING_MS);
} else {
NOTICE("bootcharting ignored\n");
}
#endif
/*
進入主進程循環:
- 查詢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
這段代碼是進入死循環處理,以便這個init 進程變成一個服務。
*/
for(;;) {
int nr, i, timeout = -1;
// 清空每個socket 的事件計數。
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
// 這段代碼是執行隊列裏的命令。
drain_action_queue();
// 這句代碼是用來判斷那些服務需要重新啓動。
restart_processes();
// 這段代碼是用來判斷哪些進程啓動超時。
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
#if BOOTCHART
//這段代碼是用來計算運行性能。
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
// 這段代碼用來輪詢幾個socket 是否有事件處理。
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
/*
這段代碼是用來處理子進程的通訊,並且能刪除任何已經退出或者殺死進程,這樣做可以保持系統更加健壯性,增強容錯能力。
*/
if (ufds[2].revents == POLLIN) {
/* we got a SIGCHLD - reap and restart as needed */
read(signal_recv_fd, tmp, sizeof(tmp));
while (!wait_for_one_process(0))
;
continue;
}
// 這段代碼是處理設備事件。
if (ufds[0].revents == POLLIN)
handle_device_fd(device_fd);
// 這段代碼是處理屬性服務事件。
if (ufds[1].revents == POLLIN)
handle_property_set_fd(property_set_fd);
// 這段代碼是處理調試模式下的組合按鍵。
if (ufds[3].revents == POLLIN)
handle_keychord(keychord_fd);
}
return 0;
}