2012年2月25日 星期六

Gtk+ 入門

之前看過 jservGtk+ 程式設計初體驗, 玩了一下範例程式, 對 Gtk+ 的 OO 表達方式和處理事件的架構有概念。再來看《GTK 學習筆記》的前幾篇, 試一下範例, 知道怎麼重頭編寫。後來看到其中一篇提到 gtk-demo, 接下來就簡單許多。看一下範例, 挑自己要的出來改, 配合官方文件查幾個 API 用法, 就搞定要做的小東西了, 整個過程還算順利。

備忘:

  • 安裝: $ sudo apt-get install libgtk2.0-dev gtk2.0-examples
  • 編譯: $ gcc prog.c -o prog `pkg-config --cflags --libs gtk+-2.0`
  • 查範例程式: $ gtk-demo ( 點左側選單兩下會執行程式, 右側有程式碼)。若有需要, 也可用 apt-get source gtk-demo 取得原始碼, 更方便。
  • Scott 提到《Parasite: Firebug for GTK+》, 方便調整元件位置, 還沒用過, 先記著。

2012年2月21日 星期二

設定 viewport 的寬度為 device-width 以支援各種 mobile browser

好歹也是花了一些時間看的東西, 備忘一下。

《The orientation media query》

  • orientation (landscape or portrait) 不是重點, 重點是螢幕寬度到底是幾 pixel
  • 結論: 用 device-width

《Mobile web design viewport size vs screen resolution - viewport META tag》

  • 詳述 viewport 為何, 覺得重述一次意思會不對, 還是請大家看原文吧
  • mobile device 的 viewport 大小不見得和 screen 大小一樣 (桌機則是一致)
  • 有些 mobile browser 像 mobile Safari 藉由讓 html 畫在較寬的 viewport 上, 再將它縮放到符合螢幕寬度, 藉此顯示整個網頁的大概樣子 (有時稱為 overview mode)。也就是說, 網頁會依 viewport 的寬度來 render, 而不是 screen 寬度。對桌機來說兩者寬度一樣, 所以不會混淆
  • 各家 mobile browser 預設的 viewport 大小不同, 造成寫網頁的人的困擾
  • 可用 <meta name="viewport"content="width=1100"/> 改變預設 viewport 寬度
  • 可用 <meta name="viewport"content="width=device-width"/> 將 viewport 設為 device 寬度
  • 舊手機不支援上述語法, 該連結有提到其它備案

《device-width and how not to hate your users》

  • 可用 CSS 3 新語法 media-query 針對螢幕寬度決定使用的 CSS rules。對於桌機不同的螢幕寬度來說, 這是個好解法, 不用擔心使用者用 24" 寬螢幕還是 19" 一般螢幕。
  • mobile device 另有 viewport 大小不同 screen 大小的特色, 所以使用 media-query 的話, 要再配合限制 viewport 寬度為 device-width, 才可確保用對 CSS rules

2012年2月20日 星期一

在 apache2 內顯示 symbolic link

要滿足以下三者才可以顯示 symbolic link

  • <Directory /path/to/dir/> 內要有 SymLinksIfOwnerMatch 外 (module userdir 預設就有設)
  • soft link 的擁有者要和連到的檔案是同一人, 這樣才安全, 也可避免 /、/home 之類的目錄被使用者亂連出去
  • soft link 目錄的整條路徑都要能讓 www daemon 存取, 若有個目錄是 750 之類的就不行

有錯時可看 /var/log/apache2/error.log, 若出現「Symbolic link not allowed or link target not accessible」, 大概上述其中一者沒設對。

2012年2月17日 星期五

git staging area 的價值

之前用 mercurial 時一直很納悶, 為啥 git 的愛好者都如此推崇 staging area, 但我怎麼看就是看不懂, mercurial 沒這東西用起來也沒特別困擾, 反而要和別人解釋 staging area 時還會一直說不清楚 (畢竟自己也沒搞懂它的價值 ...)。

看到《The Thing About Git》總算解開我多年來 (遠目...) 的疑惑, 要配合「只想 commit 檔案內部份修改」的情境才會突顯它的價值。

