About me

我的相片
I'm a web and software developer from Kaohsiung, Taiwan.
I design and implement user interfaces, also contribute to open source projects.
http://penkia.net/portfolio

2008年3月6日 星期四

Linux LiveCD Boot Speed Optimization

前言 (introduction)

最佳化, 顧名思義就是對 "瓶頸" 開刀, 把它砍成平井間....我是說, 把它以各種方式改寫, 以達到能接受的範圍.

而最佳化的陷阱在於這是一條沒有盡頭的漫漫長路, 因此要不是 1) 你很明顯知道能接受的範圍在哪 或 2) 你很懶惰, 不然貿然進行是沒有好下場的.

一般 LiveCD 在開機時間上, 最明顯的瓶頸就是讀取速度, 畢竟光碟是一種循序存取優於隨機存取的媒介. 因此透過改變映像檔中檔案的排列方式, 可以顯著地改善開機速度.

同工 (related work)

當然這種途徑是一種常見的手法, 包括像 Accelerated-KNOPPIX, BeleniX LiveCD, 06 年時的 Kubuntu Dapper, 甚至是 Ubuntu 本身都有類似的實做, 但其中最困難的步驟就是紀錄下開機過程中所有讀取的檔案清單和讀取的順序 (以作為排序的依據). 以 Accelerated-KNOPPIX 為例, 他們實做了一個 cloop profiler 以取得開機的情境; BeleniX 有昇陽 DTrace 的加持; Kubuntu Dapper 則是直接朝 SquashFS 下手.

但這些工具或手段都有或多或少的缺點:
  • 都要相當程度的 "侵入", 一點都不輕鬆
  • 其他的工具像 kprobes 或 gdb 又太難了
  • 改動程式碼的規模太大, 我太虛弱無法掌握
  • 就算改好, 也不見得適用於往後的新版本
幸好在經過多方搜尋之後, 發現了一個很黃很暴力的解決方法: openlog 補綴.

紀錄 (log)

說穿了這個補綴就是透過修改 fs/open.c, 並在每次讀取成功以後 printk 一行訊息. 這樣一來所有讀取檔案的清單和順序也都能夠保留 (只要開機紀錄存的下).

雖然 openlog 的網址在寫這篇文章的時候連不上, 但其實它的修改只有短短幾行,包含兩個部份, 其中是增加一個 logfiles=1 的開機參數:
+static unsigned int open_logfiles;
+
+static int __init setup_logfiles(char *str)
+{
+ get_option(&str, &open_logfiles);
+ return 1;
+}
+
+__setup("logfiles=", setup_logfiles);
+

long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
再來就是開啟檔案時印出相關資訊:

put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
+ if (open_logfiles)
+ printk(KERN_DEBUG "FILE %s\n", filename);
fsnotify_open(f->f_dentry);
fd_install(fd, f);
}

使用 (apply)

我們只需要取得 Ubuntu 的核心原始碼:
# apt-get install linux-source-2.6.24
解開後套用此一補綴, 將原本的 config 檔複製到原始碼目錄中, 並且 make oldconfig.

特別要注意的是, 因為我們打算讓系統認為這個核心和原本是相同的, 因此要修改
Makefile 當中的版號, 讓它符合 linux-image 的套件版號, 例如:
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 24
EXTRAVERSION = -11-generic
為了紀錄住所有的資訊, 我們可以將 CONFIG_LOG_BUF_SHIFT 加大, 將這個值修改為 18 (也就是 256K), 然後執行 make bzImage.

編好的核心會在 arch/x86/boot/bzImage, 將它取代 LiveCD 當中的 vmlinuz, 並且在開機時新增參數 logfiles=1 並且等待開機完畢, 進入 X 當中.

轉換 (convert)

進入系統後, 根據文件的建議, 可以嘗試做一些開啟程式的動作, 比如說 firefox 或是 vlc 之類的. 你可以在 /var/log/syslog 找到我們所要的紀錄.

大概長這樣:
Mar  6 01:32:41 ubuntu syslogd 1.5.0#1ubuntu1: restart.
Mar 6 01:32:41 ubuntu kernel: Inspecting /boot/System.map-2.6.24-11-generic
Mar 6 01:32:41 ubuntu kernel: Loaded 27842 symbols from /boot/System.map-2.6.24-11-generic.
Mar 6 01:32:41 ubuntu kernel: Symbols match kernel version 2.6.24.
Mar 6 01:32:42 ubuntu kernel: Loaded 19134 symbols from 89 modules.
Mar 6 01:32:42 ubuntu kernel: FILE /etc/modprobe.d/blacklist-framebuffer
Mar 6 01:32:42 ubuntu kernel: [ 47.530556] FILE /etc/modprobe.d/thinkpad_acpi.modprobe
Mar 6 01:32:42 ubuntu kernel: [ 47.530584] FILE /etc/modprobe.d/blacklist-modem
Mar 6 01:32:42 ubuntu kernel: [ 47.530610] FILE /etc/modprobe.d/aliases
Mar 6 01:32:42 ubuntu kernel: [ 47.530927] FILE /etc/modprobe.d/toshiba_acpi.modprobe
Mar 6 01:32:42 ubuntu kernel: [ 47.530950] FILE /etc/modprobe.d/blacklist
...
...
但還沒完, 我們必須將這份 log 轉換成 mksquashfs 指令能夠接受的清單,
為此我寫了一隻 Perl 小程式 list-sort.pl 來達成目標:
#!/usr/bin/perl

my $syslog = $ARGV[0] || '/var/log/syslog'; # 讀進 log 檔
my @ori;
for (`grep FILE $syslog`) {
chomp($a = (split(/FILE /, $_))[-1]); # 取得我們要的那段
$a =~ s/^\///; $a =~ s/^\///; # 拿掉開頭的 /
next if $a =~ /^(dev|proc|var|media|sys|tmp|\.)/; # 略過某些目錄
push(@ori, $a); } # 存進陣列
my @dup = grep {$marked{$_}++;$marked{$_}==1;} @ori; #刪除重複項目
my $count = 10000; # 設定權重
for (@dup) { print "$_ $count\n"; $count--; } #依序印出

將輸出存成一個文字檔:
# ./list-sort.pl syslog > sort.txt
並且利用這份檔案製作壓縮的檔案系統:
# mksquashfs ../system casper/filesyste.squashfs -sort sort.txt -b 256K -info
一個最佳化過後的映像檔就完成了!

評估 (evaluation)









沒有. (爆)

因為我手邊剛好沒有測試用的空白 CD-RW, 只有隨身碟.. (扭扭)
這種最佳化方式用隨身碟測會有漂亮數據才有鬼!! (有快啦, 大概三秒)

但根據以往的經驗我相信, 以這種方法做出來的 LiveCD 是很快的!! (誠懇)

敬請指教, 謝謝!!
Pin-Shiun Chen (penkia)

沒有留言: