2012年12月24日 星期一

[cloud] MiCloud 的信任危機


公司首次使用 MiCloud 的第一個月帳單出來了,但其中覺得不愉快的部分是因網路流量超過免費額度而被收錢。使用者付費是件理所當然的事!心中還是很在意沒有替公司省到這筆錢覺得不甘心。

回憶到 2012/12/17 突然接到同事轉接來的 MiCloud 人員的電話,主要意圖是關切我們的網路流量,對方是說我們的流量異常,向我們確認是否有為電腦中毒。當下開 MiCloud 的未出帳單,看起來二台機器的流量還在額度之內。第二天不放心又看了一次,它就到了超量的情況。


第三天又看了,似乎又回到原樣。經過了 20 號的結算日。該算的費用都出來了,流量果真又回來了。即使在這事件的幾十天前,有詢問過客服為何帳單估的誤差很大。客服是回應網頁上紅字標明的『**此未出帳單金額僅供參考,實際以結帳後之金額為主。』這句話雖然沒什麼可以挑剔之處,但它的誤差實在大到令人費解之處,特別是在網路流量計算的部份。

於是開始上網找了一些能統計流量的程式,最後我選用 vnstat。主要是它分天顯示流量的功能相當方便:

..
..


算起來每日會使用 20 至 30 G 的流量,若以均值 25 G 來算,與超量的機器的天數吻合。
花了幾天看了統計結果後,我明白了另一件事。原來我對 MiCloud Console 網頁上提供的數據失去了信任感。


上圖的機器是開始以 vnstat 統計流量時就建立的(2012/12/19)。到目前為止(2012/12/24 00:00),它顯示給客戶的流量依然是 0。

..
..

vnstat 計算已達 130 G,簡單來說剩 70 G 不到的免費額度。因為這些數字是跟算錢有關的當然要計較些。放那麼不準的數據給客戶看,那不如別放了吧。單純只是增加不信任感的作用罷了。



2012年8月22日 星期三

[jetty] 使用 deploy xml


簡單地說,一般我們要 deploy 新的 war 需要放在 jetty config 檔設的目錄中,預設是 $JETTY/webapps 目錄,若想 undeploy 只需將 webapps 下的 web module 移掉即可。而 deploy xml 則是另一種 deploy 的方法,你只要寫一個 xml 檔,指定你的 web module 路徑,並將 xml 檔放在 $JETTY/contexts 內即可:

..
..

只要將這個檔放在 $JETTY/contexts 目錄下,jetty 會偵測到它,並依你指定的 context path 與 war 的位置部署程式。另外,我在 webdefault.xml 有加 gzip filter 的設定,剛開始用 deploy xml 部署時沒有指定 webdefault.xml 就沒有吃到 gzip filter 參數。若您有額外改變 webdefault.xml 設定,就要記得在 deploy xml 指定。

2012年8月19日 星期日

[log] 久違的講課:Java Web & JDBC


因為答應了要替朋友去上課,內容是我幾乎不太寫的 Java Web。一方面是工作的主軸早就不是寫 Java Web 的內容,另一方面寫 Java Web 有它痛苦的地方。趁機寫寫課後心得作為之後上課的參考 :)

..
..

內容主要為 JDBC Programmaing 與 Web Programming 二個部分。JDBC 簡單地示範基本的 API 使用後,下一個範例就直接切入使用 Connection Pool、Commons DBUtils,在 Web 方面也是相似的安排,但多加了個不倚懶 Servlet API 完全由土炮簡單的 Web Server,並設計 Handler Class 開始。

這是我自己覺得『理想』中的 Java Web Programming 的學習路線,當然這個單純寫書教導 Java Web Programming 是不同的目標。書本需要帶給人最完整的知識,即使不值得使用的技術也是需要列上去,通常也包含大部分 API 的說明,同時也有許多為人詬病的功能。

就以 JSP 來說,它一開始的目標是為了解決 Servlet 沒有好用的 template engine 解決方案,但卻 release 了許多超出一個 template engine 本身應負擔的功能。這能想像成是一種變形的『破窗效應』,自從有人在專案內寫 scriptlet 做一些稍為複雜的邏輯後,scriptlet 就成為一種惡名昭彰的存在,而 JSP 也提供自訂 taglib 的功能,而在規格內的 JSTL 就有許多不適合在 JSP 內使用的東西,像 Database Query。在些不見得需要的功能就必需在寫書或教學時展示它,在這次的內容安排,我僅使用了 EL 與 JSTL Core 或一些變數的判斷與顯示,像是檢查使用者有沒有登入的 if 與顯示 TODOs 的 forEach 這類簡單的邏輯,若未來課程有提到 i18n 頂多再加個 fmt 相關的 taglib,還有些性質相近或易混淆的功能就不提,像是 include 部分就只教 JSP Directive include。JSP Action 或 JSTL 的 include 就完全不在內容之中。。以單純做 View Presentation 來說,並不需要太多的功能。

在 JSP 上要寫 Business Logic 其實是非常不得已的,這樣寫不僅讓你的 Business Logic 散落於各 JSP 檔,或是做一些 Java Bean 來處理 Business Logic,但這始終不是個解法。因為整個商業流程還是控制在 JSP 內,採用 Model 2 的型式實作才能真的將邏輯集中,並且較能讓設計清晰、明確。

針對 Model 2 的部分,我選用的例子是改寫原 Model 1 的例子而來。它不單純是將 request 對象改成 Controller (Servlet),而是仿 Web Framework 做 Action Mapping 的方法,透過 Servlet 進行 matching & dispatching。我們有個『微型』的 Web Framework,並直接在程式內 hardcode mapping,儼然是個小巧可用的 Web Framework。若曾寫過 WebWork 的人,可能會找到一點熟悉的感覺。

實際上執行起來發覺時間有限。也許是我把 4 小時想得太長,而對於學生能跟得上實作的時間估得太短。這樣的教學效果不盡理想。過程中有些小狀況,像是安裝課程要使用的 mysql,因為學生之前專案有先安裝過,所以同時裝另一份 unzip 版就會出現許多奇怪的現象。另外,自己沒有 win 7 的環境,也缺乏在同樣的環境測試是否 lab 能做得起來的機會。這些意外倒是比較可惜的地方。這部分的改進方案,是採用 embedded db,像是 h2。減少練習地安裝 mysql 不成功的變數。


2012年8月15日 星期三

[ec2] attach elastic ip

最近想要讓特定 autoscaling 的 instance 自動設定 elastic ip。從 boto 查了一下相關的 API,寫起來蠻容易的:

..
..

有了 script 後,在開機後執行它就行了 :D

這樣就能利用 autoscaling 固定建 1 個特定的 instance 並獲得 ip,用在可以『掛』點一會兒的 server 配合 spot instance type 真是窮人的 server 服務方案啊!

2012年8月2日 星期四

[centos] 最小安裝後無網路可用

一時興起下載了 CentOS-6.3-x86_64-minimal.iso 在 VBox 安裝起來,開機後竟然沒有網路可用。應是是預設沒有開啟,於是在網路上找到解法,要改網路設定檔,把開機啟動的選項設為 yes:

..
..

改好設定 reboot 後,完成 VBox 的 NAT port forward 就可以開始練習操作嚕 :D

[ec2] 開啟 ssh login by password 筆記


使用 ec2 ami 建出的 instance 預設是使用 *.pem 登入的,若是計劃將讓一般的使用者也能登入,就必需修改 /etc/sshd_config:


PasswordAuthentication no

改成

PasswordAuthentication yes

如果需要允許 root 登入可加開(最好是不要,或是你有限制登入 ip 的前題下):

PermitRootLogin yes

改完後別忘了指定新密碼給 root,並重新載入 sshd_config:

/etc/init.d/sshd reload
需注意的是在 /etc 下有二個檔 ssh_config 與 sshd_config,別改錯檔了。
千萬要記得限制可登入的 IP(/etc/hosts.allow 或使用 ec2 security group)


2012年6月20日 星期三

[ec2] 取得 EC2 Tags

工作的關係,開始大量使用 ec2 服務,其中一部分是撰寫『爬蟲』類。這些爬蟲大多數的內容是一樣的,但可能依爬的對向會使用不同的 rule 執行。並且有監控爬蟲的程式通知現在『生態系』不平衡了,需要多放一點特定類型的爬蟲。



在概念上,大家的程式幾乎都一樣的,那我就不用為每一種類型建一個 AMI 作為爬蟲的樣版。若將 ec2 instance 想成物件,大概是這種感覺:

Crawler c = new Crawler('type')c.run()

我們只需在『建構子』指定不同的參數,當 ec2 instance 建立起來後,讓它『找尋生命的意義』(找到自己的參數)然後開始工作。有幾個方式能做到 cloud-init,這是蠻流行的方式,我曾試著使用它加速 instance 的初始安裝。在 ec2 instance 上能塞資料的地方很多,而我們的參數也不佔什麼空間,今天靈機一動,決定將它放到 tag 內,若將它表現成 java code 是這種感覺:

Crawler c = new Crawler(EC2Instance.current().tag('type'))
c.run()


所以,我們需要做的事有:


  • 撰寫啟動 script 並讓它接受參數指定我們要啟動的程式
  • 實作取得特定 tag 的方法


本來以為 ec2 instance metadata 就會有 tag 資訊,查了一些資料發現在幾年前有人提議要將 tag 資訊加入,但目前仍沒看到。好在 aws api 蠻容易使用的,我們可以配合 ec2-metadata query tool 先取得 instance id,再查出此 instance id 擁有的 tag:

..

..


所以,實際上我們實作時,決定用 tag 內 handleClass 的值當作 Crawler 啟動參數:

Crawler c = new Crawler(EC2Instance.current().tag('handleClass'))
c.run()

將它設置好,建成 AMI。而建立 ec2 instance 時,記得指定需要的 handleClass 就能依需要動態建出不同功用的 instance。

2012年5月26日 星期六

[osx] 擠出可用記憶體

有時在  osx 上開開關關程式後會發現記憶體都變成『未啟用』的狀態。而『可用的』記憶體維持在相當少的數量:



依網路上的說法這是 osx 要最佳程式啟動的機制。當你曾開過一個程式,關閉後,他會被放入『未啟用的』這個區域。若你再打開能直接以記憶體內的『快取』啟動程式。可參考官方網頁的說明:

未啟用的:
這些資訊目前已經在 RAM 中,不過目前並未使用,只是最近曾經使用過。
舉例來說,如果長時間使用 Mail 後結束此程式,系統會將 Mail 用過的 RAM 標示為“未啟用的”記憶體。其他應用程式可以使用“未啟用的”記憶體,這一點和“可用的”記憶體相同。  不過如果其他應用程式尚未使用 Mail 的“未啟用的”記憶體,您就已經先開啟 Mail,這時 Mail 開啟的速度就會比較快,因為這部分“未啟用的”記憶體已經轉換成“已啟用的”記憶體,而不是從比較慢的磁碟機載入。

知道大概是怎麼一回事後,心理想:『那有工具來幫助我把那個快取的資料清掉嗎?』目前似乎沒有看到直接的工具,不過依據網路上的資料,普遍的看法都是當記憶體不夠用時會釋放『未啟用的』記憶體。這時就想起了平時被嫌棄記憶體用量大的 Java 程式,於是我寫了一個吃掉記體的程式:

..

這想法很單純,只是要把『未啟用』區的東西給擠出來:


因為每一隻程式約佔用 1 GB 的記憶體,我開 4 隻恰好為『未啟用』的大小。然後把他們關掉。我們又再度擁有 4 GB 的可用記憶體:





2012年4月12日 星期四

MySQL 權限管理小技巧

由於過去的 MySQL 使用並沒太多的帳號規劃,只有個簡單的 user account,例如:myuser,配上 % any host 的認證組合。這個組合開的是最大的權限,雖然有在 iptables 下設定允許存取的 ip,但仍想針對特定的 ip 進行權限管理。在密碼是多數的人馬都知道的情況下,找到一個算是可用的方法。

因為 mysql 對於連線進來的 user account 會先替無萬用字元的組合配對(萬用字元的組合數較少者優先配對),我們可以針對某一個 ip 設同一個 user account 同一組密碼。並 grant 那一組特定的 ip 使用 USAGE 權限。如同手冊說的 USAGE 就是沒有權限。這樣即使拿到 user account 也無法做什麼,我們就可以強迫由特定 ip 上來的人用我們指定的 user account。而不是只能口頭相約,希望他使用我們替他額外準備好的帳號。

2012年3月24日 星期六

貼心的 libgmp

常在 $HOME 裡安裝一些自己需要的 library,剛好看到 libgmp 有個貼心的提醒:



..

..

2012年3月10日 星期六

[ffmpeg] 0.9 版後的新式 interrupt

這次試著更新專案內的 ffmpeg 至最新的 tag 版本 n0.9。發現專案內的『deprecated』比以往更多,但還都是能編譯的狀態。依慣例得確認一下在最新的 source code 裡,那些 deprecated 是真的被移除。於是編了 master branch 上的 code,發現 io interrupt callback 的寫法已經不再相容舊版了。

對於 ffmpeg 文件常跟不上 source code 的狀態,這連 changelog 也沒有記載這件事,我們可以簡單 diff n0.9 與前一版找出改變的部分,能利用一定會用到 io interrupt callback 的 ffmpeg.c 來作 diff:

..

舊式的 io interrupt callback 是一個 global function pointer:
..

新式的 io interrupt callback 使用一個 struct 包起來,設定在各別的 AVFormatContext:
..

要修改的部分雖然不太多,但 callback function 的參數有改,這是得注意的部分 :D

2012年3月7日 星期三

[osx] 發現 ARCHFLAGS 的妙用

試著用 easy_install 安裝 PIL 結果發生問題:

qrtt1$ sudo easy_install -U pil
Searching for pil
Reading http://pypi.python.org/simple/pil/
Reading http://www.pythonware.com/products/pil
Reading http://effbot.org/zone/pil-changes-115.htm
Reading http://effbot.org/downloads/#Imaging
Best match: PIL 1.1.7
Downloading http://effbot.org/media/downloads/PIL-1.1.7.tar.gz
Processing PIL-1.1.7.tar.gz
Running PIL-1.1.7/setup.py -q bdist_egg --dist-dir /var/folders/aQ/aQLNlFLOF28xewK2A7i0X++++TM/-Tmp-/easy_install-eVQU9j/PIL-1.1.7/egg-dist-tmp-L149wy
--- using frameworks at /System/Library/Frameworks
_imaging.c:3017: warning: initialization from incompatible pointer type
_imaging.c:3077: warning: initialization from incompatible pointer type
/usr/libexec/gcc/powerpc-apple-darwin10/4.2.1/as: assembler (/usr/bin/../libexec/as/ppc/as or /usr/bin/../local/libexec/as/ppc/as) for architecture ppc not installed
Installed assemblers are:
/usr/bin/../libexec/as/x86_64/as for architecture x86_64
/usr/bin/../libexec/as/i386/as for architecture i386
/usr/bin/../libexec/as/arm/as for architecture arm
_imaging.c:3017: warning: initialization from incompatible pointer type
_imaging.c:3077: warning: initialization from incompatible pointer type
_imaging.c:3281: fatal error: error writing to -: Broken pipe
compilation terminated.
_imaging.c:3017: warning: initialization from incompatible pointer type
_imaging.c:3077: warning: initialization from incompatible pointer type
lipo: can't open input file: /var/folders/aQ/aQLNlFLOF28xewK2A7i0X++++TM/-Tmp-//ccCj3S35.out (No such file or directory)
error: Setup script exited with error: command 'gcc-4.2' failed with exit status 1