該文有範例說明, 有時我們會同時改到不同東西, 好死不死, 兩個東西的修改在同一個檔案裡。這時有幾個選擇:

  • 就給它 commit 下去, 在 commit log 裡順帶一提多 commit 的東西
  • 兩個功能一起 commit 進去, commit log 寫長一點, commit 內容比較雜
  • 回去原本的檔案「取消」不想 commit 的修改, 再回來 commit

身為一名良好市民, 大部份時候我是選第三個方案, 但是做得很辛苦。mercurial 好像有 extension 可以只 commit 部份內容 (hunk-by-hunk commit), 不過我懶得找。我後來的作法是:

  • thg 的 commit
  • 在 commit 視窗裡針對目標檔案按 "open shelve tool"
  • hunk-by-hunk 地 shelve 不想 commit 的內容
  • commit
  • unshelve all

還過得去就是了。

但是有 staging area 的話, 有不同的選擇。先 hunk-by-hunk 地將準備 commit 的內容加到 staging area (git add --patch), 接下來比對 repository 和 staging area 確認要 commit 的內容, 同時還可回頭比對 staging area 和 working directory 確定沒有漏東西。和 shelve 的作法相比, 比較直覺一些。

2012-02-18 Update

看到提到《A Git User's Guide to Mercurial Queues》 (ref.), 裡面有說明用 MQ 做到「多重 staring area」的效果, 看起來挺實用的。更新一個涉及多個模組的功能時, 個人偏向依各模組拆開 commit, 比較易讀。配合 MQ 可以更直覺地折解更新成多個 patch, 並保有隨時更新的彈性, 最後再一起 commit 成多個 changesets。

2012年2月16日 星期四

配合 c++filt 讀程式

在用 gdb 追蹤程式前, 得先找到幾個關鍵中斷點, 才能著手進行。若有機會想到不錯的關鍵字的話, 除了用 grep 之類的工具大海撈針外, 有時從 binary 裡下手, 效果也不錯, 有機會減少搜尋範圍。畢竟程式碼中難免有一些平台或參數相關的設定, 讓部份程式碼根本沒有編進 binary。從 binary 回頭找, 可免除這層顧慮。

我目前試過的作法有兩種

  • string PROG | grep KEYWORD
  • nm PROG | grep KEYWORD | awk '{print $NF}' | xargs c++filt

第一個作法是配合程式輸出的訊息來找程式。

第二個作法則是從 binary 取出可能有關的的 symbol, 再用 c++filt demangle symbol, 找出它的 namespace、signature 等資訊。需要注意的是, 有可能因編譯器最佳化 (如 inline), 實際上沒有呼叫到函式。經 qrtt1 提醒, 保險起見, 可在編譯時加上 -O0 確保行為符合預期。

題外話, C 的 function name 反而無法 demangle 找出 signature(應該沒理解錯吧)。不過相對於 C++ 的複雜度, 讀 C 的程式時, 也許沒那麼需要吧。

2012年2月14日 星期二

Linux process priorities and scheduling 心得

摘要一下讀了 TLPI 後的心得。

scheduling policy

POSIX 規定了幾種 scheduling policy, 它們的優先權如下:

SCHED_FIFO = SCHED_RR > SCHED_OTHER ~ SCHED_BATCH > SCHED_IDLE

舉例來說: SCHED_FIFO (99) > SCHED_FIFO (1) > SCHED_OTHER -20 (0) > SCHED_OTHER 19 (0) > SCHED_IDLE (0)

括號裡的數字是 process 的 static priority; -20 和 19 是 nice value, 見後文說明。

各 policy 的效果為:

  • 所有 policy 都是 preemptive, 也就是高優先權 process 想執行的時候, 會搶走執行中低優先權 process 的 CPU
  • SCHED_FIFOSCHED_RR 的 static priority 範圍必須落在 1 ~ 99, 剩下三個 (SCHED_OTHER、...) 只能設 priority = 0。達到的效果是 SCHED_FIFOSCHED_RR 永遠會比後幾種 policy 先執行
  • SCHED_FIFOSCHED_RR 是 real-time scheduling, 不過是 linux kernel 盡可能做到即時, 真的要做 real-time system (如汽車), 得用改過的 linux kernel
  • SCHED_OTHERSCHED_BATCH 的 static priority 都為 0, 所以會另外參考 dynamic priority 來決定順序, 這個值主要取決於 nice value (用 nice/renice 設), 只是參考值, 較低的 nice value 不會永遠表示較高的 dynamic priority
  • 沒做設定的話, 預設 policy 是 SCHED_OTHER
  • SCHED_BATCH 用在不需互動的程式, 會減少 wake-up 的頻率
  • nice value 對 SCHED_IDLE 無效, 這個 policy 會保證有最低優先權
  • 以上所有效果都會繼承到 sub-process

所以, 若用 SHCED_OTHER 配上 nice value, 可達到優先效果, 也不會有 process 餓死, 都搶不到 CPU。若用了 SCHED_FIFOSCHED_RR, 要小心搶光 CPU 資源的情況。用 SCHED_OTHER 配上負值 nice value 也要小心。

保險機制

做為保險, 可用 setrlimit(RLIMIT_CPU) 限制執行時間, 超過 soft limit 會收到 SIGXCPU, 預設會掛掉該 process。若沒掛的話, 之後每秒鐘會收到一次 SIGXCPU, 直到超過 hard limit 收到 SIGKILL, 保證掛掉該 process。在這之間就有操作空間來調整自己的優先權。

或用 setrlimit(RLIMIT_RTTIME), 來限制在 real-time scheduling policy 下最長執行的時間, 遇到 blocking system call 後會歸零, 可避免失控超時。超時後的行為和 setrlimit(RLIMIT_CPU) 相同。 setrlimit 也和設 scheduling 一樣, 會繼承到 sub-process。

Affinity

Linux 另有特別的 system call 可限制 process 只能跑在那些 CPU, 在 man sched_setaffinity 的 CONFORMING TO 該節有註明這是 linux-specific 的功能。

透過 set affinity, 可以滿足一些特殊需求:

  • 一台 8 core 的 server, 跑 8 個 process 限制它們各自用同一個 core 來服務大量 client, 讓 context switch 的次數降到最低
  • 限制某些類型的 process 只能用部份 CPU, 確保隨時有餘力服務其它 process。比方說留一個 CPU 不跑 real-time process, 至少失控時還能登入使用 shell 處理
  • 若 multi-thread 的程式沒寫好容易掛, 限制它們只跑在一個 core, 也許比較不會當 (這是我看 stackoverflow 裡某位路人提到他的用法 ...)

sched_setaffinity() 可以設在 process 也可設在 thread 上, 用的時候注意一下 man page 針對 pid 的說明。這個值也會繼承到 sub-process

另外, 經 wens 提醒, 還有 pthread_setaffinity_np() 可用來設 thread 的 affinity。查了一下 man page, 它是基於 sched_setaffinity() 的實作。待比較熟 multi-thread、pthread 的事情後, 大概會比較清楚為什麼要多包一個 pthread_setaffinity_np() 吧。

參考資料

  • TLPI ch35
  • man sched_setscheduler
  • man sched_setaffinity
  • man pthread_setaffinity_np

2012年2月11日 星期六

使用 hg-svn

參照《WorkingWithSubversion - Mercurial》, 前置動作是要裝 python-subversion。用了一陣子覺得滿順的。不過得等 hg rebase --svn 出現 conflict, 看看有沒有無縫銜接 kdiff3 + resolve conflict, 才可以更放心使用。

2012年2月7日 星期二

盡量避免使用 BLOB 和 TEXT

剛好看到朋友在討論, 順便記一下舊心得。

初學 mysql 時容易犯的一個錯誤就是亂用 data type, 明明沒有需要很大的空間, 只是方便就選最大的那個。一但選用 BLOB 或 TEXT 後, mysql 許多執行 SQL 的策略會不同:

別小看 query cache, 它是 mysql 有高效能表現的原因之一。想像完全不懂 database 也沒建 index 的開發者, 若網站大部份需求是讀資料, 只要開夠大的 query cache, 事先用程式掃一掃網站 warm up 一下, 之後 99% 使用者連到網站時, 網頁所用的 SQL 都會從 query cache 裡拿, 連 SQL parsing 都不用, 等同於將 mysql 當作 in-memory key/value store (key = sql), 亂用都還有不錯的效能。

在 gvim 內顯示中文

若你像我一樣習慣看英文名稱的選單, 想說也可以順便練英文, 因此裝了 win7 64bit 英文版, 才會有這需求。

除了本來就該做的:

set encoding=utf-8
set fileencoding=utf-8

之外, 還要設 guifont:

