寫在最前面的話:
後面的一系列博客主要圍繞MIT-OS的學習課程來創建;
爲什麼會學習mit-os?
1)對於複雜的事物,一種常見的思維就是從簡單到複雜的思維。根據這種思維去學習原始的os,透過它去熟悉os的基本概念與核心構造,從而以此作爲基礎去學習現代os的各項技術,能夠去更新創造與優化它們。
2)一種典型的西方邏輯思維——原子思想,所有系統都是可以分解爲原子結構,然後有序的組合;根據這種思維,很容易將os分割成若干流程或者若干結構的有序組合;而且在學習過程中,發現這種課程是很好的學習模板,它通過代碼的方式去引導學習去實現相關的流程與結構,這樣更有利於掌握與學習學習內容。
3)從一個更完整的角度去學習os,因爲os是最貼近硬件,所以需要接觸硬件的相關知識,特別是處理器的軟件參考手冊,從一個狹窄的角度會發現,os是處理器的軟件驅動;對於os,個人認爲一定需要自己嘗試去實現相關部分流程或者模塊,同時去調試os,才能更與os貼近,從而得到更深刻的理解。
具體課程可以到如下網址參考:http://pdos.csail.mit.edu/6.828/2012/schedule.html
俗話說得好“磨刀不誤砍柴工”,有了好刀才能勢如破竹的砍柴;也只有當把工具與環境有更好,更深的認識,才能將系統的知識有效的模擬與調試,才能將書本上的知識帶入到實際工程中來;以工程的態度去對待所學到的知識,將理論與實踐相結合,從而對概念有更深入的理解。
從某種角度來說,對於代碼或者軟件來說,更多的是經驗的累計與總結;而且它很實在,它可以實現自己的思想,同時也能夠被直觀的看到與仿真,說白了,就是任何代碼或者最好是能夠運行它,然後調試它,從實際運行過程中去體會它的意義,從而能夠更好的理解,然後被直接使用。目前最常用的代碼分析方法,就是看源代碼,這樣靜態的去分析與理解,這種方式很直觀而且有效,但是它只是停留在邏輯層面,忽略了實實在在運行環境,這樣很容易只是理解思想,很難對學習者有更好的啓發與提升;所以我的建議是,任何代碼都請去調試它,或者自己去實現它,在模擬環境中調試之。
對於操作系統的調試,目前有現成的解決方案就是用虛擬機去運行之,透過虛擬機平臺去調試它;所以對於MIT—OS的實驗課程所需要的工具與環境就是虛擬機。當然,以此作爲平臺,我們也可以去調試linux的內核。還有需要了解的一種工具就是鏈接源代碼與系統的工具——編譯器。所以我們會從如下2個方面去準備我們的工具與實驗環境,1個實驗環節來展示它們的力量。
前面兩個方面可以參考如下的部分:http://pdos.csail.mit.edu/6.828/2012/tools.html
一)虛擬機——qemu
爲什麼要選擇它呢?開源,而且功能強大——不僅能完全模擬pc環境,效率高,更重要的是它能與gdb調試工具直接進行交互,進行系統調試。其他更多的瞭解可以到官網http://wiki.qemu.org/Main_Page。當然它有很多的用處,但對於目前我們來說,更多是熟悉它的使用,能夠運行我們的os,然後去調試之。
1.下載源碼:從官網http://wiki.qemu.org/Download,解壓縮它。
2.編譯它,爲了防止仿真的系統直接將我們的終端給控制了,所以我建議編譯一個帶簡單窗口的版本,這樣更方便我們調試系統——所以我用的是SDL的圖形界面的——因爲它足夠簡單。
a)解決依賴關係如下:
qemu -->zlib
-->glib-->libffi
-->sdl
最新的qemu依賴於zlib,glib,sdl,同時glib依賴libffi。所以爲了成功編譯qemu,需要提前編譯安裝如上4個包,編譯方法就是linux的源碼安裝3步曲——configure,make,make install。
b)配置編譯參數:
./configure --target-list=i386-softmmu,x86_64-softmmu --enable-sdl --with-sdlabi=2.0 --enable-debug –-prefix=$YROOT
因爲我們主要模擬x86環境中的實現,所以我們只需要支持的虛擬機器構架爲 --target-list=i386-softmmu,x86_64-softmmu,當然如果需要支持其他構架的實現,也可以添加其他的;支持sdl圖形窗口:--enable-sdl --with-sdlabi=2.0 ;將qemu建議安裝到我們的實驗環境——由環境變量決定YROOT。
c)編譯與安裝:
make && make install
d)生成使用文檔——html版本:qemu-doc.html
make html
因爲生成時,有依賴關係:makeinfo-->textinfo.
3.常用的使用命令,彙總如下:如需要更詳細的參考可以用qemu-doc.html或者從官網獲取http://wiki.qemu.org/Manual。如下的描述很多從qemu-doc.html摘取,而且以i386平臺的工具使用,同時我們也主要使用它。
a)基本使用方式:
qemu-system-i386 [options] [disk_image]
options爲控制參數。
disk_image爲仿真使用的磁盤鏡像,就像我們電腦使用的硬盤。
b)處理器與內存的參數:
-smp X :處理器核數,比如雙核:-smp 2
-m Y:內存大小,比如1G:-m 1G
c)硬盤添加:
-hdX(a-d) file:添加磁盤鏡像文件,比如:-hdb basic.img
d)不是用圖形窗口:
-nographic:默認qemu是會使用圖形窗口,如果不是用則禁用之,它會在調試linux用到,qemu保存log到文件的功能沒有用。
e)保存debug系統信息到文件:
-d xxx -D file:調試系統的xxx信息到文件file,比如:查看系統的反彙編——qemu-system-i386 -d in_asm -D test-d
當然可以查看系統的其他信息: qemu-system-i386 -d help
f)添加串口:
-serial xxx:給系統添加調試串口,這在系統調試中很重要,比如:添加當前終端爲串口——-serial stdio。當然也可以查詢其他的串口:qemu-system-i386 -serial [TAB][TAB]
g)停止處理器:-S
h)設置gdb調試:-gdb tcp::port;使用tcp端口進行調試。這實際上就模擬了嵌入式linux系統的gdb遠程調試。詳細步驟可以參考《qemu-doc.html:3.12 GDB usage》:當然可以參考之前我的博客關於gdb的使用。快捷gdb調試設置:-s(等價於-gdb tcp::1234)。
i)監視器的使用:
-monitor xxx;使用qemu監視器,通過它可以發現系統的狀態,同時動態配置系統,極大的方便了系統調試。但是我們沒有用。
j)直接啓動linux:
qemu-system-i386 -kernel arch/i386/boot/bzImage -hda root-2.4.20.img -append "root=/dev/hda"
-kernel直接加載內核,-append爲添加給內核的參數。
二)交叉編譯工具
爲什麼需要交叉編譯工具呢?目前我們有最多的參考資料,最成熟的技術,是建立在x86的32位系統中的,但是目前我們電腦已經到了64位,所以我們需要在64位系統中編譯出32位運行系統,這樣儘可能方便調試系統。而對於64位系統或者其他處理器——比如:ARM,可以看作是系統的擴展與移植,當我們熟悉x86的32位系統中,就可以將某些概念與技術進行推廣,然後通過差異化去理解相關的概念。
網上有很多交叉編譯工具的編譯方法,都是我們的參考,但是能不能成功,這就需要自己動手實驗了。
如下所描述的內容都是參考,只有自己動手去嘗試才能保證其正確性,因爲每個人的實驗環境不一樣。
所以如下爲老生常談的內容,同時交叉編譯工具的製作是很麻煩的,而且需要很多配置的信息,大都需要詳細參考gcc官網的相關編譯註釋,從而我們爲了簡化這個流程,同時保證製作的穩定性,將相關的編譯部分製作成腳本,來自動生成相關工具。在我的博客對應的資源中有腳本的實現,其中有較詳細的註釋,保證能夠編譯成功。
參考文檔:
官方文檔:https://gcc.gnu.org/install/
編譯測試結果:https://gcc.gnu.org/ml/gcc-testresults/2015-05/msg00687.html
另外一個有效的參考文檔爲lfs(Linux From Scratch)相關編譯步驟,其中也包含了交叉編譯工具的製作——只不過它是在模擬root的方式下實現的:
從如下網址,下載最新的參考文檔:http://www.linuxfromscratch.org/lfs/download.html
編譯步驟如下:
1.下載源碼:
gcc-->glibc -->glibc-ports,mpfr,mpc,gmp,isl,kernel頭文件
-->binutils
因爲編譯器gcc依賴glibc與binutils,而glibc又依賴於glibc-ports,mpfr,mpc,gmp,isl,kernel頭文件。
所以需要到各自的官網上下載最新的源代碼即可,然後放到目錄tools-src路徑下。
2.設置與創建編譯,安裝環境:
ctools_instl_path=`pwd`/tools----工具安裝路徑
#編譯根目錄
compile_top=`pwd`/build------編譯的路徑
ctarget=i486-yiye-linux------目標主機的構架,格式如下:處理器構架-製造商-運行os。
glibc_instl_path=$ctools_instl_path/$ctarget--glibc安裝路徑
sysroot_path=$glibc_instl_path----系統的模擬根路徑
根據如上配置我們編譯出來的交叉工具爲:i486-yiye-linux-gcc,然後根據如上目錄創建對應的目錄。不能使用i386來標記處理器構架,因爲glibc已經認爲i386構架過時了,至少需要i486構架,但是不影響我們描述與使用。
3.以如下的步驟編譯所有源碼:
install_kernel_headers-------編譯與安裝內核頭文件
install_binutils--------編譯與安裝binutils工具
install_gcc_step1------第一遍編譯與安裝gcc
install_glibc---------編譯與安裝glibc
install_gcc_step2-------第二遍編譯與安裝gcc
如上所述,gcc被編譯了兩次,第一次編譯是爲了編譯glibc,第二次編譯是爲了gcc能夠引用我們編譯出來的gcc;詳細的安裝步驟與解釋請參考我的腳步,主要是很多細節需要去設置,當然有時可能需要去查看相關源代碼,根據官方文檔去猜測其真實用途。唯一一個需要多次調整gcc的使用相關庫的路徑。
4.腳本的使用:
./auto_compile.sh tools-src
然後就會生成交叉編譯工具了,如下是我編譯的5.1.0的 i486-yiye-linux-gcc的相關路徑。
tools/
├── qemu
│ ├── glib-2.44.0
│ ├── qemu-2.3.0
│ ├── texinfo-5.2
│ └── zlib-1.2.8
├── sdl
│ └── SDL2-2.0.3
├── sources
│ ├── binutils-2.25.tar.gz
│ ├── gcc-5.1.0.tar.bz2
│ ├── glibc-2.21.tar.xz
│ ├── glibc-ports-2.16.0.tar.xz
│ ├── gmp-6.0.0a.tar.xz
│ ├── isl-0.14.tar.bz2
│ ├── linux-4.0.2.tar.xz
│ ├── mpc-1.0.3.tar.gz
│ └── mpfr-3.1.2.tar.xz
├── test
│ ├── a.out
│ ├── binutils-2.25
│ └── main.c
└── tools
├── bin
├── i486-yiye-linux
├── include
├── lib
├── lib64
├── libexec
└── share
編譯出來的版本測試如下:
[yiye@yiye pc_prj]$ i486-yiye-linux-gcc -v
Using built-in specs.
COLLECT_GCC=i486-yiye-linux-gcc
COLLECT_LTO_WRAPPER=/home/yiye/study/pc_prj/tools/tools/libexec/gcc/i486-yiye-linux/5.1.0/lto-wrapper
Target: i486-yiye-linux
Configured with: ../gcc-5.1.0/configure --prefix=/home/yiye/study/pc_prj/tools/tools --target=i486-yiye-linux --with-sysroot=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-build-sysroot=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-local-prefix=/home/yiye/study/pc_prj/tools/tools/i486-yiye-linux --with-native-system-header-dir=/include --disable-nls --enable-shared --disable-static --enable-languages=c,c++ --enable-__cxa_atexit --enable-c99 --enable-long-long --enable-threads=posix --disable-multilib --with-system-zlib --enable-checking=release --enable-libstdcxx-time
Thread model: posix
gcc version 5.1.0 (GCC)
三)單步調試linux內核
爲什麼要調試linux內核?一個很直接的原因就是梳理內核的啓動流程與調試內核的關鍵流程,能夠對內核進行動態分析,不僅僅停留在靜態分析上,更深入去理解內核。推薦一個查看內核源代碼的工具——LXR:http://lxr.oss.org.cn/ident。
我們以如下步驟進行調試:
1.下載內核:最新內核www.kernel.org
2.配置內核:
make CROSS_COMPILE=i486-yiye-linux- ARCH=i386 menuconfig
3.編譯內核:
make -j8 && make install
4.製作根文件系統鏡像,因爲內核啓動過程中,需要一個根文件系統,同時也是爲了製作基於linux的最小系統進行調試。製作根文件系統可以參考《Embedded Linux Primer: A Practical, Real-World Approach》Chapter 6. System Initialization。詳情見博客資源。因爲製作根文件系統是一個比較麻煩的過程,所以我也創建了一個腳本自動生成硬盤鏡像;詳情見附件:create-simple-root.sh.一定要以root方式來運行。
在linux環境下製作根文件鏡像,需要如下工具:
a)創建鏡像文件
dd if=/dev/zero of=disk.img bs=1m count=512
b)將鏡像與設備聯繫起來
loseup -f disk.img
loseup /dev/loop0 disk.img
c)對設備文件進行分區
fdisk /dev/loop0
d)讀取鏡像文件的分區信息
kpartx -a /dev/loop0
f)對分區格式化
mkfs /dev/mapper/loop0p1
g)安裝grub
grub-install --target=XXX --boot-directoty=YYYY /dev/loop0
h)掛載設備loop
mount -t extX /dev/mapper/loopXpY YYY
i)卸載
umount YYY
kpartx -d /dev/loop0
loseup -d /dev/loop0
製作根文件系統的相關內容:
根文件系統需要一個簡單的工具集,所以busybox能夠幫上忙了。所以需要下載最新的busybox:http://www.busybox.net/編譯方法和內核一樣。
然後以如下步驟創建最小的根文件系統:
a)創建基本文件系統目錄
mkdir -pv tmp/{dev,proc,sys,run}
b)創建默認節點
mknod -m 600 tmp/dev/console c 5 1
mknod -m 666 tmp/dev/null c 1 3
c)創建初始化文件
mkdir -pv tmp/etc/init.d
cat > tmp/etc/init.d/rcS << EOF
#this is simple start script!!!
echo "mount add part " > /dev/console
mount -t proc /proc
mount -t sysfs /sys
mount -t tmpfs /run
umask 002
echo "this is first exe script" > /dev/console
export PATH=/bin:/sbin/:/usr/bin:/usr/sbin
EOF
chmod a+x tmp/etc/init.d/rcS
cat > tmp/etc/fstab << EOF
#dev mount-point type options dump fsck-order
/dev/sda1 / ext3 defaults 1 1
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
tmpfs /run tmpfs defaults 1 1
devpts /dev/pts devpts gid=5,mode=620 0 0
devtmpfs /dev devtmpfs mode=0755,nosuid 0 0
EOF
d)拷貝busybox依賴的庫
因爲busybox依賴於ld-linux.so/libc.so/libm.so等庫這些庫在編譯glibc時編譯出來了;拷貝它們到lib中。
使用自動創建腳本:
./create-simple-root.sh busybox-1.23.2/yiyeout/ i486-yiye-linux-
5.使用qemu進行調試linux內核
a)第一步使用qemu加載內核
qemu-system-i386 -S -s -kernel linux-4.0.2/arch/x86/boot/bzImage -hdb basic.img -append "root=/dev/sda1 console=ttyS0" -nographic
-S -s啓動gdb同時將程序掛起。
-hdb basic.img將基本根分區,加載爲hdb。
-kernel linux-4.0.2/arch/x86/boot/bzImage -append "root=/dev/sda1 console=ttyS0"加載內核與內核參數。
-nographic 不是用圖形界面,保證得到完整的打印信息。
b)啓動新終端,使用gdb調試內核:
gdb linux-4.0.2/vmlinux
設置斷點:
b start_kernel
執行之後:
(gdb) b start_kernel
Breakpoint 1 at 0xc19ca77c: file init/main.c, line 490.
鏈接到qemu的模擬遠程環境:
target remote localhost:1234
執行之後
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
繼續運行調試內核:
c
之後程序會被停在設置的斷點,linux-c代碼開始的地方。
(gdb) c
Continuing.
Breakpoint 1, start_kernel () at init/main.c:490
後續就是使用gdb的相關命令進行內核單步調試或者程序跟蹤了。
一葉說:根據如上的步驟已經創建好了一個基本的實驗環境,而且能夠調試內核了。這樣來說,對於系統的理解,就可以很直觀的運行與追蹤了。在如上的過程中,由兩個腳本起到了很重要的作用,其實,這是一種有效的學習與總結方式,將好的思想與方法用代碼的方式總結,這樣不僅能加深思考,同時也方便使用,提高效率。