看了一下 log 訊息,發現怎麼多了編譯 arm 版本的動作呢?在網上也找到有人分享了它的解法,重點在於如何讓 easy_install 只編譯我們真正需要用到的 x86_64 與 i386 版本。原來重點在於 ARCHFLAGS 的使用:

sudo ARCHFLAGS="-arch i386 -arch x86_64" easy_install -U pil

感謝 Installing python PIL in MAC OS, having xcode4 提供的解決之道 :D

2012年3月5日 星期一

[ffmpeg] 由 0.8.7 昇級至 0.9 的 linking error on osx

由於舊的 swscale 在轉換 yuv 至 rgb 時,在某些 format 會產生一條綠色的矩形在底部。這個情況已經透過由 AVFrame 內的 data 抓出來,用 swscale 之外的工具驗證過了。但單純地將 yuv 轉成 png 是沒有那個綠色的矩形的。在回報 bug 或是要試著自行修復前,當然得做一點『標準動作』:

  • 確認下一個 stable 版是否也有同樣的問題
  • 確認 repo 上最新的狀態是否也有同樣的問題
目前採用的是 ffmpeg 0.8.7 加上我們自己的 patch,由 git fetch 後,我們能看到還有 0.8.8, 0.8.9 再來就進入了 0.9 版。我選擇了 0.9 版作為我們預計要使用的 stable 版本,利用 cherry-pick 簡單地 patch 需要的檔案後,編譯一切正常。但進入 xcode 編譯 Application 時發生了問題:


Undefined symbols for architecture x86_64:
"_kVDADecoderConfiguration_Height", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_kVDADecoderConfiguration_Width", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_kVDADecoderConfiguration_SourceFormat", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_kVDADecoderConfiguration_avcCData", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_kCVPixelBufferPixelFormatTypeKey", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_kCVPixelBufferIOSurfacePropertiesKey", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_VDADecoderCreate", referenced from:
_ff_vda_create_decoder in libavcodec.a(vda.o)
"_VDADecoderDecode", referenced from:
_ff_vda_decoder_decode in libavcodec.a(vda.o)
"_CVPixelBufferRelease", referenced from:
_ff_vda_release_vda_frame in libavcodec.a(vda.o)
_ff_vda_destroy_decoder in libavcodec.a(vda.o)
"_VDADecoderDestroy", referenced from:
_ff_vda_destroy_decoder in libavcodec.a(vda.o)
"_CVPixelBufferGetPixelFormatType", referenced from:
_vda_decoder_callback in libavcodec.a(vda.o)
"_CVPixelBufferRetain", referenced from:
_vda_decoder_callback in libavcodec.a(vda.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

有某些 symbol 找不到。查了一下 changelog,原來 0.9 版還多了 vda 的加速技能。既然在 ffmpeg 編譯時沒有問題,那應該就是 xcode 的設定需要改變,透過這些 symbol 名稱,找出了我們缺少了二個 framework:
加上這二個 framework 後,我們就能編譯正常並且使用具有 VDA 加速效果的 ffmpeg。

2012年3月4日 星期日

[autotools] 加入額外的 m4 路徑

要試著在 osx 上編譯 irssi,由於我並不是用 macports 安裝 library 的,有些 .m4 檔並不是裝預設的目錄。以 homebrew 來說,它會放在 homebrew 安裝目錄的 share/aclocal。為了要讓 irssi 提供的 autogen.sh 吃到我指定的 .m4 路徑,需要更動 ACLOCAL 變數:

ACLOCAL="aclocal -I. -I$HOME/app/homebrew/share/aclocal" ./autogen.sh

除此之外,再解決一些相依性的問題,我就能編譯自己的 irssi 了 :D

2012年3月2日 星期五

[autotools] configure 指定額外的 library

想試著編 ffmpeg 並且引用 libmp3lame 的功能。但我並沒有將此 library 裝在系統內,所以得另外指定 library 的位置:

CFLAGS="-I/opt/libmp3lame/include" LDFLAGS="-L/opt/libmp3lame/lib" ./configure --enable-libmp3lame --prefix=/opt/ffmpeg_full

主要就是透過 CFLAGS 指定 include path,透過 LDFLAGS 通知 linker 該去哪個目錄找 library

2012年3月1日 星期四

[Android] 偵測 USB 拔除

最近 Player 開始測試播放 USB 上的檔案,播放當然是正常的,只是當使用者未正常地結束播放就拔掉 USB 這件事就不知道怎麼處理了。
好在同事分享了一個方法,能直接透過 Broadcast Receiver 來獲得通知,重點在於要怎麼寫 IntentFilter:

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_REMOVED);
intentFilter.addAction(Intent.ACTION_MEDIA_NOFS);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE);
intentFilter.addAction(Intent.ACTION_MEDIA_SHARED);
intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");


