1.
內核相關代碼分析init_post()函數
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);
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.");
}
/dev/console 這裏的console爲在 uboot傳進來的參數bootargs中console=/dev/ttySAC0
也是 內核打開的 第一個設備
sys_open((const char __user *) "/dev/console", O_RDWR, 0)
(void) sys_dup(0); dup表示複製的意思 打開設備
(void) sys_dup(0); dup表示複製的意思 打開設備
上邊三行用於分別定義標準輸入.標準輸出.標準錯誤所指向的設備
execute_command 這個表示從命令行獲得的參數代表內核將要執行的第一個程序
即 :run_init_process(execute_command);
如果bootargs裏沒有指定第一個執行程序的話就執行默認的程序中的一個
即 :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.");
該句通過上邊的console所定義的設備輸出
2. 關於uboot傳遞的 ”init= str“ 在內核裏處理的相關代碼如下
static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);
其中
__setup("init=", init_setup);
就是在 檢查bootargs環境變量裏邊是否有init= str時
如果有則 將str賦值給execute_command
3.busybox構建根文件系統
當我們編譯 busybox時會在 bin目錄生成一個應用程序 busybos
這個busybos是 ls cp。。等命令的組合 而bin目錄下的每一個命令均是到 busybox的鏈接
即:
# ls -al /bin/cp /bin/ls
lrwxrwxrwx 1 1000 1000 7 Jan 6 2010 /bin/cp -> busybox
lrwxrwxrwx 1 1000 1000 7 Jan 6 2010 /bin/ls -> busybox
bin目錄下的ls cp 等命令執行時都相當於執行 busybox程序
而 busybos 會根據命令的不同進行不同的操作
例如執行ls 相當於執行busybox ls
執行cp 相當於執行busybox cp
對於內核執行的默認的第一個程序 /sbin/init 它也是都busybox的一個連接
4.分析busybox源碼 分析/sbin/init
busybox源碼裏 每一個命令均對應一個 .C 文件 如 ls.c cp.c init.c
而每個.C文件裏都有一個main函數 當我們在終端執行不同的命令時都會調用不同的main函數
這些函數都被編譯生成busybos程序 根文件系統/bin /sbin裏的命令均是執行busybox程序
詳細分析/sbin/init
內核啓動的第一個程序 而我們的最終目的是啓動我們自己寫的用戶程序
所以有一個配置文件(/etc/inittab) 在這個配置文件裏指定了 後續執行哪個應用程序
init程序的工作
:讀取配置文件 解析配置文件 (根據配置文件)執行用戶程序
對應的函數 parse_inittab(); (位於main函數裏)
內容如下
:
。。。
file = fopen(INITTAB, "r");打開一個文件 INITTAB
INITTAB 宏定義爲 "/etc/inittab" 所以可得該配置文件爲 /etc/inittab
inittab的格式 Format for each entry: <id>:<runlevels>:<action>:<process>
例
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
tty2::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
分析 id項 用作終端 printf scanf stdio err..
runlevel 省略
action 何時執行 可取爲 sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, and shutdown.
process 對應的想要執行的程序
後邊的 if 語句用於說明 沒有/etc/inittable時怎麼執行
當沒有/etc/table時執行下邊代碼
if (file == NULL) {
/* No inittab file -- set up some default behavior */
#endif
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
/* Prepare to restart init when a HUP is received */
new_init_action(RESTART, "init", "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
/* sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
return;
#if ENABLE_FEATURE_USE_INITTAB
}
上邊代碼就是將生成默認的init_action結構體 並將其加入init_action_list鏈表
new_init_action(a->action, command, id); 函數分析
分析該函數 代碼如下
創建init_action 結構
struct init_action *new_action, *a, *last;
將init_action 結構放入 init_action_list 鏈表
如果該鏈表裏有一個init_action 結構體 且 command id均與傳入的command與id相同
則 覆蓋 否則 將該結構體加入 init_action_list鏈表
接着這main函數中執行
run_actions(SYSINIT); 執行 init_action_list 鏈表裏 action爲SYSINIT的proces
waitfor(a, 0); 等待執行完畢
run(a);創建 process 子進程
wpid = waitpid(runpid, &status, 0); 等待子進程執行結束
delete_init_action(a); 從 init_action_list 鏈表裏刪除該 action 的結構體
run_actions(WAIT); 執行 init_action_list 鏈表裏 action爲WAIT的proces
同 SYSINIT
run_actions(ONCE); 執行 init_action_list 鏈表裏 action爲ONCE的proces
run(a);
delete_init_action(a);
while(1)
{
/* run the respawn stuff */
run_actions(RESPAWN); 執行 init_action_list 鏈表裏 action爲 RESPAWN 的proces
/* run the askfirst stuff */
run_actions(ASKFIRST); 執行 init_action_list 鏈表裏 action爲ASKFIRST 的proces
wpid = wait(NULL);
while (wpid > 0) {
a->pid = 0;
}
}
run_actions(action);函數分析
總結
最小根文件系統需要的內容
/dev/console /dev/null(當不設置程序的id時 程序的標準輸入,標準輸出,標準錯誤,將會定位到/dev/null裏)
/etc/inittab(配置文件裏指定了 process 我們要執行的用戶process就在這裏)
庫 (init的很多函數都依靠 c庫)
/sbin/init程序(指向busybox命令)
5.構建根文件系統
編譯busybox
配置編譯工具 在頂層的makefile裏配置
Building:
=========
The BusyBox build process is similar to the Linux kernel build:
make menuconfig # This creates a file called ".config"
make # This creates the "busybox" executable
make CONFIG_PREFIX= xxx install # or make CONFIG_PREFIX=/path/from/root install
安裝到指定的目錄下
得到如下結果
book@book-desktop:/work/nfs_root/my_fs$ ls
bin linuxrc sbin usr
得到/sbin/init程序
book@book-desktop:/work/nfs_root/my_fs/sbin$ ls init -l
lrwxrwxrwx 1 book book 14 2013-08-02 23:28 init -> ../bin/busybox
創建設備 console null
mknod console c 5 1 創建字符設備 console 主設備號5 次設備號1 c表示字符設備
mknod null c 1 3
創建 /etc/inittab
mkdir etc
cd etc/
vi inittab
添加console::askfirst:-/bin/sh
安裝c庫
mkdir lib
cd /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
cp *.so* /work/nfs_root/my_fs/lib -d
*.so*爲所以的動態庫
.a 爲靜態庫 這裏只拷貝靜態庫 因爲在 make menuconfig 配置時配置的動態庫
-d 表示連接文件仍拷貝成連接文件 不加-d 時 連接文件將會變成真實內容 照成根文件系統過大
至此最小根文件系統作成
完善最小根文件系統
1. proc 將內核提供的虛擬文件系統掛接在這裏 執行ps命令時會到這裏提取信息
# mkdir proc
可以手動掛載 也可以 在寫入inittab 啓動自動掛載
方法 1 新建一個腳本 將其加入inittab文件 如下
vi /etc/init.d/rcS
在rcS腳本里寫入 mount -t proc none /proc
chmod +x /etc/init.d/rcS
然後在inittab 添加 ::sysinit:/etc/init.d/rcS
類同可以添加其它命令
方法 2 使用 mount -a 同樣在 /etc/init.d/rcS 裏添加 mount -a
改命令依賴於/etc/fstab
fstab 格式 ( busybox 的fstab文件裏)
#device mount-point type options dump fsck order
例如 掛載上邊的proc
proc /proc proc default 0 0
2. 完善dev目錄 創建設備文件
udev機制 自動創建dev下的設備 busybox裏使用的是mdev 爲udev的簡化版
使用方法如下 (源碼裏的文檔)
mkdir sys
在 fstab 裏添加 sysfs /sys sysfs default 0 0
tmpfs /dev tmpfs default 0 0
然後
在rcS裏添加 mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
網絡掛接根文件系統
1. 服務器啓動nfs服務
配置文件 虛擬機 /etc/exports中添加 要被掛接的目錄
如
/work/nfs_root/my_fs
2.重啓nfs服務
掛接命令 本機掛接 sudo mount -t nfs 10.20.17.2 :/work/nfs_root/my_fs /mnt
3.開發板手動掛接 (啓動開發板後掛接)
mkdir /mnt
mount -t nfs -o nolock 10.20.17.2 :/work/nfs_root/my_fs /mnt
4.改uboot命令行參數從NFS啓動
格式如下 參考 內核文檔 /Document/nfsroot.TXT
bootargs=noinitrd root=/dev/nfs nfsroot=10.20.17.2:/work/nfs_root/my_fs ip=10.20.17.1:10.20.17.2:255.255.255.254:255.255.255.0::eth3::off init=/linuxrc console=ttySAC0
製作鏡像文件
1. 製作yaffs映象 文件(這裏指的是yaffs2)
/work/system/ yaffs_source_util_larger_small_page_nand.tar.bz2支持製作yaffs 和yaffs2 的鏡像文件
解壓該文件
進入 Development_util_ok/yaffs2/utils 執行 make 得到
book@book-desktop:/work/system/Development_util_ok/yaffs2/utils$ ls
Makefile mkyaffsimage.c yaffs_ecc.o yaffs_tagsvalidity.c
mkyaffs2image mkyaffsimage.o yaffs_packedtags1.c yaffs_tagsvalidity.o
mkyaffs2image.c nand_ecc.c yaffs_packedtags1.o
mkyaffs2image.o nand_ecc.o yaffs_packedtags2.c
mkyaffsimage yaffs_ecc.c yaffs_packedtags2.o
book@book-desktop:/work/system/Development_util_ok/yaffs2/utils$
其中 mkyaffs2imag 用於製作yaffs2 鏡像
將其cp到 /usr/local/bin/目錄中 並chmod +x mkyaffs2imag
回到 /work/nfs_root
執行 mkyaffs2imag my_fs my_fs_yaffs2 即可的得到根文件的鏡像文件
燒寫到 flash 即可