實(shí)驗(yàn)筆記2
2017-11-06
概要: 理清u-boot的編譯過程和工作過程, 掌握Linux Kernel的編譯過程和文件結(jié)構(gòu); 根文件系統(tǒng)的過程分析;
(u-boot和kernel的編譯和燒錄過程在實(shí)驗(yàn)筆記1中)
u-boot (啟動(dòng)內(nèi)核)
具體可以參考u-boot中的README文件: ./README
1. Makefile 分析
1. 配置過程: make 100ask24x0_config
具體執(zhí)行的是:
@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0
MKCONFIG := $(SRCTREE)/mkconfig
$(@:_config=) -- 100ask24x0
/* 所以上述命令等效于: */
mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
所以從Makefile中跳轉(zhuǎn)到mkconfig中繼續(xù)執(zhí)行: mkconfig 是一個(gè)腳本文件(所以可以自閱)
結(jié)果 :
- 確定開發(fā)板的名稱: BOARD_NAME;
- 創(chuàng)建到平臺(tái)/開發(fā)板的頭文件的連接;
- 創(chuàng)建頂層Makefile包含的文件include/config.mk;
- 創(chuàng)建開發(fā)板相關(guān)的頭文件inclue/config.h;
2. 編譯過程: make
- 編譯cpu/$(CPU)/start.S;(不同的CPU, 可能還有其他的選項(xiàng))
- 對(duì)于開發(fā)板/平臺(tái)相關(guān)的每個(gè)目錄, 使用每個(gè)目錄各自的Makefile生成相應(yīng)的庫;
- 將前兩步編譯生成的 *.o, *.a 文件按照board/$(BOARDDIR)/config.mk 中指定的代碼段起始地址,
board/$(BOARDDIR)/u-boot.lds 連接腳本進(jìn)行連接; - 將上步生成的ELF格式的u-boot 轉(zhuǎn)換為二進(jìn)制格式;
2. 代碼分析
裸板實(shí)驗(yàn)的流程(回憶)
- 初始化
- 關(guān)看門狗;
- 初始化時(shí)鐘;
- 初始化SDRAM;
- 把程序從NAND FLASH 拷貝到 SDRAM;
- 設(shè)置棧;(后續(xù)需要執(zhí)行C語言程序)
- 繼續(xù)執(zhí)行程序;
u-boot 啟動(dòng)流程分析
- 硬件相關(guān)初始化:
(裸板實(shí)驗(yàn)的流程)
- 設(shè)置棧, sp->內(nèi)存; (然后調(diào)用C函數(shù))
以上過程可以看cpu/$(CPU)/start.S
具體分析 (硬件相關(guān)初始化) [第一階段]
- 進(jìn)入設(shè)為管理模式(SVC);
- 關(guān)看門狗;
- 屏蔽中斷;
- 初始化SDRAM;
- 設(shè)置SP;
- 時(shí)鐘初始化;
- 重定位(程序從FLASH拷到SDRAM)
- 清理BSS段(未初始化的數(shù)據(jù))
- 調(diào)用start_armboot;
- 讀出內(nèi)核
具體分析
- 初始化NAND FLASH 和 NOR FLASH;
- 能支持FLASH的讀和寫;
- 環(huán)境變量初始化; (默認(rèn)的 && FLASH上保存的)
- 調(diào)用main_loop;(死循環(huán))
u-boot 命令輸入;
圖示過程:
15-4.png
命令實(shí)現(xiàn) run_command()
- 輸入字符串(Name);
- Name => 動(dòng)作 => 函數(shù);
- 命令結(jié)構(gòu)體cmd_tbl_s{name, maxargs, repeatable, func*, usage, help};
- find_cmd(), 比較輸入字符串與命令結(jié)構(gòu)體中的Name;
u-boot.lds => _u_boot_cmd_start; _u_boot_cmd_end;
.u_boot_cmd -- #define struct_Section __attribute__((unused,section (".u_boot_cmd")))
<Command.h>(include)
#definn U_BOOT_CMD (name, maxargs, rep, cmd, usage, help)
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
comman/Makefile
讀出內(nèi)核的命令 nand read.jffs2(這個(gè)后綴不需要頁對(duì)齊) 0x30007FC0 kernel (mtd可見分區(qū))
命令等效于 nand read.jffs2 0x30007FC0 0x00060000 0x00200000
Flash 存儲(chǔ)的內(nèi)核是 uImage => image_header + kernel;
image_header => {ih_load(加載地址), ih_ep(入口地址)} => kernel可以存放在內(nèi)存的任意地方
bootm 命令 => 根據(jù)image_header移動(dòng)kernel到合適的地方(加載地址) => 啟動(dòng)kernel(do_bootm_linux)
do_bootm_linux => 設(shè)置kernel啟動(dòng)參數(shù) => 跳到入口地址啟動(dòng)kernel(theKernel()函數(shù)
u-boot和kernel數(shù)據(jù)交互: 在說定的地址以約定的格式, 存放數(shù)據(jù)。(地址: 0x30000100, 格式 struct tag)
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
/*
* bd->bi_arch_number -- 機(jī)器ID, 用于確定kernel是否支持這款板子(芯片);
* bd->bi_boot_params -- 入口地址
*/
// armlinux.c
setup_start_tag(bd);
setup_memory_tag (bd);
setup_command_line (bd, commandline);
setup_end_tag (bd);
內(nèi)核 (啟動(dòng)應(yīng)用程序)
配置
- 結(jié)果是生成了.config文件;
- .config里面的配置項(xiàng)有以下文件引用:
- C源碼;(宏來源于 ./include/linux/autoconf.h)
- 子目錄Makefile;
obj-y += xxx.o -- xxx.c最終被編譯進(jìn)內(nèi)核里
obj-m += yyy.o -- yyy.c最終被編譯成一個(gè)模塊
- ./include/config/auto.conf
- ./include/linux/autoconf.h (基本所有宏定義為1)
- make uImage 時(shí)發(fā)生了下列事情:
- .config => autoconf.h;
- .config => auto.conf
分析(./Documentation/kbuild/makefiles.txt)
- 字目錄下的Makefile:
- obj-y += yyy.o
- obj-m += xxx.o
- make uImage => ./arch/arm/Makefile;
- uImage <= vmlinux <= $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(kallsyms.o) FORCE
- vmlinux-init <= $(head-y) $(init-y)
- vmlinux-main <= $(core-y) $(libs-y) $(drivers-y) $(net-y)
- vmlinux-lds <= arch/$(ARCH)/kernel/vmlinux.lds
啟動(dòng)
- 處理u-boot傳遞的參數(shù); (arch/arm/kernel/head.S)
- 判斷是否支持這個(gè)CPU;
- 判斷是否支持這個(gè)單板; <= u-boot啟動(dòng)內(nèi)核時(shí)傳入的參數(shù)(機(jī)器ID);
- 建立頁表;
- 使能MMU;
- 跳轉(zhuǎn) start_kernel;
- 掛接根文件系統(tǒng);
-
最終目的, 運(yùn)行應(yīng)用程序;
Linux內(nèi)核啟動(dòng)過程圖解:
16-7(1).png
/* 內(nèi)核的啟動(dòng)流程 (函數(shù)調(diào)用) */
start_kernel
setup_arch // 解析u-boot傳入的啟動(dòng)參數(shù)
setup_command_line // 解析u_boot傳入的啟動(dòng)參數(shù)
parse_early_param
do_early_param
From __setup_start To __setup_end 調(diào)用early函數(shù)
unknown_bootoption
obsolete checksetup
From __setup_start To __setup_end 調(diào)用Non early函數(shù)
rest_init
kernel_init; (創(chuàng)建kernel thread)
prepare_namespace
mount_root // 掛接根文件系統(tǒng)
init_post
// 執(zhí)行應(yīng)用程序 (打開 /dev/console)
PS:
分區(qū)的代碼在內(nèi)核中已經(jīng)寫定, 位置在: ./arch/arm/plat-s3c24x0/common-smdk.c
struct mtd_partition smdk_default nand_part[]={};
根文件系統(tǒng) (應(yīng)用程序的基礎(chǔ)[平臺(tái)])
- kernel how to start the first application:
- open ("/dev/console"); (console -- 終端)
- sys_dup(0); sys_dup(0); (復(fù)制文件, 參考系統(tǒng)調(diào)用dup())
- run_init_process(); (可以啟動(dòng)自己的定義, 沒指定就是默認(rèn)) // 參見: ./init/main.c
busybox -- Linux 一些基礎(chǔ)命令的集合;
- init_main 作用:
- 讀取配置文件
- 解析配置文件
- 執(zhí)行用戶(程序)
busy -> init_main
parse_inittab
fopen("INITTAB", "r"); //打開配置文件 /etc/inittab
//(inittab的說明文檔在./examples/inittab)
new_init_action ();
run_actions(SYSINIT);
waitfor (a, 0); // 執(zhí)行應(yīng)用程序, 等待它執(zhí)行完畢
run (a); // 創(chuàng)建子進(jìn)程process
waitpid(runpid, &status, 0); // 等待它結(jié)束
delete_init_action (a); // 在init_action_list鏈表里刪除
run_actions(WAIT);
waitfor (a, 0); // 執(zhí)行應(yīng)用程序, 等待它執(zhí)行完畢
run (a); // 創(chuàng)建子進(jìn)程process
waitpid(runpid, &status, 0); // 等待它結(jié)束
delete_init_action (a); // 在init_action_list鏈表里刪除
run_actions(ONCE);
run (a); // 創(chuàng)建子進(jìn)程process
delete_init_action (a); // 在init_action_list鏈表里刪除
while (1) {
run_actions(RESPAWN);
if (a->pid == 0) {
a->pid = run (a);
}
run_actions(ASKFIRST);
if (a->pid == 0) {
a->pid = run (a);
}
wpid = wait(NULL);
while (wpid > 0){
a->pid = 0;
}
}
/* inittab 的格式:
* <id>:<runlevels>:<action>:<process>
* id => /dev/id, 用于終端: stdin, stdout, stderr: printf, scanf, err;
* runlevels => 應(yīng)用程序優(yōu)先級(jí)(暫時(shí)忽略)
* action => 執(zhí)行時(shí)機(jī)
* process => 應(yīng)用程序或程序
* 從默認(rèn)的 new_init_action 中推出的配置文件格式:
* ::ctrlaltdel:reboot
* ::shutdown:umount -a -r
* ::restart:init
* ::askfirst:~/bin/sh
* tty2::askfirst:~/bin/sh
* tty3::askfirst:~/bin/sh
* tty4::askfirst:~/bin/sh
* ::sysinit:/etc/init.d/rcS
*/
static void new_init_action (int action, const char *command, const char *cons);
/* 1. 創(chuàng)建一個(gè)init_action 結(jié)構(gòu);
* 2. 把這個(gè)結(jié)構(gòu)放入init_action_list 鏈表
*/
最小根文件系統(tǒng)所需的要素
- /dev/console ; /dev/null(當(dāng)程序不指向標(biāo)準(zhǔn)輸入,輸出苗分,錯(cuò)誤時(shí)可以指向NULL)
- /etc/inittab
- 配置文件指定的程序
- 應(yīng)用程序: init => busybox
- C庫(若把BusyBox編譯成靜態(tài)的時(shí)候, 不需要C庫)
制作, 燒寫根文件系統(tǒng); 使用NFS, 編譯使用驅(qū)動(dòng)文件
1. 編譯, 配置BusyBox[實(shí)驗(yàn)版本: 1.7.0]
-
解壓busybox:
tar xjf busybox-x.x.x.tar.bz2 cd busybox-x.x.x
-
修改Makefile: (新版BusyBox可以在make menuconfig 設(shè)置交叉編譯)
CROSS_COMPILE ?= arm-linux-
-
make menuconfig:
Makefile:405: *** mixed implicit and normal rules:deprecated syntax Makefile:1242: *** mixed implicit and normal rules:deprecated syntax
出現(xiàn)上述情況可參考編譯linux kernel 時(shí)的解決方案;
make & make install:
make install :
make CONFIG_PREFIX=/path/from/root install
- Complete
2. 構(gòu)建最小根文件系統(tǒng)
-
創(chuàng)建/dev/console, /dev/null
cd path/to/fs_mini mkdir dev cd dev sudo mknod console c 5 1 sudo mknod null c 1 3
-
構(gòu)造一個(gè)/etc/inittab
cd path/to/fs_mini mkdir etc vi(vim) etc/inittab console::askfirst:-/bin/sh
-
C庫
mkdir -p /path/to/fs_mini/lib cd /path/to/gcc-3.4.5-glibc-2.3.6/arm-linux/lib cp *.so* /path/to/fs_mini/lib -d (-d 保留鏈接文件)
-
進(jìn)一步完善:
創(chuàng)建 proc
cd /path/to/fs_mini mkdir proc echo "::sysinit:/etc/init.d/rcS" >> etc/inittab mkdir etc/init.d; vim etc/init.d/rcS mount -t proc none /proc (掛載proc虛擬文件系統(tǒng),用于收集當(dāng)前正在運(yùn)行的程序的信息) mount -a (讀取/etc/fstab, 根據(jù)文件內(nèi)容掛載) chmod +x etc/init.d/rcS vim etc/fstab #參考PC機(jī)上的/etc/fstab文件掛載proc #device mount-point type options dump fsck order proc /proc proc defaults 0 0
-
創(chuàng)建Dev目錄: mdev (udev簡(jiǎn)化版本)[使用方法: 參考文檔 -- ./docs/mdev.txt]
-
修改etc/fstab
#device mount-point type options dump fsck order sysfs /sys sysfs defaults 0 0 tmpfs /dev tmpfs defaults 0 0
-
修改etc/init.d/rcS
mount -a mkdir /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s
-
Complete
制作映像文件
制作yaffs2映像文件
cd /path/to/tools
tar xjf yaffs_source_util_larger_small_page_nand.tar.bz2
cd Development_util_ok
cd yaffs2/utils
make
sudo cp mkyaffs2image /usr/local/bin
sudo chmod +x /usr/local/bin/mkyaffs2image
cd path/to/fs_mini; cd ..
mkyaffs2image fs_mini fs_mini.yaffs2
制作jffs2映像文件
# 安裝zlib壓縮庫
cd /path/to/zlib-x.x.x.tar.gz
tar xzf zlib-x.x.x.tar.gz ; cd zlib-x.x.x
./configure --shared --prefix=/usr
make ; sudo make install
# 安裝mkfs.jffs2 壓縮工具
cd /path/to/mtd-utils-x.x.x.tar.bz2
tar xjf mtd-utils-x.x.x.tar.bz2 ; cd mtd-utils-x.x.x/util
make && sudo make install
# 制作jffs2鏡像文件
cd /path/to/fs_mini; cd ..
mkfs.jffs2 -n -s 2048 -e 128KiB -d fs_mini -o fs_mini.jffs2
# -s -- 一頁的大小
# -e -- 可擦除塊的大小
# 燒寫jffs2文件系統(tǒng)鏡像時(shí)需要強(qiáng)制指定文件系統(tǒng)類型
set bootargs noinitrd root=/dev/mtdblock3 rootfstype=jffs2 ....(其余選項(xiàng)不變)
nfs的使用 (參考內(nèi)核文檔: ./Documentation/nfsroot.txt)
- 安裝Ubuntu的NFS的服務(wù)
sudo apt-get install nfs-kernel-server (推薦安裝了一個(gè): open-iscsi watchdog)
\\ 以防萬一都裝上吧;
- 設(shè)置/etc/exports:
- 確定你需要掛載的目錄: /path/to/dir;
- 修改目錄權(quán)限:
sudo chmod -R /path/to/dir
- 進(jìn)入文件/etc/exports輸入:
/path/to/dir *(rw,sync,no_root_squash) # 至于選項(xiàng)參考網(wǎng)站 [nfs安裝與設(shè)置](http://www.cnblogs.com/mchina/archive/2013/01/03/2840040.html)
- 重啟nfs-kernel-server服務(wù):
sudo /etc/init.d/nfs-kernel-server restart
OR
sudo service nfs-kernel-server restart
- 在本機(jī)試掛載:
sudo mount -t nfs <ipaddr>:<path-to-dir> <target-dir>
- 進(jìn)入開發(fā)板進(jìn)行掛載:
mkdir /mnt
mount -t nfs <ipaddr>:<path-to-dir> <target-dir>>
- 解決開機(jī)掛載nfs的問題: (進(jìn)入u-boot啟動(dòng)設(shè)置界面[在等待時(shí), 按<Space>進(jìn)入])
set noinitrd root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf> init=/linuxrc console=ttySAC0
eg:
# kernel 2.22.6
set bootargs noinitrd root=/dev/nfs nfsroot=192.168.1.25:/home/yjh/WorkSpace/nfs_root/first_fs ip=192.168.1.17:192.168.1.25:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0
# kernel 3.4.2
set bootargs noinitrd root=/dev/nfs nfsroot=192.168.0.25:/home/yjh/nfs_root/new_first_mini ip=192.168.0.17:192.168.0.25:192.168.0.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200
// 具體設(shè)置以及參數(shù)意義可以參考: ./Documentation/nfsroot.txt
save
- 完成