對我們的情況來說,最重要的是 Intent.ACTION_MEDIA_EJECT,因為不管怎麼樣,只要 USB 被拔掉了,而 Player 又在播放 local file 的情況我們就需要停下它。

2012年2月24日 星期五

[objc] 被沒收的 UTF8String

最近協助同事開發 webkit plugin,我提供的 Player 能使用 playWithUrl 啟動播放:

-(void)playWithUrl: (const char*) aUrl;

但在 plugin 由外層 javascript 傳入的參數是 NSString* 型式的,直覺上我們會利用 UTF8String 方法,獲得 c String:

[theUrl UTF8String]

但試了幾次後,發現轉進來的 c String 偶爾會變成奇怪的內容,俗稱『亂碼』。這種偶發性的不確定因素,有股熟悉感。先翻閱手冊,在 UTF8String 中提到:


The returned C string is automatically freed just as a returned object would be released; you should copy the C string if it needs to store it outside of the autorelease context in which the C string is created.

簡單地說,這個 c String 是生命週期會跟著原來的 NSString 跑,所以,要確保它被使用的時候,是正確的內容,我們應該先 retain 它,最後,我們討論了一下,決定提供一個新的 method:

-(void)playWithNSStringUrl: (NSString*) theUrl
{
[theUrl retain];
memset(inputUrl, 0, sizeof(char) * 1024);
strncpy(inputUrl, [theUrl UTF8String], 1024);
[theUrl release];
[self playWithUrl: inputUrl];
}

確實宣示我們持有 theUrl,並保存其內容。亂碼就不再來亂了 :D

2012年2月17日 星期五

XCode 編譯 i386 架構時的 "illegal text reloc to XXX" 問題

最近在協助同事編寫 Webkit Plugin。經驗上如同 iOS 實作時相仿:為了同時支援不同的架構(arm6, arm7)得將用到的 library 分別編譯出來,將利用 lipo 將它們 merge 成一個同時支援多種架構的 library,即為俗稱的 universal library。
在一開始我忘了需要不同架構的 library,因為某些 osx 需要 32bit 的 library,在我電腦上,預設編出來的是 x86_64 架構,本來以為單純編譯出 i386 的版本再合併起來就好。沒想到發生了新的問題:

ld: illegal text reloc to cstring from ../FFMPEG.i386/lib/libavformat.a(aea.o) in _aea_read_header for architecture i386
collect2: ld returned 1 exit status
Command /Developer/usr/bin/llvm-g++-4.2 failed with exit code 1


