tag:blogger.com,1999:blog-35196062782007773082024-03-06T00:30:46.449+08:00olvTime fliesolvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.comBlogger20125tag:blogger.com,1999:blog-3519606278200777308.post-71948119987223611062012-05-24T06:29:00.004+08:002012-05-24T06:29:44.284+08:00Boot to Gnome on Debian<p>以前剛準備要從 Windows 換到 GNU/Linux 在 Linux 連線板爬文時, 才知道有些事並不是那麼理所當然. 例如這些 Windows 上的常識
<ul>
<li>系統升級要重灌</li>
<li>系統壞掉要重灌</li>
<li>換主機板要重灌</li>
<li>換硬碟要重灌</li>
<li>用久了要重灌 (那個時代)</li>
<li>更新要重開機</li>
<li>安裝很多軟體後要重開機</li>
<li>...</li>
</ul>
在 GNU/Linux 上並不一定適用.</p>
<p>當時仿效 <a href="http://blog.vgod.tw/">vgod</a> 大神, 沒想太多就找了 Debian potato (Linux 2.2!) 來安裝. 用沒多久發現有些套件在 potato 上沒有或太舊, 很快就決定從 stable 升級到 testing; 再從 testing 升到 unstable. 除了最開始安裝 Debian potato 外, 後來都只用到 apt-get. 等到後來買了筆電第二次裝 Debian, 也已經是 7 年後的事了. 而這中間的更新除非有動到 kernel, 不然也大都也沒有重開機. 頂多只是重啟 X server. 即使換了 CPU 跟主機板, 還是舊硬碟的系統繼續用. 甚至換硬碟時也只用到 cp 跟 grub-install 來搬系統. 一切就像 Linux 板說地一樣美好.</p>
<p>期間當然有很多次更新後有東西壞掉或不能開機, 但總是幸運地能夠修好. 能夠做到這點很重要的因素就是對開機流程有基本認識</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVyx1L0ITF4cC8BbBkL0CetlefLUDxw_20K_Vtec93dBdlkRjnP1Jp5tLaTwYlgpPrWENnFhQqGxWx-FgKXHMtPFms3OenHVdbvNrE_W-jzzeY92xrBLP634dEiZ4gysCfQRTlAIKQ_2o/s1600/boot-sequence.png" imageanchor="1" style=""><img border="0" height="342" width="142" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVyx1L0ITF4cC8BbBkL0CetlefLUDxw_20K_Vtec93dBdlkRjnP1Jp5tLaTwYlgpPrWENnFhQqGxWx-FgKXHMtPFms3OenHVdbvNrE_W-jzzeY92xrBLP634dEiZ4gysCfQRTlAIKQ_2o/s400/boot-sequence.png" /></a></div>
<p>知道系統是怎麼從 bootloader 進到桌面環境, 系統更新後出了問題才不會束手無策. 雖然有時候這比重灌或重開機更花時間.</p>
<h1>Bootloader: grub</h1>
<p>在用傳統 BIOS 的環境, grub 由 boot.img 跟 core.img 兩部份組成. 因為 MBR 大小的限制, 只有 boot.img 會被寫到 MBR. 也就是說當 BIOS 把 MBR 讀到記憶體並跳過去執行時, 實際上是在跑 boot.img. 它做的事只是把 core.img 最前頭的部份讀到記憶體就再跳過去. 而這個最前頭的部份才是真正把 core.img 剩餘的部份讀完, 並接著執行後才秀出我們熟悉的開機選單. 換句話說, grub 的主體是 core.img. boot.img 只是為因應 MBR 大小限制, 被設計來載入 core.img 用的.</p>
<p>在系統中, grub-setup 可以用來把 boot.img 寫到 MBR. core.img 則可以用 grub-mkimage 產生. 之所以需要動態產生 core.img 是因為依硬碟的 partition table type 跟 filesystem 不同, 我們需要不同的 core.img. 不過一般只要執行 grub-install 就會幫我們都處理好.</p>
<p>系統更新後如果重開機不能進 grub 或 kernel, 通常是 grub 壞了. 這時得用 Debian 安裝光碟 或 USB 碟 (或其它像是 <a href="http://www.sysresccd.org">SystemRescueCd</a>) 開機. 開好機後進 console 把原來的 root mount 起來重跑一次 grub-install 常常就能解決問題. 如果是因為新版 grub 本身的問題, 那要先找舊版的 grub (或許還在 /var/cache/apt/archives ), 裝好再跑 grub-install.</p>
<p>而在 EFI 的環境, grub 只會有 core.img. grub-install 會把 core.img 複製到 EFI system partition 並設定好 EFI boot manager. 要注意的是在執行 grub-install 前需要先把 EFI system partition mount 到 /boot/efi 並且載入 efivars kernel module.</p>
<h1>Kernel & initramfs</h1>
<p>bootloader 會負責把 kernel 跟 initramfs 載入到記憶體並執行. 詳細的作法規範在 Documentation/x86/boot.txt.</p>
<p>沒特別設定時 kernel image 本身也會帶一份空的 initramfs, 所以這裡的 initramfs 指的是 external initramfs. 它是獨立的一個檔案. bootloader 可以透過舊有的 initrd 機制把 external initramfs 指定給 kernel.</p>
<p>kernel 初始化好系統後會去執行第一個 user space 的程式: init. 在 initramfs 裡有 /init 這個執行檔時 kernel 會去執行它. 否則 kernel 會把 root mount 起來, 並執行 root 裡的 /sbin/init.</p>
<p>initramfs 的 /init 如果存在時, 它在做的通常也是把 root mount 起來, 並執行 root 裡 的 /sbin/init. 之所以會需要 initramfs 是因為有些環境, 例如 root 在遠端時, kernel 做這些事比較麻煩.</p>
<p>kernel 沒辦法進到 user space 常常是因為有缺 driver. 最常見的應該是 SATA 或 filesystem driver 沒有被編進 kernel 或者沒有在 initramfs 裡.</p>
<h1>init: sysvinit</h1>
<p>sysvinit 之後可能會被 <a href="http://www.freedesktop.org/wiki/Software/systemd">systemd</a> 取代, 不過在這之前還是得熟悉 sysvinit.</p>
<p>sysvinit 的設定檔在 /etc/inittab. 在 Debian 裡它主要會去執行 /etc/rcS.d 跟 /etc/rc2.d 裡所有的 script, 並讓使用者可以從 tty1 到 tty6 登入系統. 這些 script 在做的不外乎是 mount filesystem, 對網路, 系統時間等做基本設定, 還有啟動系統服務. 很多 script 會到 /etc/default 底下讀設定, 所以可以透過修改 /etc/default 裡的檔案來改變這些 script 的行為.</p>
<p>如果開機過程在這個部份出問題時, 可以試著在 kernel command line 加上 single 進入單人模式或 init=/bin/sh 讓 shell 當 init process. 因為這邊都是 script, 有問題時也比較容易解決.</p>
<p>一個多重開機到 Windows 後再回來可能會出現的問題是系統時間差了 8 小時. 這是因為 Debian 在把系統時間寫到 RTC 時會用 UTC 時間, 而 Windows 會把 RTC 的時間當成是 UTC+8. 如果遇到這個問題可以到 /etc/default/rcS 把 UTC 設定 no.</p>
<h1>Display Manager: gdm</h1>
<p>gdm 會把 X server 跑起來, 並顯示我們熟悉的圖形登入畫面. 它會掃描
<pre>/usr/share/gdm/BuiltInSessions 跟
/usr/share/xsessions</pre>
裡面的 .desktop 檔找出系統提供的 session. 當我們選好 session 並成功登入時, 它會執行
<pre>/etc/gdm/Xsession session-name</pre>
/etc/gdm/Xsession 這個 script 會讀入 /etc/X11/Xsession.d 底下所有的 script. 它們在最後通常會執行類似這樣的命令
<pre>ssh-agent dbus-launch some-session-manager</pre>
讓 session manager 帶出桌面環境來.</p>
<p>如果想要額外設定別的環境變數的話, 可以寫在 ~/.xsessionrc. 或者如果不想跑系統現有的 session 的話, 也可以自己寫 shell script 存成 ~/.xsession. 如此 /etc/gdm/Xsession 最後會改執行 ~/.xsession 而不是 some-session-manager. 這些細節可以在 /etc/X11/Xsession.d 裡看到.</p>
<p>在 Debian 裡輸入法現在是透過 im-config 在管理. 它也是利用 Xsession.d 的機制在登入的過程中被跑起來.</p>
<h1>Session Manager: gnome-session</h1>
<p>登入後的桌面環境是由 session manager 帶出來的. 一般情況 gnome-session 會參照 /usr/share/gnome-session/sessions/gnome.session 執行 gnome-shell 與 gnome-settings-daemon. 它同時也會執行
<pre>~/.config/autostart
/usr/share/gnome/autostart
/etc/xdg/autostart</pre>
裡所有沒被 disable 的程式. 後者可以利用 gnome-session-properties 做增減.</p>
<p>gnome-shell 就是眼睛看得到的部分, 這邊不提. gnome-settings-daemon 的功能之一是它會監看某些 GSettings key 並做回應. 例如在 terminal 執行
<pre>$ gsettings set org.gnome.desktop.background picture-uri file://<path-to-image></pre>
gnome-settings-daemon 會收到這項改變, 並且把桌面的背景圖片換成新指定的圖片. 或者執行
<pre>$ gsettings set org.gnome.desktop.interface gtk-theme <theme-name></pre>
gnome-settings-daemon 會把新的 GTK+ theme name 廣播出去. 所有 GTK+-based 的程式也會自動換用新的 theme.</p>olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-51586501224754665802009-12-25T16:22:00.003+08:002009-12-25T17:04:31.601+08:00Android with 3D Effects年終了。<br /><br />最近幾個月不知道為什麼,沒辦法專心工作。利用上 ptt 跟看漫畫的空檔,把說很久的特效弄出可以 demo 的樣子<br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/pIBHQiqcdTM&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/pIBHQiqcdTM&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br /><br />感謝 <a href="http://i-miss-erin.blogspot.com/">Erin</a> 的幫忙,這段影片展示了 Cube 跟 Fire 特效。 WindowManagerService 允許的地方都可以接特效,但現在只有接在程式啟動時。<br /><br />目前的進展可以在 <a href="http://gitorious.org/android-eeepc/base/commits/effects">gitorious</a> 上取得。不過很顯然地,還有很多東西要弄。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com1tag:blogger.com,1999:blog-3519606278200777308.post-83995143978484660992009-11-29T22:54:00.007+08:002009-11-30T01:09:46.174+08:00Fast bit counting最近 mesa3d-dev mailing list 有一個討論串在討論 <a href="http://sourceforge.net/mailarchive/message.php?msg_name=1259431876.2186.19.camel%40ztoshiba">bit counting</a>,也就是在算一個 unsigned int 裡有幾個 bit 是 1。<br /><br />假設 unsigned int 有 32 bits 寬,最簡單的算法就是一個迴圈跑 32 次,一個一個 bit 去看。想當然這不是有效率的算法。事實上,只要做<a href="http://cgit.freedesktop.org/mesa/mesa/commit/?id=c93dcbfea7b8e1cd0f14a96bc466419bdce7eb30">簡單的修改</a>,就可以讓迴圈跑的次數降到跟 bit 為 1 的 bit 數相同。換句話說,如果 unsigned int 裡只有 3 個 bit 為 1,迴圈只要跑三次就可以。不過像這麼基礎的運算,一定有不少人下過功夫在找最快的算法。一個問題,工程師會去尋找更好的方法;但是身為鄉民,我們感興趣的只有最好的方法,而且我們從不自己找!很幸運地,在最開頭提到的討論串裡頭,就有人提出一個號稱最快的算法。<br /><br />討論串中提到的方法經過簡化可以寫成<br /><br /><pre class="programlisting">int bitcount(unsigned int v)<br />{<br /> v = v - ((v >> 1) & 0x55555555);<br /> v = (v & 0x33333333) + ((v >> 2) & 0x33333333);<br /> v = (v + (v >> 4)) & 0x0f0f0f0f;<br /> return (v * 0x01010101) >> 24;<br />}</pre><br /><br />在深入去看這段程式碼前,最好可以用抽像點的方式看它想做的事<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioVhjRowhfqj_kOpGFXN5v203FQNu5AwYplbozv8GgTgiq1gtBh00vMf_gtlIT3ludOhozIauJACF26vqojhN0XV4JptND6K1sTCSdx23Ci_PqlrQilclpFBC6x74rUdTgNGJ6I4bUh5M/s1600/bitcount1.png"><img style="cursor:pointer; cursor:hand;width: 400px; height: 125px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioVhjRowhfqj_kOpGFXN5v203FQNu5AwYplbozv8GgTgiq1gtBh00vMf_gtlIT3ludOhozIauJACF26vqojhN0XV4JptND6K1sTCSdx23Ci_PqlrQilclpFBC6x74rUdTgNGJ6I4bUh5M/s400/bitcount1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5409547226103072562" /></a><br /><br />上圖中,v0 是使用者的輸入。展開成二進位表示法,每個 bit 的值不是 1 就是 0。如果有一個方法可以讓所有的 bit 變成兩個兩個一組 (v1)、4 個 4 個一組 (v2)、8 個 8 個一組 (v3)、一直到 32 個 bit 自己成一組 (v5),那 v5 的值剛好就是我們要的結果。從 v4 看起,假設我們已經有 v4 了,那麼應該不難發現<br /><pre class="programlisting">v5 = (v4 & 0xffff) + (v4 >> 16);</pre><br /><br />以此類推,很快就可以發現我們需要的是<br /><br /><pre class="programlisting">int bitcount2(unsigned int v)<br />{<br /> v = (v & 0x55555555) + ((v >> 1) & 0x55555555); /* v1 */<br /> v = (v & 0x33333333) + ((v >> 2) & 0x33333333); /* v2 */<br /> v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f); /* v3 */<br /> v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff); /* v4 */<br /> v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff); /* v5 */<br /> return v;<br />}</pre><br /><br />這時再回過頭去看 bitcount,可以注意到雖然算法不完全相同,但它前三行在做的也是求 v1、v2 與 v3。但它接下來不是求 v4,而是把 v3 乘上 0x01010101。透過四則運算,這個乘法可以改寫成加法<br /><br /><pre class="programlisting">v = (v3 << 24) + (v3 << 16) + (v3 << 8) + v3</pre><br /><br />把 v3 的展開式代入,會發現<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7x9JCmhiYBrXt25Zl5j3apbEkccc2fSIEZ9UjesQU3tOGhidCQMSqDAYW5TwfmKPf6zNxonsWa_hXzFvJcjdhsMR6L-_wFDILAN-4qvcZghW_QF2iRtjLRdWFkogYXcHeJY-F-ZxnqtE/s1600/bitcount2.png"><img style="cursor:pointer; cursor:hand;width: 400px; height: 29px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7x9JCmhiYBrXt25Zl5j3apbEkccc2fSIEZ9UjesQU3tOGhidCQMSqDAYW5TwfmKPf6zNxonsWa_hXzFvJcjdhsMR6L-_wFDILAN-4qvcZghW_QF2iRtjLRdWFkogYXcHeJY-F-ZxnqtE/s400/bitcount2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5409554173801458194" /></a><br /><br />將這個結果向右 shift 24 bits 得到的也是 v5。這就是 bitcount 的做法。<br /><br />如果把寫死的 magic number 換成適當型別的除法與補數運算,這個 bitcount 函數對 32/64/128-bits 寬的 unsigned int 都可以適用。寬度的限制是來自於 v4' 的第一個小括號,它不能大於或等於 2^8。不過如果 bitcount 求到 v4 再來做乘法,這個限制也可以被放寬。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com6tag:blogger.com,1999:blog-3519606278200777308.post-34244844274683148642009-06-25T10:47:00.006+08:002009-06-25T15:19:58.445+08:00一千零一夜之 GEM Object<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br /><br />GEM object 是一種 buffer,一種可以在 A 程式被配置,在 B 程式被使用的 buffer。當 userspace 產生一個 GEM object 時,以 i915 為例,它會呼叫 drm_gem_object_alloc。從這個函數的頭幾行<br /><br /><pre class="programlisting">struct drm_gem_object *obj;<br /><br />BUG_ON((size & (PAGE_SIZE - 1)) != 0);<br /><br />obj = kcalloc(1, sizeof(*obj), GFP_KERNEL);<br /><br />obj->dev = dev;<br />obj->filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);<br />if (IS_ERR(obj->filp)) {<br /> kfree(obj);<br /> return NULL;<br />}</pre><br /><br />可以看出,它只是一塊 shared memory。就這個方式來看,GEM object 其實不是新的東西。<br /><br />但這塊 buffer 除了 A、B 程式會用到外,它還另外有一個使用者,這個使用者是 GPU。之所以把 shared memory 包裝成 GEM object 最主要的原因是,kernel 需要同步 CPU 和 GPU 對這塊 buffer 的存取。很明顯地,如果讓 CPU 與 GPU 同時寫入同一塊記憶體,出來的結果一定無法預期。在 GEM object 裡,處理同步的方法是引入 domain 的機制。所有人在存取 GEM object 前,必須先把它的 domain 設好。如果是 GPU 要讀取或寫入,那就需要先把 read domain 或 write domain 設成 GPU;相對的,如果是 CPU 要讀取或寫入,則需要把 read/write domain 設成 CPU。DRM 保證的是,只要正確地指定 domain,存取到的資料也會是正確的。<br /><br />為了解 domain 跟同步的關係,我們可以看一下當我們把 read domain 指定為 CPU 時,kernel 會幫我們做什麼處理。這部份完整的程式碼可以在 i915_gem_object_set_to_cpu_domain 看到。<br /><br />首先,kernel 要確定之前所下的 GPU 指令已經全部執行完畢,而且 GPU cache 要 flush 掉<br /><br /><pre class="programlisting">i915_gem_object_flush_gpu_write_domain(obj);<br />ret = i915_gem_object_wait_rendering(obj);</pre><br /><br />GPU 的運作方式是會依序執行某個指定的 ring buffer 裡面的指令,而開發者要做的是把指令放到這個 buffer。為了達到目的,kernel 會在 ring buffer 最後加上 flush 跟 interrupt 指令,並且開始等待中斷。我們可以想像,當 kernel 收到中斷時,也就表示之前的指令都已經執行,包含它自己加上的 flush 指令。<br /><br />事實上,CPU 除了直接存取 shared memory 外,也可以從 AGP aperture 去存取,這在 i915 裡稱做 GTT domain。所以 kernel 也要確定這邊沒有資料被 cache 住<br /><br /><pre class="programlisting">i915_gem_object_flush_gtt_write_domain(obj);</pre><br /><br />這些動作可能造成記憶體上的資料被改變,而,這時候 CPU 還不知道這件事。所以只要再確保 CPU 可以知道這些變動<br /><br /><pre class="programlisting">i915_gem_object_set_to_full_cpu_read_domain(obj);<br />if ((obj->read_domains & I915_GEM_DOMAIN_CPU) == 0) {<br /> i915_gem_clflush_object(obj);<br /> obj->read_domains |= I915_GEM_DOMAIN_CPU;<br />}</pre><br /><br />我們就可以說 GEM object 已經被移到 CPU read domain。<br /><br />GEM object 是 shared memory 的特性,讓 X server 可以直接把 buffer 傳給 client 做 DRI。它同時也可以當做到 OpenGL 裡頭各種 buffer object 的 storage。除了 Intel 外,在 2.6.31 也可以看到初步的 <a href="http://airlied.livejournal.com/66958.html">ATI Radeon KMS</a> 支援。雖然從使用者的經驗來看,似乎只是進入另一個黑暗時期。但是在<a href="http://keithp.com/blogs/Sharpening_the_Intel_Driver_Focus/">領袖</a>的帶領下,我們好像也可以看到光了!olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-21889393444725628602009-06-24T10:10:00.004+08:002009-06-24T12:10:28.473+08:00一千零一夜之 Kernel and Firmware<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />Driver 需要 firmware 時會呼叫 request_firmware,這個函數會在 /sys 底下產生適當的裝置讓 userspace 上傳 firmware 給 driver。它會等到 firmware 上傳完成 (或發生錯誤) 才回傳。除了建立適當的裝置,它也會送一個 uevent,內容類似<br /><br /><pre class="programlisting">KERNEL[1245809267.000297] add /devices/pci0000:00/0000:00:1c.2/0000:02:00.0/firmware/0000:02:00.0 (firmware)<br />UDEV_LOG=3<br />ACTION=add<br />DEVPATH=/devices/pci0000:00/0000:00:1c.2/0000:02:00.0/firmware/0000:02:00.0<br />SUBSYSTEM=firmware<br />FIRMWARE=iwlwifi-3945-2.ucode<br />SEQNUM=1226</pre><br /><br />uevent 事件通常是 udev 在處理,在遇到 firmware 事件,也就是 SUBSYSTEM 是 firmware 的事件時,udev 預設會執行 /lib/udev/firmware.agent (在 Debian 上) 來上傳 firmware。簡化過的 firmware.agent 其實只做了<br /><br /><pre class="programlisting">echo 1 > /sys/$DEVPATH/loading # 準備上傳<br />cat $firmwaredir/$FIRMWARE > /sys/$DEVPATH/data<br />echo 0 > /sys/$DEVPATH/loading # 上傳完畢</pre><br /><br />注意到上面大寫的變數內容都是 uevent 提供,像是 $DEVPATH 就是 kernel 剛產生用來接受 firmware 上傳的虛擬裝置。<br /><br />這是 firmware 擺在 filesystem 上的情形。當 CONFIG_FIRMWARE_IN_KERNEL 打開時,如果 driver 被編進 kernel,firmware 也會一起被編進去。在 request_firmware 中有一段<br /><br /><pre class="programlisting">for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;<br /> builtin++) {<br /> if (strcmp(name, builtin->name))<br /> continue;<br /> dev_info(device, "firmware: using built-in firmware %s\n",<br /> name);<br /> firmware->size = builtin->size;<br /> firmware->data = builtin->data;<br /> return 0;<br />}</pre><br /><br />會先檢查 driver 要求的 firmware 是否有內建。這邊的 __start_builtin_fw 與 __end_builtin_fw 是 link time 產生的 symbol,所以不會在任何 C code 找到它們的定義。在 vmlinux.lds (或 include/asm-generic/vmlinux.lds.h) 可以看到,它們的值會是所有 .builtin_fw section 內容的開始位址與結束位址。<br /><br />.builtin_fw 的內容要再找到 firmware/Makefile。在那份 Makefile 可以看到 kbuild 會為每一個要內建的 firmware 產生一份 assembly code。這些 assembly code 編出來的 object code 會跟 kernel link 在一起,達成 firmware 內建的目的。從 assembly code 可以看出,這些 object code 的內容會是 firmware 本身,以及一個放在 .builtin_fw section 的資料結構,給出 firmware 的檔名、位置、還有大小。<br /><br />這邊看到內建 firmware 的做法其實是 kernel 常見的手法之一。寫 driver 最開始會加的 module_init(entry_point) 是另一個例子。這些 macro 會產生 initcall,而所有的 initcall 在 link time 也是用類似的方式被收集。在電腦剛開機進 userspace 前它們會依照被收集的順序依序被執行。這邊可以看到一個要注意的小地方。在每個 subsystem (rtc, scsi, ...) 的 Makefile 中,核心的部份必須放在 driver 之前。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-23018888907677857772009-06-09T18:14:00.003+08:002009-06-09T18:33:06.352+08:00Android Wave<a href="http://olvaffe.blogspot.com/2009/06/android-3d-acceleration-on-x86.html">上篇文章</a>提到的程式碼已上線,有興趣的朋友可以到 <a href="http://gitorious.org/android-eeepc/pages/Home">Android Eee PC</a> 專案網頁下載。<br /><br />在做 clean build 的空檔,請 <a href="http://jeremyone.blogspot.com/">Jeremy</a> 幫忙錄了一段影片,以證明加速是真的 XD<br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/ricl2Kg3pMI&hl=zh_TW&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/ricl2Kg3pMI&hl=zh_TW&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br /><br />不過錄到後半,我頭暈得差點連瀏覽器 icon 在哪都找不到,所以請注意不要靠太近看。再來兩天要參加 <a href="http://freedomhectaipei.pbworks.com/">FreedomHEC Taipei 2009</a>,順便休息一下。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com1tag:blogger.com,1999:blog-3519606278200777308.post-14640295711776235212009-06-04T17:49:00.002+08:002009-06-04T18:39:23.471+08:00Android 3D acceleration on x86過去一、二個月的文章都是圍繞著 android x86 與 mesa/dri 在打轉,原因無它,因為這陣子在做的事恰好就是 Android 在 Eee PC 上的 3D 加速。因為 android 跟 mesa 都是第一次接觸,所以確實費了不少工夫在認識它們。<br /><br />Android 在起來的時候,會試著載入 /system/lib 底下的 libagl.so 與 libhgl.so。前者是 OpenGL ES 與 EGL 的純軟體實作,source code 在 frameworks/base/opengl/libagl/ 底下;後者則與硬體相關,目前沒有開放的實作。所以我做的事基本上就是寫一份 libhgl.so。<br /><br />之前在 <a href="http://olvaffe.blogspot.com/2009/04/dri2-overview.html">DRI Overview</a> 有提到,只要有適當的 loader,DRI driver 可以在 X 以外的平台使用。目前我的 libhgl.so 實作是基於 <a href="http://cgit.freedesktop.org/~krh/eagle/">eagle</a>,並只支援 intel graphics chipsets。因為 i915_dri.so 實作了 OpenGL,而不是 OpenGL ES,所以定點數與部份 ES 才有的 extension 沒有辦法支援。這個問題最快的解決方式應該是改用 Gallium 搭配 <a href="http://zrusin.blogspot.com/2009/05/opengl-es.html">OpenGL ES state tracker</a>,這也是接下來要評估的。<br /><br />對 Android 本身,主要則有三個部份需要修改。一是 EGLDisplaySurface 要用 kernel modesetting 的實作取代,同時 kernel 也要上 2.6.29;二是 GPUHardware 要改用 i915 的 GEM object 實作;三是 Surface 要能認得 GEM object。<br /><br />目前整個 Android 系統可以正常運作。SurfaceFlinger 或一些 3D 軟體像是 <a href="http://code.google.com/p/android-gl/">Android-GL</a> 也可以正確地被加速。不過目前的實作比較接近於驗證這個方式的可行性。程式碼整理好後會放在 <a href="http://gitorious.org/android-eeepc">Android Eee PC</a>,到時有興趣的開發者都可以一同參與。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-53817527062665731842009-05-15T15:39:00.006+08:002009-05-15T17:06:48.938+08:00Android Window System在上星期 <a href="http://0xlab.org/">0xlab</a> 的討論會中,我對 Android 的視窗系統做了簡短的分享,投影片可以在<a href="http://people.debian.org.tw/~olv/surfaceflinger/surfaceflinger.pdf">這邊</a>取得。簡報內容主要在介紹 SurfaceManager,同時也附上簡短的<a href="http://people.debian.org.tw/~olv/surfaceflinger/demo.tar.gz">範例程式</a>,示範利用 Android 的 native library 取得可用來繪圖的記憶體。<br /><br />之前的<a href="http://olvaffe.blogspot.com/2009/04/android-binder.html">文章</a>提到 Android 跟 <a href="http://cairographics.org/">cairo</a> 的結合,也是利用一樣的方法,麻煩的地方反而是在於 cairo 的編譯。我當時是照著 cairo (與 pixman) 的 Makefile.am 寫一份 Android.mk,非常的苦。jserv 後來提到了 <a href="http://plausible.org/andy/agcc">agcc</a>,或許可以拿來與既有程式的 autotools 結合。<br /><br />不過苦也有苦的好處,因為苦慣了就不會害怕。不小心還會弄出怪怪的東西<br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/dgrzLViKFT0&hl=zh_TW&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/dgrzLViKFT0&hl=zh_TW&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object><br /><br />花了一陣子的時間在認識 Android,慢慢地也比較能掌握。希望下篇文章開始,本小站可以脫離嘴炮,向上提升。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com3tag:blogger.com,1999:blog-3519606278200777308.post-41422664018895575292009-04-29T11:31:00.008+08:002009-04-29T12:05:40.850+08:00一千零一夜之 eagle<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />之前的<a href="http://olvaffe.blogspot.com/2009/04/dri2-overview.html">文章</a>提到過,只要有適當的 loader,DRI driver 也可以用在 X server 以外的平台,eagle 就是一個例子。eagle 提供開發者接近於 <a href="http://www.khronos.org/egl/">EGL</a> 的 API,並且可以運作在支援 DRI2 的 X server 與支援 GEM 的 intel drm 之上。這篇文章主要想討論後者。<br /><br />eagle 與視窗系統無關的實作,行數落在大約 800 行左右。intel drm 的支援也只在 250 行左右。對它使用到的技術有些許了解後,只要花一個晚上就可以讀完,算是學習 linux 3D 實作不錯的途徑。也因為它的簡單,閱讀過程如果有難以理解的部份,也可以馬上修改跑跑看。如果這兩部份看完後想要再看 DRI2 的部份,eagle 也可以讓人滿足。<br /><br />intel drm 的支援在 eagle 中稱為 backend,以 EagleBackend 表示。Backend 與視窗系統相關,要提供的主要功能有<br /><ul><br /><li>建立 EGLSurface</li><br /><li>提供 DRI driver 需要的 GEM buffer</li><br /><li>更新畫好的內容到螢幕上</li><br /></ul><br /><br />intel drm backend 建立 EGLSurface 的函數是 intelCreateSurfaceForName。這邊的 name 型別為 uint32_t,指的是 GEM buffer 的 identifier。如果沒給的話,backend 會產生一塊新的 buffer。client 可以要求 double buffering,這時 surface 的 backBuffer 會設為 EGL_TRUE。<br /><br />intelGetBuffers 負責提供 DRI driver 需要的 GEM buffer,像是 front buffer,back buffer 或 depth buffer 等。這邊要特別注意,在 double buffering 的情形,也就是 backBuffer 設為 EGL_TRUE 的時候,即便 DRI driver 要求了 front buffer,intelGetBuffers 還是會產生新的 buffer 回傳給 DRI driver,而不是回傳 intelCreateSurfaceForName 時給定或產生的 front buffer。這個新產生的 buffer 也被稱為 fake front buffer。<br /><br />最後看到 intelSwapBuffers,也就是 eglSwapBuffers 會呼叫的函數。可以看到,當不是 double buffering 時,這個函數會直接 return,因為 DRI driver 本來就是畫在真正的 front buffer 上。若是 double buffering 的話,intelSwapBuffers 會把 DRI driver 以為的 (fake) front buffer 複製到真正的 front buffer 上。<br /><br />Backend 大致上就處理這些事情。eagle 的核心則負責提供接近 EGL 的 API。它同時也是 DRI loader,選定適當的 EagleBackend 並載入適當的 DRI driver。除了選擇與 dlopen 適當的 .so 檔外,核心其餘的程式碼大多在處理 EGL 的型別與 DRI 型別的對映。這個部份很基礎,也是所有想寫 loader 的開發者都要熟悉的東西。這邊的程式碼比較直接,有興趣的人可以自行閱讀。<br /><br />在初次研讀 eagle 的過程中,我最感興趣的其實是這篇文章沒提到的 glapi。這是 mesa 或 mesa 自動產生的程式碼,其中後者佔了絕大多數。在許多功用中,其中一項功用是提供 OpenGL (ES) 的 global symbols,讓 link 到 libeagle.so 的程式可以呼叫 gl* 等函數。這可以算是 mesa build system 的一部份,對熟悉 linux 3D 的組成有相當的幫助。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-6006471397212814502009-04-17T13:53:00.009+08:002009-04-17T14:13:23.937+08:00一千零一夜之 Android Binder<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />Binder 是 android 大量使用的 IPC 機制。當使用者在 launcher (home) 按了某個 App 時,這個動作會經由 ActivityManager 向 zygote 發出請求,並從 zygote fork 出新的 process 執行被選擇的 App。這個過程大致上有 4 個 process 參與:home、system server、zygote 和新產生的 appA。更仔細去看的話,<ol><br /><li>system server 向 home 送出 touchscreen 事件</li><br /><li>home 向 system server 送出 start activity 事件</li><br /><li>system server 向 zygote 送出 appA 的啟動參數</li><br /><li>appA 向 system server 送出 attach 事件</li></ol><br /><br />除了第 3 項以外,其它跨 process 的溝通都是採用 binder。Zygote 是 android 上少數幾個聽 unix socket 的程式。驗證這個說法最簡單的方法是去看 process 的 memory maps 還有 opened fd,這個技巧在<a href="http://olvaffe.blogspot.com/2008/12/dri.html">之前</a>有用過,這邊暫且略過。<br /><br />一個 IPC 機制的效能很大一部份取決於它資料交換的能力,從這點去切入是不錯的途徑。所有參與 binder 的 process 都會在一開始向 /dev/binder 裝置 mmap 一塊 read-only 的記憶體,大小在幾個 MB 左右。A process 傳送資料給 B process 的做法是,由 A 對 /dev/binder 做 ioctl 將資料先送給 binder driver。依資料大小,binder driver 動態配置足夠的 physical memory 存放來自 A 的資料,並在 B 一開始 mmap 得到的 address space 裡尋找一段足夠大且未使用的 address area。找到後,binder driver 會修改 B 的 page table,讓這段 address area 映射到剛剛動態配置的 physical memory。如此一來,B 可以直接存取 A 送過來的資料;等到 B 不需要這些資料時,再由 B 負責把這塊記憶體還回去給系統。這段實作可以參改 binder driver 的 binder_update_page_range 函數。<br /><br />Process 對 binder 的操作只透過 ioctl 做 buffer 的讀寫。Buffer 的內容是一系列的 (command, argument) 組。每個 command 為 4 bytes,argument 的長度視 command 而定,可能是固定的,也可能是變動的。當 A 跟 B 交換資料時,通常會由一方送出 BC_TRANSACTION command,再由另一方回覆 BC_REPLY command。Transaction/reply 的 argument 除了一段 header 外,其餘的資料部份會使用上一段討論的方式做交換。資料的長度跟內容由 A、B 另行約定,可能是整數、浮點數或字串等。除了上述的基本資料型別外,還有三種用 struct flat_binder_object 表示的特殊型別:BINDER、HANDLE、FD。FD 型別讓 A、B 可以交換 file descriptor,也就是說,A 可以打開某個檔案,把 fd 傳給 B,讓 B 去讀寫。或者說,也是更重要的,surface manager 可以準備一塊 surface,把 surface 的 fd (一塊 ashmem 記憶體) 傳給一個 app,讓 app 可以在上面作畫。有了這層理解,我們對 android 可以有更自由的發揮,例如,可以不改一行 cairo 的程式碼,就利用 cairo 與 c++ 寫出一個會動的時鐘:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOi0nB9OTlS9sIbKUJBTHKm8HDv4VXw-EMMfkJ4emXKejdpo-EuXbKacTavHBOSyNrbO0s4N535cNopE9DpK2lz52NRjE_KPBdTG1KiOPoMsjO8xItBHkN8yvhXAvXI9mR6zQDhqw4JwU/s1600-h/android-cairo.png"><img style="cursor:pointer; cursor:hand;width: 400px; height: 309px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOi0nB9OTlS9sIbKUJBTHKm8HDv4VXw-EMMfkJ4emXKejdpo-EuXbKacTavHBOSyNrbO0s4N535cNopE9DpK2lz52NRjE_KPBdTG1KiOPoMsjO8xItBHkN8yvhXAvXI9mR6zQDhqw4JwU/s400/android-cairo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5325536719678562658" /></a><br /><br />回到正題,我們知道 binder 還有 BINDER 與 HANDLE 這兩個特殊型別。它們的功用可以用 service port 跟 connection 做類比。就像 httpd 可以開 80 port 提供服務,client 可以跟它建立 connection 使用服務一樣,在 android 上頭,WindowManagerService 可能建立了一個 BINDER,提供了視窗操作的服務。其它程式可能會建立這個服務的 HANDLE。在 binder 之上,android 提供了 aidl 讓開發者可以把 remote service 當做 local object 操作,所以開發者可以直接呼叫 mWindowManager.setRotation 來旋轉螢幕,而幾乎可以不用知道 BINDER 跟 HANDLE 的存在。<br /><br />當一個 process 收到 transaction 的時候,如果 binder driver 發現 process 沒有任何 thread 已經準備好等著要處理,等到下次有 thread 來處理時,它會收到 BR_SPAWN_LOOPER 的訊息,userspace 也會多產生一個 thread 專門對 /dev/binder 做 I/O,確保下次有 transaction 的時候,有比較高的機會是已經有 thread 準備好了。提個數字,在 android sdk 1.5 preview 的 emulator 剛開好機時,在我的機器上,Phone App 會有 11 個 thread,而其中有 6 個 thread 是跑在同一個迴圈裡等著對 /dev/binder 做 I/O。這項機制相信是為了縮短應用程式的反應時間。<br /><br />Binder 再探討下去還有許多東西可以討論,例如 android native library 沒有 aidl 可以用,它如何建立操作 binder 的物件?或者 native code 裡面常常看到的 sp<>、wp<> 跟 binder 的 reference counting 關係為何?對 binder 的功能有初步認識後,再去理解這些實作會顯得直覺許多。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com5tag:blogger.com,1999:blog-3519606278200777308.post-29496632418024765332009-04-09T03:19:00.009+08:002009-04-09T03:36:17.473+08:00Android with cursor一般 PC 上面沒有 touchscreen 只有滑鼠,為了方便做事,晚上試著幫 Android 加上 cursor:<br /><br /><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/YgmsLJswq50&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/YgmsLJswq50&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="425" height="344"></embed></object><br /><br />暫時是做在 WindowManagerService 裡面,髒髒的 patch 可以在<a href="http://people.debian.org.tw/~olv/android/dirty-software-cursor.patch">這邊</a>取得。目前 drag 有點問題,而且是 software cursor,歡迎有興趣的網友幫忙改進 :)olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com1tag:blogger.com,1999:blog-3519606278200777308.post-10883072873118552692009-04-06T01:02:00.005+08:002009-04-06T02:00:31.983+08:00一千零一夜之 DRI2 Overview<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />Linux 的圖形加速一直是個問題,不過這個問題在最近幾版的 kernel 隱約可以看到一些希望。從 2.6.28 的 GEM memory manager 到 2.6.29 的 kernel modesetting,由 Intel 與 RedHat 的開發者所提出的加速架構已經進入 kernel,雖然目前採用這些架構的只有 Intel 自身的驅動程式,但其它繪圖晶片的 open source 驅動程式勢必也得跟隨此架構。在 Intel 與 AMD 相繼釋出 datasheet 後,或許有天所有的使用者都可以享用這些成果,不再依賴私有的驅動程式。<br /><br />去年年中 GEM 公佈後,許多架構迅速翻新,DRI2 最後也環繞著 GEM 在設計。DRI (Direct Rendering Infrastructure) 是各家 open source 驅動程式所依循的架構,雖然處處都可以看見這個名詞,但是要了解它背後的實作卻不太容易。困難的地方在於 DRI 的組成份子很多,而且大部份來頭都不小。以 Intel 的 945GM 為例,在 kernel 有 drm.ko 與 i915.ko 兩個模組,對映到 userspace,有 drm 提供的libdrm.so 與 libdrm_intel.so;在 xserver 有 libglx.so 與 libdri2.so 這兩個 extension,以及 xf86-video-intel 提供的 intel_drv.so;最後在 mesa 也有 libGL.so 以及 i915_dri.so。習慣上,intel_drv.so 被稱作 2D driver 或 DDX driver,i915_dri.so 則是 3D driver 或 DRI driver。從一個 3D 程式被執行時到顯示在螢幕上,參與這個看似普通的過程背後就是上面這些檔案。<br /><br />DRI 最重要的內涵在於,一個 X client 可以向 X server 取得視窗的 buffer(s),並利用硬體加速在上面直接繪圖,不再透過 X server。Kernel 在 DRI 扮演的主要角色是讓多個 process 可以共享 (GEM) buffer,還有接受與執行 process 傳來的硬體指令。這邊困難的地方之一在於 CPU 跟 GPU 都有各自的 MMU 與 cache,所以有許多同步的問題要處理。<br /><br />libGL.so 在 DRI 架構裡扮演了 loader 的角色,它跑在 client 這邊。就像 OpenGL 與平台無關一樣,DRI driver 也與平台無關。loader 一方面載入 DRI driver,一方面又提供適當的 hook 讓 DRI driver 呼叫,並實作了平台相關的部份。所以在 DRI driver 真的要用到 buffer 的時候,它會呼叫 loader 的 getBuffers 函數取得視窗的 buffer 與大小。libglx.so 跟 libdri2.so 實作了 GLX 與 DRI2 這兩個 X extensions。在 X 的平台上,對於 DRI driver 的要求,libGL.so 會利用 GLX 與 DRI2 協定取得所需的資訊,並傳回給 DRI driver。<br /><br />一個快速驗證上面說法的方法是直接檢視系統的 /usr/lib/libGL.so 跟 /usr/lib/dri/i915_dri.so。libGL.so 只是 loader,檔案大小應該要比 i915_dri.so 小不少;而 i915_dri.so 與平台無關,所以 ldd 應該不會看到 X11 的函式庫。如果再進一步想,那是不是只要提供適當的 loader,DRI driver 也能用在 X 以外的平台?答案是肯定的。<br /><br />在 compiz 剛出來的時候,有很多名詞跟著它一起冒出來。像是 XGL、GLX_EXT_texture_from_pixmap、還有 AIGLX 等。在 libGL.so 無法成功跟 X server 溝通使用 DRI 時,client 的 3D 繪圖指令會改經由 GLX 傳給 server 處理,這被稱作 indirect rendering。在早先,libglx.so 會利用純軟體的方式執行這些 3D 指令,所以 indirect rendering 跟 software rendering 是等價的。這個情形在 AIGLX 引入後有很大的改變。在 AIGLX 下,libglx.so 除了提供 GLX extension 實作外,它同時也是一個 DRI loader,可以利用 DRI driver 進行 3D 加速。因為 AIGLX,indirect rendering 不再直接被視為慢、無法接受,雖然它還是多了 client 到 server 的 overhead。AIGLX 即是 Accelerated Indirect GLX 的縮寫,非常直接。<br /><br />因為我直接從 DRI2 開始看,無法比較它跟 DRI1 的差異。在 DRI2 作者 Kristian Høgsberg 的 <a href="http://hoegsberg.blogspot.com/">blog</a> 可以找到一些關鍵字。Kristian Høgsberg 同時也是<a href="http://olvaffe.blogspot.com/2009/03/wayland.html">之前文章</a>提到的 Wayland 的作者。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-51811505373318214572009-03-23T15:09:00.009+08:002009-03-23T18:08:39.716+08:00Android on x86因為之後的需要,這兩天開始看起 android,尤其是在 x86 的情境。不過手邊沒有硬體可以用,打算先用模擬器測試。可能是 cupcake 在我動工的前兩天 <a href="http://www.mail-archive.com/android-framework@googlegroups.com/msg01736.html">merge 到 master</a> 時的部份變更,雖然很快就編好 image,可以進到 console,但是 UI 起不來。又多花了一天時間才順利完工。<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrxebddqwpZvqVeMcX6LFn1KgWsytqGEABV49imYF-xvrDX0t5MnLRIwSueNdC1gTVfI1ZzvcJxwJj6GN-eU2TB1n7FVW0ygmtweudgHhigc1kiLB8o3fMO5ExwURhmexieRDDYk38nPM/s1600-h/Screenshot-QEMU.png"><img style="cursor: pointer; width: 400px; height: 309px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrxebddqwpZvqVeMcX6LFn1KgWsytqGEABV49imYF-xvrDX0t5MnLRIwSueNdC1gTVfI1ZzvcJxwJj6GN-eU2TB1n7FVW0ygmtweudgHhigc1kiLB8o3fMO5ExwURhmexieRDDYk38nPM/s400/Screenshot-QEMU.png" alt="" id="BLOGGER_PHOTO_ID_5316280809528382834" border="0" /></a><br /><br />在 android 的 <a href="http://source.android.com/download">Get source</a> 頁面有編譯 ARM 版本的方式。關於 android on x86,最重要的參考資料則是 <a href="http://groups.google.com/group/android-porting/browse_thread/thread/66862bdb52dac936/1ae192cee32eaa71">這個討論串</a>,利用 google 也可以找到更詳細的步驟。前面提到的 cupcake merge 同時造成了 opencore 的編譯問題,解法一樣要去看最開頭提到的 <a href="http://www.mail-archive.com/android-framework@googlegroups.com/msg01736.html">merge 到 master</a> 討論串。不過我在 Debian testing (AMD64) 上做的時候,還需要先找到 build/core/combo/linux-x86.mk 的這兩行<br /><pre class="programlisting">$(combo_target)GLOBAL_CFLAGS := $($(combo_target)GLOBAL_CFLAGS) -m32<br />$(combo_target)GLOBAL_LDFLAGS := $($(combo_target)GLOBAL_LDFLAGS) -m32</pre><br />把 -m32 參數拿掉,然後再多安裝 {gcc,g++}-multilib 套件。前者是說不要把 host binary 預設編成 32bit,後者則是讓某些在 Android.mk 又加上 -m32 的模組可以編譯成功。<br /><br />一開始我是用 VirtualBox 2.1.4,不過因為 UI 起不來,想要用 adb 來除錯,所以後來改用 QEMU 0.10.0 (x86 版),並利用它的 -redir 功能讓 host 可以 adb 進去模擬出來的機器。給 QEMU 用的 kernel 有幾件事要注意一下:為方便起見,最好把<br /><pre class="programlisting">CONFIG_NE2K_PCI=y<br />CONFIG_FB_VESA=y<br />CONFIG_FRAMEBUFFER_CONSOLE=y</pre><br />直接編進 kernel,並設定 vga=xxx。如果 kernel 跑到一半停住,並且在倒數幾行出現<br /><pre class="programlisting">MP-BIOS bug: 8254 timer not connected to IO-APIC</pre><br />訊息,可以在 cmdline 加上 noapic 參數解決。<br /><br />前面一直提到的 UI 問題可以參考這個 <a href="http://people.debian.org.tw/%7Eolv/android/qemu-x86-dirty-fix-after-cupcake-merged.patch">dirty patch</a> 做修改。這樣應該就完成了。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com4tag:blogger.com,1999:blog-3519606278200777308.post-7749175338690283662009-03-01T22:09:00.008+08:002009-03-23T18:09:07.621+08:00編譯 Wayland<a href="http://groups.google.com/group/wayland-display-server">Wayland</a> 是一個極精簡的 display server。它是由 <a href="http://hoegsberg.blogspot.com/">Kristian Høgsberg</a> 在工作之餘所進行的實驗性計畫。與 X server 不同,Wayland client 要負責所有的繪圖動作,server 只處理最後的合成與顯示。這邊的繪圖動作還包含視窗邊框,這在 X 的世界裡是由 window manager 完成。<br /><br />client 必須把它想顯示的畫面畫在 GEM buffer 上。GEM buffer 是 kernel 在管理的資源,它在不同的時間點可能會存在於不同的地方,例如它可能在系統記憶體或者是顯示卡的記憶體上。client 想要繪圖時可以利用 mmap 取得 GEM buffer 的指標,直接對它進行操作。client 與 server 的資料交換,圖形以外的部份是透過 unix socket;圖形部份因為使用 GEM buffer,server 可以直接取得。server 最後再透過 compositor 把 client 的畫面合成並顯示到螢幕上。<br /><br />Wayland 還在早期的階段,這邊提到的編譯方法可能很快就不適用 (或被簡化)。編譯 Wayland 之所以困難,主要原因在於它用到的 git repository 不好找。先讓我們找到 DRM 並且安裝起來<br /><pre class="programlisting">$ git clone git://anongit.freedesktop.org/git/mesa/drm<br />$ cd drm<br />$ ./autogen.sh --prefix=/opt/gfx<br />$ make<br />$ make install<br />$ export PKG_CONFIG_PATH=/opt/gfx/lib/pkgconfig<br />$ export LD_LIBRARY_PATH=/opt/gfx/lib</pre><br /><br />再來安裝 Mesa 跟 DRI driver (這邊選用 i915)<br /><pre class="programlisting">$ git clone git://anongit.freedesktop.org/git/mesa/mesa<br />$ cd mesa<br />$ git remote add krh git://people.freedesktop.org/~krh/mesa<br />$ git fetch krh<br />$ git checkout -b eagle krh/eagle<br />$ ./autogen.sh --prefix=/opt/gfx --with-dri-drivers=i915 \<br /> --disable-gallium --disable-glw --disable-glut --disable-glu \<br /> --without-demos<br />$ make<br />$ make install</pre><br /><br />Wayland 會用到 udev 136 之後提供的 libudev,雖然這邊只安裝 libudev,但要注意它可能會覆蓋系統上本來的檔案<br /><pre class="programlisting">$ git clone git://git.kernel.org/pub/scm/linux/hotplug/udev.git<br />$ cd udev<br />$ ./autogen.sh<br />$ cd udev/lib<br />$ make<br />$ make install</pre><br /><br />Wayland 目前的 compositor 是採用 eagle 這個 EGL 實作<br /><pre class="programlisting">$ git clone git://anongit.freedesktop.org/~krh/eagle<br />$ cd eagle<br />$ autoreconf -vif<br />$ ./configure --prefix=/opt/gfx<br />$ make<br />$ make install<br />$ export EAGLE_DRIVER_PATH=/opt/gfx/lib/dri</pre><br /><br />大部份的 example client 用到了 cairo-drm,這是一個直接以 GEM buffer 為 surface backend 的 cairo 分支<br /><pre class="programlisting">$ git clone git://anongit.freedesktop.org/git/cairo<br />$ cd cairo<br />$ git remote add ickle git://anongit.freedesktop.org/~ickle/cairo<br />$ git fetch ickle<br />$ git checkout -b drm ickle/drm<br />$ ./autogen.sh --prefix=/opt/gfx --disable-xlib<br />$ make<br />$ make install</pre><br /><br />最後,Wayland<br /><pre class="programlisting">$ git clone git://people.freedesktop.org/~krh/wayland.git<br />$ cd wayland<br />$ autoreconf -vif<br />$ ./configure --prefix=/opt/gfx<br />$ make<br />$ make install</pre><br /><br />Wayland 需要 kernel modesetting 的支援 (linux kernel >= 2.6.29)。再克服這一關,一個晚上折騰的結果<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-9QrE15c2rbTcw6WwMqVIuldSB5GeO_qmwBFDYIqpc2xnlyz5LPkePbRxUkhx6mMAs4bz1jDysYkM1-Tk0fKo-DYxpuBSKugb09toM9qDN2bhk4e3NOkUW6GqzkfF3jQefwYDBcHX4_8/s1600-h/wayland-screenshot-0.png"><img style="cursor:pointer; cursor:hand;width: 400px; height: 250px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-9QrE15c2rbTcw6WwMqVIuldSB5GeO_qmwBFDYIqpc2xnlyz5LPkePbRxUkhx6mMAs4bz1jDysYkM1-Tk0fKo-DYxpuBSKugb09toM9qDN2bhk4e3NOkUW6GqzkfF3jQefwYDBcHX4_8/s400/wayland-screenshot-0.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5308245305326024738" /></a>olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com8tag:blogger.com,1999:blog-3519606278200777308.post-30600055054378108612009-02-27T22:44:00.007+08:002009-02-27T23:03:09.499+08:00objdump 反組譯objdump 反組譯是一個很實用的功能,但有時候我們拿到的不是執行檔,而是 binary image 或者是自行從記憶體 dump 下來的內容。在這種情況,要手動給 objdump 足夠的資訊,它才能完成它的工作。事實上,它需要的只是簡單的:<br /><pre class="programlisting">$ arm-angstrom-linux-gnueabi-objdump -b binary -m arm -D bootrom.bin</pre><br />或者也可以先利用 objcopy 將 binary image 轉成 ELF 格式:<br /><pre class="programlisting">$ arm-angstrom-linux-gnueabi-objcopy -I binary -O elf32-littlearm -B arm --rename-section .data=.text bootrom.bin bootrom.elf</pre><br />這樣也可以照本來的習慣去使用 objdump。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-82206451043993516712009-01-30T13:59:00.009+08:002009-01-30T15:09:16.556+08:00一千零一夜之 Console I/O<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />當一個程式被執行時,它的 stdin/stdout/stderr 會從父程序繼承。如果沒有特別指定,它們通常會指向同一個諸如 /dev/ttyX 或 /dev/pts/X 的 TTY device。很難不去好奇,當我們 getchar() 或 printf() 時,對一個 TTY device 的 I/O,在 kernel 裡頭會發生什麼事?<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4Gel9e-yh05Qw2vkr2_WUM1huBF5Oed1xO8uffK8He4lPHgJmJjawchJ_vTPMr-M4l6Gy4IMR7IvS-z6PAVIo07UCTZs_SyofG3Q4xS_Ax842RPvvP4AwGXles8wwVtMY1qhHptDfQtM/s1600-h/tty.png"><img style="cursor:pointer; cursor:hand;width: 400px; height: 167px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4Gel9e-yh05Qw2vkr2_WUM1huBF5Oed1xO8uffK8He4lPHgJmJjawchJ_vTPMr-M4l6Gy4IMR7IvS-z6PAVIo07UCTZs_SyofG3Q4xS_Ax842RPvvP4AwGXles8wwVtMY1qhHptDfQtM/s400/tty.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5296978097955724914" /></a><br /><br />口語裡的 console,指的通常是圖中 VT。VT 是 virtual terminal 的縮寫,對 userspace 來說,它就像是一部真實存在著的終端機;在 kernel 裡頭,VT(的功能之一)是一個 TTY driver,它是所有 /dev/ttyX 裝置的驅動程式。對 TTY device 的 I/O 動作,會經由 TTY core 送給 TTY driver。資料往來於 TTY 與 TTY driver 的過程中,或者說圖中連結 TTY 與 VT 的線,被稱做 TTY ldisc,TTY line discipline。<br /><br />在<a href="http://olvaffe.blogspot.com/2009/01/keyboard-in-console.html">之前的文章</a>中我們看到鍵盤向 TTY 送出的資料。來自於鍵盤的輸入,可能是特別的按鍵組合,目的並不是讓 userspace 去取得這些輸入。例如,我們會敲下 Ctrl-C 來中斷目前的程式;Ctrl-Z 來暫停目前的程式。又或者,我們不希望輸入的密碼會出現在螢幕上。這些需求都在 line discipline 中被滿足。因為所有的 I/O 都會經由 line discipline,於是它可以在使用者輸入特定字元時,對目前的程式送出 SIGINT 或 SIGTSTP;它也可以選擇是否要把使用者的輸入再 echo 回 VT。從這邊也可以看到,line discipline 不只可以對資料做緩衝與處理,它還被允計針對特定的輸入做特定的行為。當我們從 VT 換到實體的 COM port 時,且當與 COM port 連結的裝置不是終端機,而是數據機時,line discipline 甚至可以拒絕所有來自 userspace 的 I/O,轉而從 PPP network device 與 userspace 做資料交換。實際上,TTY device 只是一個 transport layer,可以跟它連結的裝置千變萬化。line discipline 於是也被設計成可以抽換;當從終端機換成數據機時,line discipline 也從 N_TTY 換成 N_PPP。<br /><br />跟來自鍵盤的輸入一樣,使用者對 TTY device 寫入的資料,會經過 line discipline,VT,再進入螢幕。在 drivers/char/n_tty.c 的 do_output_char 函數中<br /><pre class="programlisting">switch (c) {<br />...<br />case '\r':<br /> if (O_ONOCR(tty) && tty->column == 0)<br /> return 0;<br /> if (O_OCRNL(tty)) {<br /> c = '\n';<br /> if (O_ONLRET(tty))<br /> tty->canon_column = tty->column = 0;<br /> break;<br /> }<br /> tty->canon_column = tty->column = 0;<br /> break;<br />case '\t':<br /> spaces = 8 - (tty->column & 7);<br /> if (O_TABDLY(tty) == XTABS) {<br /> if (space <>column += spaces;<br /> tty->ops->write(tty, " ", spaces);<br /> return spaces;<br /> }<br /> tty->column += spaces;<br /> break;<br />...<br />}<br /></pre><br />可以看到 line discipline 會對特殊字元,像是換行符號或 tab 等做排版。更多關於 N_TTY 這個 line discipline 的功能可以參考 stty(1)。<br /><br />VT 的架構與實體的 terminal 與 host computer 架構是對應的。要把現在手上正在上網的電腦想像成它是 terminal 與 host computer 的組成可能不是那麼容易。一個相對常見、具體的情境,是用一條線把手上的電腦與 wifi router 或手機的 UART console 連結。上圖中的 Computer B 可以理解成手上的電腦,Computer A 則是跑著 linux 的嵌入式系統。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com2tag:blogger.com,1999:blog-3519606278200777308.post-75311340013037143902009-01-27T11:21:00.004+08:002009-01-27T11:37:40.780+08:00一千零一夜之 keyboard in console<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />按下或放開按鍵的時候,鍵盤會產生一段長度不等的 scancode,這段 scancode 會被 keyboard driver (像是 drivers/input/keyboard/atkbd.c) 處理,轉換成類型為 EV_MSC 與 EV_KEY 的 input event。EV_MSC 事件的內容是 scancode 本身,與硬體直接相關且不容易處理,除非有特殊需求,通常我們比較關心的是 EV_KEY input event。想要處理這些事件的其它子系統,可以向 input layer 註冊 input handler。例如 evdev 會註冊一個 input handler 讓 userspace 可以從 /dev/input/eventX 直接取得鍵盤事件。kernel 的 console driver 也會向 input layer 註冊一個 handler 來取得鍵盤事件,這是我們這次想要比較深入了解的項目。<br /><br />console 跟鍵盤的交互作用可以在 drivers/input/char/keyboard.c 看到。在 console 底下,鍵盤有四種模式,<br /><pre class="programlisting"><br />#define VC_XLATE 0 /* translate keycodes using keymap */<br />#define VC_MEDIUMRAW 1 /* medium raw (keycode) mode */<br />#define VC_RAW 2 /* raw (scancode) mode */<br />#define VC_UNICODE 3 /* Unicode mode */<br /></pre><br />可以利用 kbd_mode 命令來切換。前面有提到 keyboard driver 會送出 EV_MSC 與 EV_KEY 兩種 input event,其中前者送出的 scancode 被用來實作 VC_RAW 模式;後者送出的 keycode,則可以用來實作 VC_MEDIUMRAW 模式。剩下的兩種模式都是 keycode 的再加工。<br /><br />scancode 到 keycode 的轉換透過了 keyboard driver 定義的 keycode table 完成。在 atkbd.c 裡,如果忽略掉其它細節,它只是簡單的<br /><pre class="programlisting"><br />keycode = atkbd->keycode[code];<br /></pre><br />一行程式碼。有些多媒體鍵盤會有一些按鍵的 scancode 不在預設的 keycode table 裡,當這些按鍵被按下或放開的時候,kernel 會印出類似<br /><pre class="programlisting"><br />atkbd.c: Unknown key pressed (translated set 2, code 0x1e on isa0060/serio0).<br />atkbd.c: Use 'setkeycodes 1e <keycode>' to make it known.<br /></pre><br />的訊息。依照這段訊息提示的方法去做<br /><pre class="programlisting"><br /># setkeycodes <scancode> <keycode><br /></pre><br />就可以新增一筆對映的資料。setkeycodes 與 getkeycodes 命令即是用來設定或取得目前的 keycode table。值得注意的是,大部份的 keycode 有對應的 symbolic name,例如 keycode 30 的 symbolic name 是 KEY_A,keycode 48 的是 KEY_B,keycode 46 的是 KEY_C 等 (見 /usr/include/linux/input.h)。這些 symbolic name 是照美式鍵盤定義而來,拿到其它語言的鍵盤上並沒有意義。<br /><br />console 下 keyboard 預設的模式是 VC_XLATE 或 VC_UNICODE。從 input layer 拿到 keycode 後,console 會呼叫 kbd_event 函數做處理。在這兩個模式下,keycode 會被轉換成 keysym<br /><pre class="programlisting"><br />key_map = key_maps[shift_final];<br />...<br />keysym = key_map[keycode];<br /></pre><br />然後 queue 到 console buffer。stdin 接到 console 的程式即是由這邊取得使用者的輸入。這個動作會經過 line discipline,不過我們這邊不去討論。<br /><br />一個 keysym 是兩個 byte,分別可以由 KTYP(keysym) 跟 KVAL(keysym) 取得它的 type 與 value。當 type 小於 0xf0 時,整個 keysym 代表一個 UCS2 的 codepoint,keysym 會被轉成 UTF-8 放進 console buffer;當 type >= 0xf0 時,與 type 相關的 handler 被呼叫,其 prototype 為<br /><pre class="programlisting"><br />typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value,<br /> char up_flag);<br /></pre><br />其中 value 就是剛剛看到的 KVAL(keysym)。有些 handler 會把處理過的 value 寫入 console buffer,有些則不會,這樣設計的目的以例子來看最容易理解。我們知道 shift 按著的時候,所有的字母大小寫會互換。當 shift 的按下時,鍵盤控制器送出中斷,keyboard driver 收到後把 scancode 讀出並轉成 keycode,送出 input event。console 收到 event 後利用 keymap 把 keycode 轉成 keysym。shift 的 keysym type 是 0xf7,value 是 0x00,送到對應的 handler 中<br /><pre class="programlisting"><br />if (up_flag) {<br /> if (shift_down[value])<br /> shift_down[value]--;<br />} else<br /> shift_down[value]++;<br /><br />if (shift_down[value])<br /> shift_state |= (1 << value);<br />else<br /> shift_state &= ~(1 << value);<br /></pre><br />可以發現 shift_state 的第一個 bit 被設為 1。shift_state 是 keyboard.c 裡的 file-scope 變數,當任何一個按鍵再被按下時,kbd_event 裡這一段程式碼<br /><pre class="programlisting"><br />shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;<br />key_map = key_maps[shift_final];<br /></pre><br />選取了不同的 keymap,導致或許不一樣的 keysym 被送出,本來是小寫的英文字母也因而變成大寫。<br /><br />在 kernel 內部實作,console 使用的是 UCS2 編碼。不管是使用者列印到 console 的字串,或者是 keysym 的 value 都會先重新編碼成 UCS2 再做進一步處理。在 VC_UNICODE 模式底下,UCS2 會以 UTF-8 形式被送進 console buffer;在 VC_XLATE 模式,則會嘗試再編碼回原來的 value 送到 console buffer。<br /><br />相對於 keymap 複雜的機制,它的操作卻非常簡單。在 userspace 可以利用 loadkeys 與 dumpkeys 命令來操作 keymaps<br /><pre><br /># loadkeys de # 使用德式鍵盤 layout<br /># loadkeys us # 使用美式鍵盤 layout<br /></pre><br />進一步的資訊可以參考 keymaps(5)。<br /><br />這篇文章我們看到了鍵盤在 console 的運作。如果換到 X(org) server,情形有點不同,而且也因驅動程式而異。例如 xf86-input-keyboard 雖然是從 console 取得鍵盤事件,但它會把 console 設為 VC_RAW 模式,自行處理 scancode。而 xf86-input-evdev 則是直接從 /dev/input/eventX 取得鍵盤事件。以後有機會也可以看看 X(org) 下的鍵盤運作。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0tag:blogger.com,1999:blog-3519606278200777308.post-84757413879397455952008-12-26T00:44:00.022+08:002008-12-26T16:23:25.367+08:00一千零一夜之 BitBake Data class<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br />曾經使用過 <a href="http://www.openembedded.org/">OpenEmbedded</a> 的開發者應該可以想像,當 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。<br /><br />以 glib-2.0-native_2.16.1.bb 為例,<br /><pre class="programlisting"><br />require glib-2.0_${PV}.bb<br /><br />FILESPATH = "${FILE_DIRNAME}/glib-2.0-${PV}:${FILE_DIRNAME}/files"<br />DEPENDS = "gtk-doc-native"<br />PR = "r2"<br /><br />inherit native<br /><br />do_configure_prepend() {<br /> if [ -e ${S}/${TARGET_SYS}-libtool ] ; then<br /> echo "${TARGET_SYS}-libtool already present"<br /> else<br /> cp ${STAGING_BINDIR}/${TARGET_SYS}-libtool ${S}<br /> fi<br />}<br /><br />do_stage () {<br /> ...<br />}<br /><br />...<br /></pre><br />可能看出 bb 檔在做的事,主要是設定變數以及定義函數 (python or shell script)。Data class 就是用來紀綠 bitbake 從各設定檔跟 bb 檔讀進來各個變數的值。特別值得注意的是,對 Data class 來說,函數跟變數並沒有太大的不同。或者說,函數只是比較特別一點的變數。在上面的例子裡,do_stage() 其實只是定義了一個叫做 do_stage 的變數,而它的值剛好是一段 shell script。bb 檔裡還會出現一些 keyword (require, inherit 等),parser 在看到它們的時候,不一定是利用 Data class 來紀錄這些資訊。<br /><br />在了解 bitbake 怎麼處理變數前我們必須先知道 Data class 提供了哪些機制。我們知道 Data class 是用來紀錄 key-value pairs 用的。又,上面提到有些變數是特殊的,所以每個 key 勢必還要有 flag 來描述其性質 (是不是可以執行,可執行的話是 python or shell script 等)。為了達到這個需求,實作上 Data class 是一個 dict of dict。也就是說,<br /><pre class="programlisting"><br />DEPENDS = "gtk-doc-native"<br />S = "${WORKDIR}/git"<br />FILES_${PN}-locale = "${datadir}/locale"<br /></pre><br />會以<br /><pre class="programlisting"><br />d.dict['DEPENDS']['content'] = 'gtk-doc-native'<br />d.dict['S']['content'] = '${WORKDIR}/git'<br />d.dict['FILES_${PN}-locale']['content'] = '${datadir}/locale'<br /></pre><br />的形式存到 d (an instance of class Data) 裡面。而<br /><pre class="programlisting"><br />do_stage () { some_code }<br /></pre><br />則會變成<br /><pre class="programlisting"><br />d.dict['do_stage']['content'] = 'some_code'<br />d.dict['do_stage']['func'] = '1'<br /></pre><br />利用 dict of dict,Data class 輕易地為任何的 key 提供任意多的性質。<br /><br />我們知道了 Data class 讓我們可以為任意的 key 標上任意的性質,但 bitbake 的需求不僅僅只是如此。更重要的是,bitbake 想提供的是變數的處理。從上面的例子可以看到,S = "${WORKDIR}/git" 進到 Data class 裡是 d['S']['content'] = '${WORKDIR}/git' 的形式,WORKDIR 這個變數並沒有被展開。Data class 在變數展開方面採用的是 lazy 的策略。也就是說變數的展開,會在讀值的時候發生。所以要等到稍後某個 task 取 S 的值時<br /><pre class="programlisting">cd ${S}</pre><br />WORKDIR 才會被即時展開。<br /><br />變數除了可以出現在 value 外也可以出現在 key 的部份 (如 FILES_${PN}-locale)。不同的是,key 的變數展開發生在 d.expandKeys(),而且是不可復原的。它的目的跟我們最後想看的 prepend/append/override 有關。<br /><br />在 bb 檔裡頭不難看到<br /><pre class="programlisting"><br />do_configure_prepend() { some_code }<br />do_install_append() { some_other_code }<br />TARGET_FPU_arm = "soft"<br /></pre><br />等語法。這讓開發者可以很輕易地在 task 開始或結束的地方加上一段 script,或者讓開發者可以有條件地設定某個變數。例如,我們可能希望當目標架構是 arm 時把 TARGET_FPU 設成 soft。上面這些語法在 Data class (不完全正確) 的作法是<br /><pre class="programlisting"><br />d.dict['do_configure']['_prepend'].append('some_code')<br />d.dict['do_install']['_append'].append('some_other_code')<br />d.dict['TARGET_FPU_arm']['content'] = 'soft'<br /></pre><br /><br />而 prepend/append/override 發生的時機,不像變數展開是在取值時即時發生,也不像變數展開並不會去修改 d 的值,prepend/append/override 只有在 d.update_data() 時才會發生,而且修改也不可復原。例如當目標架構是 arm 時,TARGET_FPU 會被 override 成 TARGET_FPU_arm 的值<br /><pre class="programlisting"><br />d.dict['TARGET_FPU'] = d.dict['TARGET_FPU_arm']<br /></pre><br />Override 機制比這邊討論的略為複雜一些。有興趣的人建議可以直接參考實作。<br /><br />BitBake parser 規範了 bb 檔的語法。但部份 parser 允許的操作並不被 Data class 直接支援。最後,不妨猜猜看 ?=, :=, += 等操作跟 Data class 如何相互作用?olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com1tag:blogger.com,1999:blog-3519606278200777308.post-51714977207565712262008-12-14T17:09:00.011+08:002008-12-26T00:48:12.327+08:00一千零一夜之 DRI<blockquote>這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。</blockquote><br /><br />DRI 讓 X clients 可以直接利用硬體來做繪圖,這點可以利用下面指令看出來:<br /><pre class="programlisting"><br />olvaffe@m500:~$ glxgears &<br />[1] 24684<br />olvaffe@m500:~$ cat /proc/24684/maps <br />08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears<br />0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears<br />...<br />b38fa000-b58fa000 rw-s e6000000 00:0d 7315 /dev/dri/card0<br />b58fa000-b62fa000 rw-s e5000000 00:0d 7315 /dev/dri/card0<br />b62fa000-b6cfa000 rw-s e4000000 00:0d 7315 /dev/dri/card0<br />b6cfa000-b733a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0<br />b733a000-b797a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0<br />...<br />b79a0000-b7bbd000 r-xp 00000000 08:03 347365 /usr/lib/dri/i915_dri.so<br />b7bbd000-b7bd1000 rw-p 0021c000 08:03 347365 /usr/lib/dri/i915_dri.so<br />b7c00000-b7c07000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1<br />b7c07000-b7c08000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1<br />b7eb5000-b7f11000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2<br />b7f11000-b7f17000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2<br />...<br /></pre><br />我們可以看到,glxgears 把 i915_dri.so (intel 的 driver) 載進來並且開啟了 /dev/dri/card0。而在不使用 DRI 的時候:<br /><pre class="programlisting"><br />olvaffe@m500:~$ LIBGL_ALWAYS_INDIRECT=1 glxgears &<br />[1] 24834<br />olvaffe@m500:~$ cat /proc/24834/maps <br />08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears<br />0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears<br />...<br />b7bd3000-b7bda000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1<br />b7bda000-b7bdb000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1<br />...<br />b7e88000-b7ee4000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2<br />b7ee4000-b7eea000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2<br />...<br /></pre><br />則少了上述的檔案。這是因為繪圖方面都轉送給 X server 去處理。如果用 CPU usage 去看,也可以看出在這兩個情形下,比較忙的可能是 server 或著是 client。而就像 client 可以利用 DRI 做加速一樣,X server 在收到 client 的請求時,也可以利用 DRI 去完成這些請求,這個技術被稱為 AIGLX (Accelerated Indirect GLX)。<br /><br />從上面的例子我們可以看出 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 來用。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com1tag:blogger.com,1999:blog-3519606278200777308.post-33121397920565468652008-01-02T15:05:00.000+08:002008-01-03T03:02:16.383+08:00gobject 的 marshaller在剛開始使用 GObject 很容易遇到的一個問題是,marshaller 是什麼?因為在幫自己的物件設計 signal 的時候,有一項資訊就是 marshaller:<br /><pre class="programlisting">guint<br />g_signal_new (const gchar *signal_name,<br /> GType itype,<br /> GSignalFlags signal_flags,<br /> guint class_offset,<br /> GSignalAccumulator accumulator,<br /> gpointer accu_data,<br /> GSignalCMarshaller c_marshaller,<br /> GType return_type,<br /> guint n_params,<br /> ...);</pre><br />雖然說搜尋 glib-genmarshal 可以找到很多生成 c_marshaller 的方法,但我們更感興趣的是為什麼會需要 marshaller 的存在。<br /><br />這邊的 marshaller 更精準的說法是 closure marshaller。在 gobject 中,我們利用 g_signal_connect 連接 signal handler 時,所提供的 C 函數會先被包裝成一個 closure,然後這個 closure 才真正地連接上去。上面提到的 c_marshaller 則被用來作為這個 closure 的 closure marshaller。<br /><br />Marshaller 自己本身也是 C 函數。在 signal 發生時,gobject 並不會直接呼叫 signal handler,而是呼叫該 handler 對應的 closure marshaller。換句話說,呼叫的動作變成一種間接的行為。<br /><br />這層間接提供的好處是,我們的 signal handler 不一定要用 C 來寫。只要提供適當的 closure marshaller,signal handler 可以用任何語言來寫。當然這時就得用比 g_signal_connect 提供更大彈性的<br /><pre class="programlisting">gulong<br />g_signal_connect_closure (gpointer instance,<br /> const gchar *detailed_signal,<br /> GClosure *closure,<br /> gboolean after);</pre><br />來連接 signal handler。<br /><br />gobject 的各種 bindings 就可以充份利用 gobject 的這項機制。像是 Perl binding 即提供了 gperl_closure_marshal 來當 marshaller,溝通在 Perl 與 C 之間。<br /><br />Closure 是 gobject 用來一般化 callback function 的方法。伴隨著 callback function,資料也會在不同的語言中交換。對於資料,gobject 是用 GValue 來處理交換的問題。雖然用了不同的名詞,但G_VALUE_LCOPY 跟 G_VALUE_COLLECT 實際上就是 GValue 給 C 通用的 marshaller 跟 demarshaller。<br /><br />要更清楚了解 marshaller/demarshaller 的意義,除了各 bindings 外,也可以參考 dbus-glib 的實作。在裡頭,可以看到 dbus message arguments 怎麼被 demarshal 成 GValue's 再被 marshal 成 C 型別;或者是相反過來的程序。dbus-glib 為了效能考量,有做了一些壞事。這些壞事反而是了解 gobject 很好的參考。olvhttp://www.blogger.com/profile/14109154724494271204noreply@blogger.com0