Friday, December 26, 2008

一千零一夜之 BitBake Data class

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

曾經使用過 OpenEmbedded 的開發者應該可以想像,當 bitbake my-app 被執行時,它會去做哪些事。例如,它可能會去讀 conf 檔,然後利用這資料 (e.g., PREFERRED_PROVIDER of my-app) 決定要用哪個 bb 檔。當然 bb 檔可能會 inherit 某個 class 檔或者 include 某個 inc 檔。等到需要的資料都讀進來了,bitbake 就可以開始 unpack、pack、configure 等去逐一執行這個 bb 檔所指定的各 task。如果暫時不去理會 cache、相依性或者某 bb 檔指定的某檔案是怎麼抓下來,這個想像也就是 bitbake 核心的功能之一,也是這篇文章感興趣的項目:BitBake Data class。

以 glib-2.0-native_2.16.1.bb 為例,

require glib-2.0_${PV}.bb

FILESPATH = "${FILE_DIRNAME}/glib-2.0-${PV}:${FILE_DIRNAME}/files"
DEPENDS = "gtk-doc-native"
PR = "r2"

inherit native

do_configure_prepend() {
if [ -e ${S}/${TARGET_SYS}-libtool ] ; then
echo "${TARGET_SYS}-libtool already present"
else
cp ${STAGING_BINDIR}/${TARGET_SYS}-libtool ${S}
fi
}

do_stage () {
...
}

...

可能看出 bb 檔在做的事,主要是設定變數以及定義函數 (python or shell script)。Data class 就是用來紀綠 bitbake 從各設定檔跟 bb 檔讀進來各個變數的值。特別值得注意的是,對 Data class 來說,函數跟變數並沒有太大的不同。或者說,函數只是比較特別一點的變數。在上面的例子裡,do_stage() 其實只是定義了一個叫做 do_stage 的變數,而它的值剛好是一段 shell script。bb 檔裡還會出現一些 keyword (require, inherit 等),parser 在看到它們的時候,不一定是利用 Data class 來紀錄這些資訊。

在了解 bitbake 怎麼處理變數前我們必須先知道 Data class 提供了哪些機制。我們知道 Data class 是用來紀錄 key-value pairs 用的。又,上面提到有些變數是特殊的,所以每個 key 勢必還要有 flag 來描述其性質 (是不是可以執行,可執行的話是 python or shell script 等)。為了達到這個需求,實作上 Data class 是一個 dict of dict。也就是說,

DEPENDS = "gtk-doc-native"
S = "${WORKDIR}/git"
FILES_${PN}-locale = "${datadir}/locale"

會以

d.dict['DEPENDS']['content'] = 'gtk-doc-native'
d.dict['S']['content'] = '${WORKDIR}/git'
d.dict['FILES_${PN}-locale']['content'] = '${datadir}/locale'

的形式存到 d (an instance of class Data) 裡面。而

do_stage () { some_code }

則會變成

d.dict['do_stage']['content'] = 'some_code'
d.dict['do_stage']['func'] = '1'

利用 dict of dict,Data class 輕易地為任何的 key 提供任意多的性質。

我們知道了 Data class 讓我們可以為任意的 key 標上任意的性質,但 bitbake 的需求不僅僅只是如此。更重要的是,bitbake 想提供的是變數的處理。從上面的例子可以看到,S = "${WORKDIR}/git" 進到 Data class 裡是 d['S']['content'] = '${WORKDIR}/git' 的形式,WORKDIR 這個變數並沒有被展開。Data class 在變數展開方面採用的是 lazy 的策略。也就是說變數的展開,會在讀值的時候發生。所以要等到稍後某個 task 取 S 的值時
cd ${S}

WORKDIR 才會被即時展開。

變數除了可以出現在 value 外也可以出現在 key 的部份 (如 FILES_${PN}-locale)。不同的是,key 的變數展開發生在 d.expandKeys(),而且是不可復原的。它的目的跟我們最後想看的 prepend/append/override 有關。

在 bb 檔裡頭不難看到

do_configure_prepend() { some_code }
do_install_append() { some_other_code }
TARGET_FPU_arm = "soft"

等語法。這讓開發者可以很輕易地在 task 開始或結束的地方加上一段 script,或者讓開發者可以有條件地設定某個變數。例如,我們可能希望當目標架構是 arm 時把 TARGET_FPU 設成 soft。上面這些語法在 Data class (不完全正確) 的作法是

d.dict['do_configure']['_prepend'].append('some_code')
d.dict['do_install']['_append'].append('some_other_code')
d.dict['TARGET_FPU_arm']['content'] = 'soft'


而 prepend/append/override 發生的時機,不像變數展開是在取值時即時發生,也不像變數展開並不會去修改 d 的值,prepend/append/override 只有在 d.update_data() 時才會發生,而且修改也不可復原。例如當目標架構是 arm 時,TARGET_FPU 會被 override 成 TARGET_FPU_arm 的值

d.dict['TARGET_FPU'] = d.dict['TARGET_FPU_arm']

Override 機制比這邊討論的略為複雜一些。有興趣的人建議可以直接參考實作。

BitBake parser 規範了 bb 檔的語法。但部份 parser 允許的操作並不被 Data class 直接支援。最後,不妨猜猜看 ?=, :=, += 等操作跟 Data class 如何相互作用?