雖然不太清楚細節是什麼,總之這錯誤看起來是 linker 在進行 relocation 時發生了問題。為何這個問題只在 i386 編譯時會發生呢?剛好找到在 apple maillist 上有說明:

http://lists.apple.com/archives/unix-porting/2008/Jan/msg00027.html

*** EXPLANATION ***
The two assembly commands load the absolutes address of _trail into R15. Doing so is fine if _trail is ultimately in the same linkage unit. _trail is in libmodule.dylib.
For this to work, at runtime the dynamic loader (dyld) would have to rewrite the two instructions. Normally dyld only updates data pointers.
One work around is to make libdyalog an archive (e.g. libdyalog.a) and link that with pere.s. Then all the code would be in the same linkage unit, so there would be no need for runtime text relocs.
The runtime (dyld) does support text relocs (updating instructions) for i386, but you need to link with -read_only_relocs suppress.

相信大部分的人跟我一樣對於底層能掌握的知識不多,至少我們知道:

The runtime (dyld) does support text relocs (updating instructions) for i386, but you need to link with -read_only_relocs suppress.

索性在 other link flags 加上:

-read_only_relocs suppress

程式似乎就能正常編譯成功了。

PS. 這個 flag 不能加在 x86_64 的架構,請設定在條件式編譯選項。

2012年2月7日 星期二

MySQL 資料檔移轉 (on osx)

由於某台測試 Server 的一些設定檔毁損讓 LVM 無法正常掛載,經 MIS 緊急修復後我也參與了復原工作。從掛回去的檔案系統上,MIS 備份出了 MySQL data 路徑的資料庫檔案。由於 MySQL Server 是 5.0 版本的,要直接將檔案丟到新版的 MySQL Server 是不可行的。最好的情況是能轉變成 mysqldump 匯出的 .sql 檔。在 MySQL 官網看了半天,找不到合用的 5.0 binary 檔,要弄個合適的 linux 環境可能又要花不少時間,乾脆就抓 source code 自己來編了。這樣就可以不需要 linux 環境,能編出來放在 osx 上執行。

source code 的編譯上沒有遇到什麼困難,一如往常地 ./configure --prefix=/opt/mysql && make install 就能搞定。而接下來遇到的問題才是值得我筆記下來的部分!

將備份出來的 data 放至 MySQL 指定到的 data 路徑(自編的情況是放在 $MYSQL/var),啟動 MySQL 看起來一切正常。show tables 也都有看到資料表出現,但有些是採用 innodb engine,因為忘了放 ibdata1 檔而只有看到名字,沒有實際的內容。在關閉 MySQL Server 放回檔案就能正確讀到那些使用 innodb engine 的資料表。

我開始了第一次 mysqldump 的嘗試,但它跟我說找不到資料表:

mysqldump: Got error: 1146: Table .agentlog doesn't exist when using LOCK TABLES

這個資料表在 show tables 有出現,但名稱為 AgentLog。我也查了檔案是有 AgentLog.frm 的。於是我推測問題應該是出在 osx 的檔案系統,osx 安裝時選用的檔案系統預設為不分大小寫的。要在 osx 上建立出區分大小寫的檔案系統很容易,這步驟在 Android Soruce 編譯的指引中有包含:請參考『Setting up a Mac OS X build environment』。

於是我重新將資料放在新建立的、有區分大小寫的檔案系統中,再度執行 mysqldump 就不會找不到檔案。但我遇到新的問題:mysqldump 要匯出某些表格時說它無法支援 big5_chinese_ci。於是我查了 ./configure --help

--with-charset=CHARSET
Default character set, use one of:
binary
armscii8 ascii big5 cp1250 cp1251 cp1256 cp1257
cp850 cp852 cp866 cp932 dec8 eucjpms euckr gb2312 gbk geostd8
greek hebrew hp8 keybcs2 koi8r koi8u
latin1 latin2lab latin5 latin7 macce macroman
sjis swe7 tis620 ucs2 ujis utf8

