Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

其中用到的程式碼可能會有變化。根據實際情況理解。

整體設計

make-tinycore-linux 是一個把以下流程跑一遍的腳本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apply_build_rc  ---------------------- 應用 build.rc 設定(如果有)
apply_tc_settings ------------------- 根據設置判斷 TinyCore 資源文件路徑
prepare_workspace ------------------- 準備工作區(build 檔案夾)
|\- (clean-up hook install) --------- 安裝退出鉤子(按需清理 build 檔案夾)
prepare_tinycore_linux_image -------- 下載 TinyCore 的內核以及 initrd 檔
prepare_packages -------------------- 準備每一個軟件包(如果有)
|\- resolve_packages ---------------- 解析安裝包依賴
| \- resolve_package_recursively -- 遞歸處理安裝包依賴
|\- download_and_unpack_packages ---- 根據安裝包列表下載安裝包並解包
extract_rootfs ---------------------- 解包 initrd 到 rootfs
apply_packages ---------------------- 拷貝安裝包到 rootfs
apply_additional_patchs ------------- 應用額外處理過程(如果有)
generate_new_rootfs ----------------- 打包 initrd 到目的地
copy_core --------------------------- 複製內核文件到目的地

(execute clean-up hook) ------------- 一但腳本結束就會觸發這個退出鉤子

每一個流程都對應著一個 bash 函数,函數定義的順序也剛好就是執行順序。

整個腳本可以分成以下階段:

  • 配置处理:apply_build_rc, apply_tc_settings, prepare_workspace
  • 系统下载:prepare_tinycore_linux_image
  • 軟件依賴處理:prepare_packages
  • 修改階段:extract_rootfs, apply_packages, apply_additional_patches
  • 構建階段:generate_new_rootfs, copy_core

配置處理

於 apply_build_rc 之前,腳本還會作一系列檢查:

  • 當前用戶是否為 root。畢竟 rootfs 內的大部分檔案權限都是 root。
  • 檢查依賴的命令是否存在。当前来说 curl、unsquashfs、cpio、zcat、uniq 都是必须的。

這時候以下命令被定義:

  • require:檢查給定命令是否存在
  • DOWNLOAD:下載檔案
  • FETCH:獲取 http 檔案內容
  • cache_files_enabled: 判斷檔案緩存是否被啟用

apply_build_rc 會在當前目錄尋找 build.rc。如果存在,則應用它。所以,變量都可從此文件中修改。

apply_tc_settings 根據設定的 TC_VERSIONTC_ARCH 來決定遠端 repo 裡,內核和 initrd 的的檔案名。若為 x86,則為 vmlinuz 和 core.gz;若為 x86_64,則為 vmlinuz64 和 corepure64.gz。這兩個變量的默認值是 13x86_64

prepare_workspace 做了如下事情:

  • 檢查 build 檔案夾是否存在。若不存在則創建。
  • 安裝退出鉤子。當腳本結束時選擇是否執行鉤子(通過 DO_NOT_CLEAN_UP 控制)

系統下載

prepare_tinycore_linux_image 很簡單:

  • 如果沒有緩存,下載內核
  • 如果沒有緩存,下載 initrd

如果緩存被關閉,將會安裝對應的刪除鉤子。

軟件依賴處理

prepare_packages 會定義三個函數:

  • resolve_packages
  • resolve_package_recursively
  • download_and_unpack_packages

主要的流程是先 resolve_package,然後 download_and_unpack_packages。但為了實現遞歸依賴處理,需要 resolve_package_recursively 來幫忙。它的原理很簡單:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
resolve_package_recursively() {
local pkgname="$1"
local depth="$2"
local pkgdep="$PACKAGE_DIR/$pkgname.dep"
# 如果包名稱為空,則推出
[ -z "$pkgname" ] && return 0

# 好看的輸出
echo "$depth|- $pkgname";

# 下載依賴列表文件,輸出到位置 $pkgdep
(FETCH -sf "$TC_REPO/$TC_VERSION.x/$TC_ARCH/tcz/$pkgname.dep" || :)>> "$pkgdep";

# 如果 依賴列表文件行數大於 0 的話,讀入每一行,清除空行,並循環讀取到 pkg 變量(這裡用了管道符,所以開啟了 subshell)
[ 0 -lt `cat "$pkgdep" | wc -l | tr -d '[:space:]'` ] && cat "$pkgdep" | sed '/^$/d' | while read pkg
do
resolve_package_recursively $pkg "$depth "
done

# Add package and dependencies to the temp file
echo "$pkgname" >> "$PACKAGE_LIST.tmp"
cat "$pkgdep" >> "$PACKAGE_LIST.tmp"

do_not_clean_up || rm -rf "$pkgdep"
}

