本章學習如何啟動第一個應用程序
1.在前面的分析中我們了解到,在init進程中內核掛接到根文件系統之后,會開始啟動第一個應用程序:
kernel_init函數代碼如下:
static int __init kernel_init(void * unused) //進入init進程 { prepare_namespace() //掛載根文件系統 { ... ... / /通過解析出來的命令行參數” root=/dev/mtdblock3”來掛接根文件系統 mount_root(); //開始掛載 } init_post(); //啟動應用程序 } }
2.接下來開始分析init_post()如何啟動應用程序的,代碼如下:
static int noinline init_post(void) { /*內核已經初始化完成,所以清除__init_begin段到__init_end段之間的數據*/ free_initmem(); unlock_kernel(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy(); /* 打開dev/console控制台設備(串口0),使用戶能輸入信息, /dev/console即成為kernel_init進程的標准輸入源(文件描述符0),
打開失敗則打印Warning: unable to open an initial console.\n */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n");
當我們刪除根文件系統的內容再啟動內核,發現串口就會打印上面的字符串,如下圖:
會顯示打開dev/console失敗,是因為根文件系統還是在root=/dev/mtdblock3, 所以能掛載根文件系統,我們擦除了mtd3內容,也就是dev里面的內容,所以無法打開console控制台。
接下來繼續分析init_post():
/*調用dup打開/dev/console文件描述符兩次, 該控制台設備就也可以供標准輸出和標准錯誤使用(文件描述符1和2),
kernel_init進程現在就擁有3個文件描述符--標准輸入、標准輸出以及標准錯誤。*/ (void) sys_dup(0); (void) sys_dup(0);
if (ramdisk_execute_command) { //若 ramdisk_execute_command為0,不運行它 run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); }
搜索上面ramdisk_execute_command,發現它是一個char型全局數組,找到它被用在init_setup()中,代碼如下:

static int __init rdinit_setup(char *str) { unsigned int i; /* 使ramdisk_execute_command數組等於str *、 ramdisk_execute_command = str; /* See "auto" comment in init_setup */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("rdinit=", rdinit_setup);
發現上面__setup和我們上節分析的掛載根文件系統的__setup都是一樣的
它是匹配命令行中以” rdinit=”開頭的字符串,由於我們uboot的命令行參數中沒有”rdint=”,所以ramdisk_execute_command=0,不執行if判斷
接下來繼續分析init_post():
if (execute_command) { // execute_command不為0, 運行它 /* run_init_process 運行目標程序成功后會一直死循環*/ run_init_process(execute_command); /*run_init_process運行失敗退出后,打印Failed to execute /linuxrc. Attempting defaults... */ printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); }
搜索上面execute_command,發現它是一個char型全局數組,找到它被用在init_setup()中,代碼如下:

static int __init init_setup(char *str) { unsigned int i; /*execute_command =str*/ execute_command = str; for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("init=", init_setup);
發現上面__setup和我們上節分析的掛載根文件系統的__setup都是一樣的
顯然這里就是用來匹配命令行中以” init=”開頭的字符串,然后再將命令行參數bootargs中的” init=/linuxrc”中的” /linuxrc”放在execute_command數組中.
(init=/linuxrc:指定內核啟動后運行的第一個腳本是當前目錄下linuxrc腳本)
最終__setup("init=", init_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 };
然后放在.init.setup段中,在內核啟動后進入start_kernel()函數中使用這個宏,並將” /linuxrc”放在execute_command數組中.
當文件系統被擦除后,就會運行linuxrc應用程序失敗,打印執行linuxrc失敗,如下圖:
接下來繼續分析init_post():
/*運行應用程序失敗后,從下面3個地方查找可能出現 init應用程序的所有地方*/ run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); /*試圖建立/bin/sh 來代替應用程序 */ run_init_process("/bin/sh"); /*如上圖所示,當前面的所有情況都失敗時,調用panic。這樣內核就會試圖同步磁盤,確保其狀態一致。
如果超過了內核選項中定義的時間,它也可能會重新啟動機器。*/ panic("No init found. Try passing init= option to kernel."); }
在這里init_post函數就分析完畢了.
3.當在內核中,能輸入數據時,表示根文件系統的應用程序啟動完畢
比如輸入ps查看進程,如下圖,(ps-process status)
接下來開始分析init進程,知道命令是怎么來的