if has("gui_running")
    set guifont=MingLiU:h14
endif

要怎麼知道 guifont 的值呢? 只要按 Edit -> Select Font ..., 選好字型後再打 :set guifont, 就會看到設好的字型, 將它寫入 $HOME/_vimrc 下即可。

特別的是, 在 win32 下和 win64 的設法有點不同, 不能直接照著選完字型的值打, 得去掉最後的 :cCHINESEBIG5。在 vim 內輸入 help guifont 會看到完整說明, 不過好像沒看到 win64 的說明。

2012年2月4日 星期六

善用 Google Form 和 Google Apps Script 製作問卷

之前收過幾個朋友用 Google Form 寫的「婚禮報名表」, 知道可以用 Google Docs 做問卷收集資訊, 到是沒有自己實際操作一次。

查了一下才知道, 以前這功能寫在 Google Spreadsheet 裡, 有個 Form 的功能, 但現在已獨立成新的檔案類型 Google Form

試用後覺得功能相當陽春, 而且最後沒有個「確認頁」讓使用者觀看之前回答的答案。又再研究了一下, 發現原來可以寫程式產生確認信, 真是太 geek 了 (這是稱讚), 不給你陽春的基本功能, 只給你全套工具箱, 就自己想辦法吧。

摘要作法如下:

  • 在 Google Form 的原始檔 (spreadsheet 檔) 點「Tools」->「Script Editor」開啟編輯器
  • 寫個 javascript 函式作些事
  • 在編輯頁點「Triggers」->「Current script's triggers ...」, 然後新增 trigger, 選剛寫的函式並在第三個下拉式選單選「On form submit」。按「Save」後會跳出確認視窗, 點「Authorize」

至於 javascript 函式裡能做什麼事呢, 得翻翻 Google Apps Script 才知道。一直覺得 Google 提供了一堆 API 可以做很多事, 不過沒什麼動力仔細研究, 希望之後有多些需求, 再來投入時間研究。在官方 events 的文件裡, 搜 "on form submit" 會看到簡單的範例。這篇有提供完整的範例寄出使用者填的內容到使用者信箱, 兩者講得是同件事, 挑自己順眼的讀。

不過世界也不是如此美好, 目前 Google Form 有幾個很明顯的問題, 搜一下會在 Google 討論區看到許多抱怨, 結論是目前無解:

  • 除了 Form 的開頭和最後的訊息, 中間的問題欄都不能用 html 語法, 我想要 link 啊。
  • Form 的開頭會自動將 URL 轉成網址, 但不能用 html 語法, 只能貼很醜的長網址。
  • Form 的欄位順序很死, 在程式讀取的順序和 spreadsheet 裡填的順序一樣, 都是照「問題產生的順序」定的, 不是它在 Form 裡的順序, 也就是說, 最後產生出來的問題, 即使移到第一題, 在 spreadsheet 裡仍放在最後一欄。

2012年2月1日 星期三

產生 core dump 的方法

這篇是 "Learning core dump from the hard way", 寫下幾個注意事項, 後來才發現 man core 就可以看到全部東西:

測試

若執行的程式有讀 terminal input, 可直接輸入 ctrl + \ 送出 SIGQUIT。或用指令 kill -QUIT PID 或 kill -ABRT PID, 要求程式產生 core dump, 藉此測試目前的設定是否 ok。

參考《從 /proc/PID/status 了解執行中程式處理 signal 的方式》, 先確認 signal handler 沒有被覆蓋掉, 才可放心測試。

ulimit / setrlimit(RLIMIT_CORE, ...)

Ubuntu 預設為 ulimit -c 0, 表示不產生 core dump, 所以要先執行 ulimit -c unlimited 允許產生 core dump。可以將這行寫到 ~/.bashrc 裡, 以後就預設會產生 core dump。或在程式裡呼叫 setrlimit(RLIMIT_CORE, &limit) 也可以。

/proc/sys/kernel/core_pattern

預設 core dump 的檔名可能不合使用, 參考《setting the core dump name schema》, 可用

$ echo "core.%e.%p.%t" | sudo tee /proc/sys/kernel/core_pattern

改變 core dump 的檔名, 這樣檔名會記錄是程式名稱、PID、發生的時間。在 multi-process 或 multi-thread 時特別有用。若希望每次開機都會生效, 則要在 /etc/sysctl.conf 加入 kernel.core_pattern = core.%e.%p.%t。