Sunday, December 14, 2008

一千零一夜之 DRI

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。


DRI 讓 X clients 可以直接利用硬體來做繪圖,這點可以利用下面指令看出來:

olvaffe@m500:~$ glxgears &
[1] 24684
olvaffe@m500:~$ cat /proc/24684/maps
08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears
0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears
...
b38fa000-b58fa000 rw-s e6000000 00:0d 7315 /dev/dri/card0
b58fa000-b62fa000 rw-s e5000000 00:0d 7315 /dev/dri/card0
b62fa000-b6cfa000 rw-s e4000000 00:0d 7315 /dev/dri/card0
b6cfa000-b733a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0
b733a000-b797a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0
...
b79a0000-b7bbd000 r-xp 00000000 08:03 347365 /usr/lib/dri/i915_dri.so
b7bbd000-b7bd1000 rw-p 0021c000 08:03 347365 /usr/lib/dri/i915_dri.so
b7c00000-b7c07000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7c07000-b7c08000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7eb5000-b7f11000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2
b7f11000-b7f17000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2
...

我們可以看到,glxgears 把 i915_dri.so (intel 的 driver) 載進來並且開啟了 /dev/dri/card0。而在不使用 DRI 的時候:

olvaffe@m500:~$ LIBGL_ALWAYS_INDIRECT=1 glxgears &
[1] 24834
olvaffe@m500:~$ cat /proc/24834/maps
08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears
0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears
...
b7bd3000-b7bda000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7bda000-b7bdb000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1
...
b7e88000-b7ee4000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2
b7ee4000-b7eea000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2
...

則少了上述的檔案。這是因為繪圖方面都轉送給 X server 去處理。如果用 CPU usage 去看,也可以看出在這兩個情形下,比較忙的可能是 server 或著是 client。而就像 client 可以利用 DRI 做加速一樣,X server 在收到 client 的請求時,也可以利用 DRI 去完成這些請求,這個技術被稱為 AIGLX (Accelerated Indirect GLX)。

從上面的例子我們可以看出 DRI 這個架講的基本組成份子:提供 /dev/dri/card0 的 i915 kernel module、存取這個裝置用到的 libdrm、Mesa 提供的 libGL 與 i915_dri.so、最後 X server 那邊提供了 GLX 跟 DRI server extensions。值得一提的是,X server 這邊雖然沒有用到 libGL,但它其實用到了部份 Mesa 的 source 來實作 GLX 跟 DRI server extensions,在 AIGLX 的情況下,甚至會 dlopen i915_dri.so 來用。

Wednesday, January 2, 2008

gobject 的 marshaller

在剛開始使用 GObject 很容易遇到的一個問題是,marshaller 是什麼?因為在幫自己的物件設計 signal 的時候,有一項資訊就是 marshaller:
guint
g_signal_new (const gchar *signal_name,
GType itype,
GSignalFlags signal_flags,
guint class_offset,
GSignalAccumulator accumulator,
gpointer accu_data,
GSignalCMarshaller c_marshaller,
GType return_type,
guint n_params,
...);

雖然說搜尋 glib-genmarshal 可以找到很多生成 c_marshaller 的方法,但我們更感興趣的是為什麼會需要 marshaller 的存在。

這邊的 marshaller 更精準的說法是 closure marshaller。在 gobject 中,我們利用 g_signal_connect 連接 signal handler 時,所提供的 C 函數會先被包裝成一個 closure,然後這個 closure 才真正地連接上去。上面提到的 c_marshaller 則被用來作為這個 closure 的 closure marshaller。

Marshaller 自己本身也是 C 函數。在 signal 發生時,gobject 並不會直接呼叫 signal handler,而是呼叫該 handler 對應的 closure marshaller。換句話說,呼叫的動作變成一種間接的行為。

這層間接提供的好處是,我們的 signal handler 不一定要用 C 來寫。只要提供適當的 closure marshaller,signal handler 可以用任何語言來寫。當然這時就得用比 g_signal_connect 提供更大彈性的
gulong
g_signal_connect_closure (gpointer instance,
const gchar *detailed_signal,
GClosure *closure,
gboolean after);

來連接 signal handler。

gobject 的各種 bindings 就可以充份利用 gobject 的這項機制。像是 Perl binding 即提供了 gperl_closure_marshal 來當 marshaller,溝通在 Perl 與 C 之間。

Closure 是 gobject 用來一般化 callback function 的方法。伴隨著 callback function,資料也會在不同的語言中交換。對於資料,gobject 是用 GValue 來處理交換的問題。雖然用了不同的名詞,但G_VALUE_LCOPY 跟 G_VALUE_COLLECT 實際上就是 GValue 給 C 通用的 marshaller 跟 demarshaller。

要更清楚了解 marshaller/demarshaller 的意義,除了各 bindings 外,也可以參考 dbus-glib 的實作。在裡頭,可以看到 dbus message arguments 怎麼被 demarshal 成 GValue's 再被 marshal 成 C 型別;或者是相反過來的程序。dbus-glib 為了效能考量,有做了一些壞事。這些壞事反而是了解 gobject 很好的參考。