在預設的情況下沒有包含 big5_chinese_ci!不過我們有另一個參數可以使用:

--with-extra-charsets=CHARSET,CHARSET,...
Use charsets in addition to default (none, complex,
all, or a list selected from the above sets)

於是我指定了 --with-extra-charsets=all,最後終於將資料匯成 .sql 的型式

2012年2月2日 星期四

以 OpenGL ES 1.x 在 iOS 上貼圖(texture) 的限制

最近為了要製作 Media Player 開始研究怎麼在 iOS 上畫圖,其中一種畫圖的方式是使用 OpenGL ES。OpenGL ES 對於我這初學者來說難度不小,而在先前『試作』Media Player 的經驗,我們能運用 Texture 來貼圖。參考了 iPhone 遊戲自作入門,我已經知道如何畫出一個方形的圖片。

More about iPhone遊戲自作入門


值得注意的是在書上的 93 頁提到:

讀入材質中的圖像尺寸的長與寬必須分別都是 2 的倍數。若沒有遵守這個條件,圖像可能無法正常描繪,必須多加注意。

同樣的限制在著名的 OpenGL ES From the Ground Up, Part 6: Textures and Texture Mapping 也有提到:

Images used for textures must be sized so that their width and height are powers of 2, so both the width and height should be 2, 4, 8, 16, 32, 64, 128, 256, 512, or 1024. An image could be, for example, 64x128, or 512x512.


所以,我們能知道書上的翻譯不太正確,是 2 的次方,而非 2 的倍數。在這限制大約在半年前就有遇過,那時只是先對於在 iOS 上貼圖的方法進行調查與撰寫雛型,隨著時間過去與忙碌於其他專案而將程式碼弄丟了(也忘了筆記下來)。在重新開始這個工作前,我試著在 StackOverflow 提問:How do I render frames from FFmpeg to the screen in an iOS app? 在網友的回覆中,得到一些重要的 keyword!其中最有用的資訊就是知道 NPOT。並且由另一篇提問得到畫出非 2 次方邊長是可行的 Rendering to non-power-of-two texture on iPhone

因此,對於畫出非 2 次方邊長的 texture 的條件在於:

一、你的裝置必需支援 GL_APPLE_texture_2D_limited_npot Extension,可以利用官方文件提到的 CheckForExtension 測試

二、必需滿足 APPLE_texture_2D_limited_npot 的使用條件

In the absence of OES_texture_npot, which lifts these restrictions, neither mipmapping nor wrap modes other than CLAMP_TO_EDGE are supported in conjunction with NPOT 2D textures. A NPOT 2D texture with a wrap mode that is not CLAMP_TO_EDGE or a minfilter that is not NEAREST or LINEAR is considered incomplete. If such a texture is bound to a texture unit, it is as if texture mapping were disabled for that texture unit
這句話在實作上的意義就是:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

完整的範例如下:
https://s3.amazonaws.com/qrtt1.articles/xcode.sample/texture.npot.tgz

2012年2月1日 星期三

讀取 iOS 專案內的資源檔

去年也開始接觸了 iOS 的開發,有些小技巧還是不熟悉啊!像簡單地讀取專案內的資源檔就查了半天。決定開始記錄這些小事情。為了練習使用 opengles 畫出單一的圖形,我放了一個 RGB565 的資料檔(rgb320x240.raw) 在專案內,打算採用 fopen 將它開啟讀入記憶體上操作。但不知道『路徑』該填些什麼。一開始只填檔名,想碰一下運氣,但 FILE* 始終是 NULL。後來在 stackoverflow 找到了解答:

NSString *path = [[NSBundle mainBundle] pathForResource:@"rgb320x240" ofType:@"raw"];

這麼一來我就取得了 rgb320x240.raw 在 iPhone 上的實際路徑:

/var/mobile/Applications/DAC73AB8-79F8-4B6F-9AC0-099C564C8ADA/GLES.app/rgb320x240.raw