core pattern 可以是 "|PROGRAM", 這樣會將 core dump 導到 PROGRAM 的標準輸入, 可以自己寫 PROGRAM 做控制。像是 core dump 太頻繁時, 取樣留下幾個就好, 以免一下就塞爆硬碟。同樣的, %e 那些參數也可以接在 PROGRAM 後當參數用, 像是 "|PROGRAM core.%e.%p.%t", 自己的 PROGRAM 就能從 argv[1] 裡取得適合的檔名。需要特別注意的是, | 和 PROGRAM 之間不可以有空白。

setuid / setgid

若確認 ulimit -c 有設好, core_pattern 沒寫到奇怪的地方 (像是 /dev/null), 硬碟也仍有空間, 卻仍無法產生 core dump, 可能是用到 setuid。用 ls -l 檢查一下, 或用 strace -esetuid 檢查。Linux 為了安全考量, 在使用 setuid/setgid 後, 會自動禁用 core dump。

若確實有觸發 setuid 的話, 在 setuid 後執行:

prctl(PR_SET_DUMPABLE, 1);

重新允許產生 core dump, 應該就 ok 了。

2013-03-26 更新

裝 Ubuntu 12.04 後, 發覺 /etc/sysctl.conf 的設定沒有生效, 查了一下才發覺 apport 覆寫了 /proc/sys/kernel/core_pattern, 參考這裡關掉它, /etc/sysctl.conf 的值應該可如預期作用:

$ vi /etc/default/apport  # 更新成 "enabled=0"

從 /proc/PID/status 了解執行中程式處理 signal 的方式

man proc 可得知在 /proc/PID/status 裡有給人讀的 process 資訊, 其中 SigXXX 是關於 signal 的處理方式, 比方 SigIgn 表示忽略該 signal; SigCgt 表示有註冊自己的 signal handler。

比較需要花點心思的地方, 在於怎麼讀 signal mask。這裡有提到解碼方式, 每個數字用四個 bit 表示四個號碼, 從最右側開始遞增。

以這個程式為例:

#include <stdio.h>
#include <signal.h>

void handler(int signum) {
}

int main(void) {
    signal(SIGQUIT, SIG_IGN);
    signal(SIGHUP, handler);
    signal(SIGPIPE, handler);
    while (1) ;
    return 0;
}

它的 /proc/PID/status 如下:

SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000004
SigCgt: 0000000000001001

表示 3 號 signal (SIGQUIT) 設為 ignore (1, 2, 4 -> 4 表示第三個號碼), 1 號和 13 號 (SIGHUP 和 SIGPIPE) 有註冊 handler。signal 的號碼可從 man 7 signal 得知, 或在 include signal.h 後, 用 gcc -E -dD 展開看也可以。

2012-02-02 更新

Scott 在留言提供正式的解釋, 附註在此:

這是用 16 進位顯示一個 64 bit 的 bitmask,每個 bit 對應到一個 signal。 bits 從 0 開始編號而 Linux signals 從 1 開始編號,故 bit 0 代表 1 號 signal (SIGHUP), bit 1 代表 2 號 signal (SIGINT)。

原本寫的時候想不起來正式的說法, 想說就隨手寫個「白話版」吧。原本的「白話版」也留著, 有不同描述方式, 應該多少會有幫助吧。

查詢 gdb 的新功能

最近聽 Scott 講到一些 gdb 的好功能, 發現為啥我這裡都沒有, 才發覺原來用的 gdb 版本不同。

要看 gdb 各版的 release note, 得下載 gdb 該版的 tarball, 解開後在 gdb/NEWS 裡有寫詳細的新功能。列一下最近聊到幾個我想用但在 7.2 卻沒有的新功能:

7.3 版: 加速 executable 載入時間的 gdb index:

* GDB now has support for reading and writing a new .gdb_index section. This section holds a fast index of DWARF debugging information and can be used to greatly speed up GDB startup and operation. See the documentation for `save gdb-index' for details.

7.4 版: 不要在離開 scope 時刪掉 watch 的目標變數:

* The "watch" command now accepts an optional "-location" argument. When used, this causes GDB to watch the memory referred to by the exblockquotession. Such a watchpoint is never deleted due to it going out of scope.

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...