wc -l 是計算输入文件行數的意思,但它的輸出数字开头會包含空格,所以用 tr -d [:space:] 過濾掉空格之後就只剩下数字了。tr -d 意思是刪除匹配的字符,而這裡匹配的是空格。。

cat "$pkgdep" | sed '/^$/d'sed 用於過濾掉包依賴列表裡的空行。从 http 服务器获取的内容是不可控的,为了防止空行带来的问题,我这里用 sed 清除掉所有空行,同时 resolve_package_recursively 在遇到空包名的時候也會退出循環。雙保險

以上過程最後,會產出一個 PACKAGE_LISR 的文件,包含去重之後的結果。這個文件列表就可以拿去對照著從 tinycore 下載軟件了。

每次下載包含下載和解包兩個操作。當緩存中(build/tcz)存在這個包,則跳過下載。加載完成後,開啟一個 sub-shell 來執行解包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
download_and_unpack_packages() {
echo "Start preparing $(cat "$PACKAGE_LIST" | wc -l | tr -d '[:space:]') packages..."
local _IFS="$IFS"
IFS=$'\n'
local i
for i in $(cat "$PACKAGE_LIST")
do
[ -z "$i" ] && continue

[ -f "$PACKAGE_DIR/$i" ] || (
echo "Fetching package '$i'..." ;
cd "$PACKAGE_DIR" ;
DOWNLOAD "$TC_REPO/$TC_VERSION.x/$TC_ARCH/tcz/$i" -o "$i.tmp" ;
mv "$i.tmp" "$i" ;
)

# 解包
( echo "Unpacking '$i'..."; cd "$BUILD_DIR"; unsquashfs -quiet -f "$PACKAGE_DIR/$i" ; )
done
IFS="$_IFS"
}

unsquashfs 使用 quiet 可以只輸出進度條。配合只顯示進度條的 DOWNLOAD 命令(稍後會講的),看著就很不錯。

修改

修改的第一步是先解包。

extract_rootfs 就是簡單地把 initrd 解壓出來

1
( mkdir -p "$rootfs"; cd "$rootfs"; gzip -d -c "$BUILD_DIR/$TC_RAMDISK" | cpio -idm )

apply_packages 把解包後的軟件拷貝到 rootfs 檔案夾,維持原有權限設置,覆蓋內容。

1
cp -Rp "$BUILD_DIR/squashfs-root/usr/." "$BUILD_DIR/rootfs/usr/"

apply_additional_patches 檢查 build.rc 中是否有定義 additional_patchs。若有,則執行

1
2
3
4
5
6
apply_additional_patchs() {
if declare -f additional_patchs > /dev/null 2>&1
then
( echo "Applying additional patchs."; cd $BUILD_DIR; additional_patchs; )
fi
}

構建

這一階段就是把 rootfs 重新打包成 initrd。然後把內核複製到目的地,完工。

1
2
3
4
5
6
7
8
9
10
generate_new_rootfs() {
# repackage core into initrd.gz
echo "Generating new rootfs"
( cd "$BUILD_DIR/rootfs" ; find -P . | cpio -o -H newc ) | gzip -c > "$BUILD_OUTPUT/initrd.gz"
}

copy_core() {
echo "Copying Linux core"
cp "$BUILD_DIR/$TC_CORE" "$BUILD_OUTPUT/vmlinuz"
}

DOWNLOAD 和 FETCH

我使用了 DOWNLOAD 和 FETCH 兩個指令來區分文件下載和信息獲取操作。

1
2
3
4
5
6
7
8
9
CURL_DOWNLOAD="curl -L -# --retry 10 --retry-all-errors --connect-timeout 5 --speed-limit 10 --speed-time 5"
DOWNLOAD() {
eval "$CURL_DOWNLOAD $@"
}

CURL_FETCH="curl -sfL --retry 10 --connect-timeout 5 --speed-time 5"
FETCH() {
eval "$CURL_FETCH $@"
}

--retry 指定重試次數;-L 跟隨跳轉;-s 是安靜模式,不輸出信息;-f 是遇到错误时安静不输出;--retry-all-errors 任何錯誤出現都重試;--connect-timeout 連接超時秒數;--speed-limit 低於這個速度重試;--speed-time 速度低於指定速度多久之後重試,如果不指定速度,則默認為 1;-# 打印一個進度條。

下回分解

下一篇文章將講解內核啟動後的配置過程是如何實現的。

(未完待續)

评论