2012年12月15日 星期六

Some tips about Android JNI

和 JNI 奮鬥了一陣子, 隨手備忘查到的東西

用法和一般 Java JNI 差不多, 所以可以先參考一般 JNI 的用法, 再來看 Android JNI Tips:

C 和 C++ 的寫法只有一點小差異, 見 Wikipedia 的範例, 這樣看別人的範例時, 可以一併參考 C 或 C++ 的 code

JNI 的用法和 reflection 的概念差不多, 要先取得 class、再取得 method、再呼叫 method。所以比較需要查的是型別參數:

  • 這裡的 Type Signatures
  • constructor 的名稱是 <init>
  • inner class 的型別用 $ 分隔, 比方 package com.mypkg 的 class A 內有 inner class B, 型別名稱為 Lcom.mypkg.A$B;。注意 inner class 為 non-static 時, 要先有 A 才能 new 出 B

2012年12月2日 星期日

在新的 thread 裡呼叫 method

若需要執行一個可能會花些時間的函式 (e.g., 有 blocking read) 但又不希望停住目前的 thread, 解法之一是產生一個新 thread, 然後在該 thread 執行原本欲執行的函式。先來看看 Python 遇到這種情況要怎麼作:

import thread

class A(object):
    def out(self):
        print 'A.out()'

a = A()
thread.start_new_thread(A.out, (a,))  # 就這行

相當地直覺。

在 C++ 裡想要執行 non-static member function 的話, 就有點惱人。解決的方法很多, 以下是我土法煉鋼的陽春解法。看 code 即可明白, 關鍵是使用 template 以及 pointer-to-member function。算是一個練習 template 的好題目, 還有了解 C++ 編譯時期多型的威力。

t.h

#include <pthread.h>

template<class T>
struct MethodWrapper {
  typedef void(T::*Method)();
  MethodWrapper(T *object, Method method)
      : m_object(object), m_method(method) {}

  void Run() { (m_object->*m_method)(); }

  T *m_object;
  Method m_method;
};

// The interface for pthread's usage.
template <class T>
void* CallMethod(void *v) {
  MethodWrapper<T> *p = (MethodWrapper<T>*)v;
  p->Run();
  delete p;
  return NULL;
}

template <class T>
inline pthread_t RunInNewThread(MethodWrapper<T>* param) {
  pthread_t th;
  pthread_create(&th, NULL, CallMethod<T>, (void*)param);
  return th;
}

f.h

#include <iostream>
#include <sys/syscall.h>

struct A {
  void Out() {
    std::cout << "tid=" << syscall(__NR_gettid)
        << " A.Out()" << std::endl;
  }
};

f.cpp

#include "f.h"
#include "t.h"

/* Compilation: g++ f.cpp -o f -g -Wall -lpthread */
int main(void) {
  A a;
  a.Out();  // Direct call.

  MethodWrapper<A>* m = new MethodWrapper<A>(&a, &A::Out);
  m->Run();  // Called via the wrapper.

  CallMethod<A>(m);  // Called via a template function.

  m = new MethodWrapper<A>(&a, &A::Out);
  pthread_t th = RunInNewThread(m);  // Called in a new thread.
  pthread_join(th, NULL);
  return 0;
}

C++ passing method pointer as template argument 提到用 boost::function0 的解法, 以後再來讀吧。

2012-12-02 更新

Kencommand 補充了簡單的作法:

Btw, 這篇文章主要目的是練習 template, 另外在這裡補上若要土法煉鋼地支援參數時該怎麼辦。

2012年11月28日 星期三

在 x86-64 上對 system call 使用 conditional break

在了解目標模組的動態行為時, 最近覺得從模組的 public interface 設中斷點是滿不錯的作法。廣意來說, system call 則是最一般化的 public interface, 從 system call 回頭夾擊目標也滿有效的。比方像這篇strace 找到切入點。

若想獲得更多資訊, 可以用 gdb 在 system call 上設中斷點, 一步步從退回來找到呼叫者、傳遞的資料等更多訊息。但在尋找 write、send 這類廣為使用的 system call 時, 被呼叫的次數過於頻繁, 就有點難找了。

查了一下, 看到這篇提到從 register 設定 conditional break 的方法:

(gdb) b write if 1==$rdi  # stop only when write(1, ...)

Scott 補充了以下的資訊:

要在 syscall argument 上設條件,且要 portable 以目前的工具不容易。用 gdb 的話需能背出『x86-64 上參數依序擺在 rdi, rsi, rdx, rcx, r8d, r9d』。 x86-64 上特別好記,因 syscall 與 一般 function call 被設計成將參數擺在同位置。ARM 上還有 64bit 參數開頭一定擺在偶數暫存器如 r0-r1, r2-r3 而不擺在 r1-r2 等規則。

目前 gdb 沒有將 syscall parameter 存在如 $syscall_arg0 的 convenience variable 中,否則寫: catch syscall write if $syscall_arg0 == 1 即可。

用 "system call calling convention" 當關鍵字查到《What are the calling conventions for UNIX & Linux system calls on x86-64》, 文中有許多相關資訊, 就先備忘吧。

2012年11月24日 星期六

善用 prctl(PR_SET_NAME, name) 協助 debug multi-thread

參考文章: Name your threads

man 2 prtcl 說 prctl(PR_SET_NAME, char*) 會改變 process name, 但是它其實改的是 thread name。使用 gdb 的 info thread 會顯示 thread name, 這對於除錯 multi-thread 很有幫助。需要注意的是, 小心別改到 main thread 的名稱, 避免其它相關 kill 指令或自己寫的 script 失效。

2012-11-25 更新

Scott 提醒, 使用 pthread_setname_np(pthread_t, const char*) 更頗當, 且可用來設別的 thread 的名字。注意參數是 pthread_t 不是 tid, 還有不知為何, Ubuntu 12.04 下沒有它的 man page, 但 /usr/include/pthread.h 有它的宣告, 且編譯連結試用後, 也沒有問題。

2012年11月22日 星期四

善用 shared library visibility 減少程式之間的衝突

關於 static library 和 shared library 的基本知識:

static library 沒有特別的, 但是 shared library 有不少神奇的功能可用。

想像一個情境, 有四個獨立的專案 A, B, X, Y 四者, 其中 A 用到 B, X 用到 Y。若 A 也想用到 X, 但 B 和 Y 有重覆的 symbol, 得在編譯和連結時動點手腳, 才能過關。

上面情境的示意圖:

A -> B
|
v
X -> Y

假設編譯 B, X, Y 時產生 static library, 在產生執行檔 A 時, ld 會抱怨某些 symbol 衝突 (在 B 和 Y 裡面)。

static library 只是一堆 object file 的集合體, 就像 tar 包覆一堆檔案一般, 只是 static library 裝的是 binary 並且可以加入 index 檔。也因此 static library 通常比 shared library 大包, 因為 static library 不管 (也無法知道) 每個 symbol 最後是否會被用到, 總之就先留著它。

shared library 多了不少功能可用, 其中一個實用的功能是 visibility。可透過編譯 (非連結) 時下參數隱藏不需要的 symbol。以上面的例子來說, 若 A 只用到 X 且不會用到 Y, 那麼, 改用 shared library 的 visibility, 可以避開 B 和 Y 衝突的 symbol, 作法如下所述。

為簡化描述, 以下用單一檔案表示一個專案:

1. 產生 libXY.so

$ g++ -c Y.cpp -fPIC -fvisibility=hidden
$ g++ -c X.cpp -fPIC
$ g++ -shared -o libXY.so X.o Y.o

注意: 編譯 Y.cpp 時多了 -fvisibility=hidden, 表示除非程式內有用 g++ 特有的語法指定 visibility (__attribute__((__visibility__("default"))), 不然預設行為變成:

  • 無論原本 C++ 語意的 scope 為何, shared library 外看不見此 object file 的 symbol
  • shared library 內仍採用 C++ 的語意

對部份平台來說, -fPIC 是編譯 shared library 的必要條件, 先當作編譯 shared library 時需要在編譯時加上 -fPIC 吧。

2. 產生 libB.a

$ g++ -c B.cpp
$ ar rvs libB.a B.o

shared 或 static library 在此無關緊要

3. 產生執行檔 A

$ g++ -c A.cpp
$ g++ A.o libB.a libXY.so -o A

備註:

2011-11-24 更新

Scott 補充有關 PIC 的說明, 我就直接備忘在這裡啦。

相關 key word: "text relocation" [1] "text" 在此指機械碼,跟可執行檔中擺機械碼的 section 稱為 .text section 一樣。"relocation" 指『有參考到某 symbol,故連結器需一併修改的地方』。有加 *-fPIC*,compiler 產出的機械碼就不會有 text relocation,dynamic linker 才願意在執行時載入。

在幾乎全部平台上, -fPIC 或 -fpic 都是必要的。10 年前 i386 Linux 上不加也可以,但現在連 i386 預設也會被 security policy 拒絕。*elfutils* 中有個 eu-findtexrel 可找出 shard library 中沒加 "-fPIC" 編譯的 .o。

運作原理上,若沒加 -fPIC ,compiler 產出的機械碼中每個用到全域變數與函式的地方都會嵌有該 symbol 的位址。即 shared library .text section 中會散佈很多 memory reference。但那些位址在執行期間需被 dynamic linker 修改。加了 -fPIC ,產生出來的機械碼 reference 全域變數與函式的方法就不一樣了 [2]。

在安全性上不希望一頁記憶體既可寫又可執行;從多 process 共用 shared library .text section 的 physical memory pages 角度來看,希望 .text section 保持不變且 read only。 所以後來 dynamic linker 遇到有 text relocation 的 shared library 就拒絕載入了。

[1]: http://www.akkadia.org/drepper/textrelocs.html [2]: http://www.iecc.com/linker/linker10.html

GNU Makefile 雜項語法備忘

一般的 tutorial 教得都差不多卻少了一些我想知道的語法, 以下是自己備忘用的語法, 對於讀別人的 Makefile 時有幫助

$ cat Makefile
var ?= xxx         # assign var = xxx if var is not assigned
.PHONY: all        # tell make that "all" is not a file
all:               # first target is the default target
    @echo make-all # @ means do not display the cmd
    @echo all: x $(x)

all: b             # all depends on b

b:
    @echo make-b   # must use TAB to indent actions
    @echo b: var $(var)


all: a             # now all depends on a and b

a: x:= 3           # set x = 3 only in this context
a:
    @echo make-a
    @echo a: x $(x)

reverse = $(2) $(1)  # define a function
c:
    @echo c: x y
    @echo c: $(call reverse, x, y)  # use "call" to use
                                    # defined functions

範例輸出

$ make
make-a
a: x 3
make-b
b: var xxx
make-all
all: x
$ var=ooo make b  # override var
make-b
b: var ooo
$ x=9 make  # set x = 9 globally
make-a
a: x 3   # note that x is still 3
make-b
b: var xxx
make-all
all: x 9
$ make c
c: x y
c: y x

注意 Makefile 自己有套變數, 想改變 g++ 的參數時, Makefile 必須寫成傳遞變數作為 g++ 參數, 比方說慣例上會用 CFLAGS, CXXFLAGS, LDFLAGS 之類的。若 Makefile 沒這麼定, 想改變 g++ 用的參數時, 就只能直接修改 Makefile 了。

2012-11-25 更新

Scott 提醒, 補上有用的參考資料:

2014-01-20 更新

更新連結, 還有補上 Scott 在留言裡補充的說明:

用『=』定義的變數像是建立一個數學關係式,每次用到該變數時會重新解譯、計算出新的值。

2012年11月18日 星期日

查詢 Ubuntu package 編譯時的參數

GDB Python API 提到要在編譯 gdb 時有加 --with-python 才會支援此功能, 想確認編譯 Ubuntu 12.04 的 gdb 時, 是否有下參數 --with-python, 參考資料: compiling - Where can I find the configure options used to build a package? - Ask Ubuntu

摘要回覆的作法如下

抓原始碼看編譯規則

$ apt-get source gdb
$ cd gdb-7.4-2012.04/
$ vi debian/rules

然後看到兩組 configure rule, 一個對到 --without-python, 另一個對到 --with-python, 嗯..., 雖然看起來比較像後者, 還是有些懷疑

查官方 build log

$ apt-cache showpkg gdb

得知名稱是 7.4-2012.04-0ubuntu2, 接著到 https://launchpad.net/ubuntu/+source/gdb 查詢:

  • click 7.4-2012.04-0ubuntu2 updates (main) 2012-05-15
  • click Builds 下的 amd64
  • click Build status 下的 buildlog

查詢 ./configure 找到 --with-python, 所以應該是有加上 --with-python 才是

2012年11月17日 星期六

以使用 libsqlite 為例說明如何找到程式的進入點

一直對於如何善用 runtime 資訊找到程式進入點很有興趣, 終於有個不錯的小例子。

目標

假設要觀察的專案有用到 sqlite 儲存資料, 從使用方式知道一開始會先載入 sqlite 的內容, 現在想找到程式讀取第一筆資料的進入點。

第一步先觀察程式如何使用 sqlite, 是用 shared lib 或是直接包含在程式裡:

$ ldd PROG | grep sqlite

Case 1: 沒有結果, 表示 sqlite 的實作直接含在 PROG 裡面

找出和 sqlite 相關的 API:

$ nm PROG | grep sqlite | awk '{print $NF}' | xargs c++filt

或是查官網文件也成, 不過個人覺得 nm + c++filt 這招比較方便也比較酷。

觀察一下後, 得知開檔的 API 有: sqlite3_open, sqlite3_open16, sqlite3_open_v2, 之後用 cgdb 執行程式, 都設中斷點, 就結案了。

Case 2: 有 grep 結果, 表示在 shared lib 裡

若不想看官方文件, 也想來個動態搜集 API, 可用 ltrace:

$ ltrace -l /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 PROG

其中 libsqlite3 的路徑是從 ldd PROG 得知的。

安裝需要的 debug symbol, 以做進一步觀察:

$ aptitude search sqlite | grep dbg # 找到 libsqlite3-0-dbg
$ sudo aptitude install libsqlite3-0-dbg

再來就用 cgdb 執行程式, 準備設中斷點, 結案。

相關參考資料:

以 abstract class 為例說明 C++ 編譯器和連結器的運作

ps. 經 Scott 提醒發覺內容有誤, 已更新內文。

Effective C++ item 7 提到, 若想使用 abstract class A, 建議這麼做:

// a.h
class A {
public:
  virtual ~A() = 0; // 注意只有宣告沒有實作, 是 pure virtual
};

但是還是得另外提供 ~A() 的實作:

// a.cpp
#include "a.h"
A::~A() {}

不然連結時會出錯

以 class B 繼承 A 為例, 假設程式如下:

// b.h
#include "a.h"
class B : public A {
};

// b.cpp
#include "b.h"
// Nothing.

先來看少了 a.cpp 的情況。由於編譯時只會看 header, 編譯器會阻止任何嘗試生成 A 的程式, 這是我們想要的好結果, 利用宣告含有至少一個 pure virtual 函式的作法, 得到 abstract class 的性質。用 destructor 是不錯的選擇, 不會因此增加不必要的 virtual method, 況且 「base class + 使用多型」的情況要有 virtual destructor, 和使用 abstract class 的目的相合。

然後, 來看編譯 b.cpp 得到的 b.o。不論是否有呼叫到 B() 或 ~B(), 編譯器都會產生 B() 和 ~B() 的 binary code。編譯器對照 a.h 和 b.h 覺得 access level 沒有問題, 不會有編譯錯誤, 其中 ~B() 會呼叫 ~A(), ~A() 的 symbol 還未定義, 等待連結時補上。

但是, 若少了在 a.cpp 內 ~A() 的實作的話, 連結時會發覺找不到 ~A() 的 symbol, 於是有 link error。就語法來說這結果挺怪異的, 宣告為 pure virtual 卻又得提供實作才行。反之, 從實作面來看 compiler 和 linker 怎麼運作, 就不會覺得奇怪。又一次讓我覺得要理解 C++ 的語法, 得從運作的方式來理解才行。只看語法的話, 不太容易理解和記憶。

關聯文章:

2012年11月15日 星期四

編譯或連結錯誤的檢錯流程 (初版)

開發環境是 Ubuntu。先寫篇草稿, 日後慢慢補完。

找不到 xxx.h

檢查是否有 xxx.h

$ sudo updatedb && locate xxx.h

若 OS 內沒有的話, 看看要裝什麼套件才有

$ sudo apt-file update && apt-file search xxx.h

確定有檔案後, 檢查使用 libxxx 需要用的編譯參數為何

$ pkg-config --cflags xxx

若不確定 pkg-config 參數的名稱, 使用 apt-file search 查到的 package 名稱 "PKG-X", 查詢 PKG-X 包含的檔案

$ dpkg -L PKG-X | grep pkgconfig

比對編譯時用的參數, 是否有含到正確的 include path (參數 -I), 沒有的話, 可能是 makefile 出錯, 檢查產生 makefile 的設定檔是否正確

若 OS 內沒有 xxx.h, 也沒有任何一個套件含有 xxx.h, 可能目前 OS 太舊, 用 Ubuntu Packages Search 查詢 xxx.h, 確認是否新版的 OS 才有 xxx.h

相關文章

2012年11月3日 星期六

C++ 隱藏共用的 helper class

以前寫 Java 時滿習慣用 helper class 分擔主 class 的一些工作, 簡化主 class 本體的複雜度, 或是提供輔助用的 data object, 但只在內部使用, 不讓外部使用。在 Java 裡滿直覺的, helper class 宣告成 package-private 即可。但在 C++ 的情況, 明白 compiler 怎麼編譯 C++ 程式後, 才想通作法。

程式如下:

a.h

class X;

class A {
public:
  A();
  void print();

private:
  X* x;
};

class B {
public:
  B();
  void print();

private:
  X* x;
};

a.cpp

#include <iostream>
#include "a.h"

class X {
public:
  void print(const char *s) {
    std::cout << s << std::endl;
  }
};

A::A() : x(new X()) {}

B::B() : x(new X()) {}

void A::print() { x->print("A"); }

void B::print() { x->print("B"); }

int main(void) {
  A a;
  B b;
  a.print();
  b.print();
  return 0;
}

兩個關鍵:

  • a.h 需要知道 X 是 class, 不是天外飛來的不知名符號, 必須有 forward declaration class X。include a.h 的程式沒有 X 的完整宣告, 自然也無法使用 X
  • class A 和 B 不能宣告 member field 為 X x, 必須用指標, 因為在宣告 A 或 B 的物件時, compiler 要知道該在 stack 上配置多少空間, 但 a.h 裡沒提到, 所以除了 a.cpp 以外引入 a.h 的程式也不會知道, 這和 Pimpl 宣告實作物件為指標是同樣的原因。用指標的話則無此問題, 指標不論型別大小都一樣

相較於其它語言, C++ 給我的感覺是, 從思考 Compiler 如何生成程式的角度來看, 會比較容易理解它的語法和限制

2012年10月31日 星期三

手機瀏覽器捲軸的使用體驗

花了點時間研究在手機上使用 home/end 的操作方式, 這和切換分頁一樣, 各家瀏覽器都發揮不少創意

  • ASUS Transformer 和 Padfone2 修改了內建瀏覽器, 在捲動時右側不只會有捲軸, 捲軸較大且有上下移動的箭頭 (參考圖片來源), 可以用手按住拖放到畫面上任何一個位置, 實際用起來還滿方便
  • Opera Mobile 在捲動速度夠快時, 會出現一個箭頭的按鈕 (參考圖片來源), 點了以後會跑到畫面的邊緣。往上捲會出現向上的箭頭, 功能同桌機的 Home; 反之是向下箭頭, 功能同 End
  • Dolphin 使用手勢, 點畫面左下角開始輸入手勢, 並內建 Home/End 的手勢

就我個人的偏好來說, 手勢不夠直覺, 雖然在用桌機 Firefox 時, 我有用 mouse gesture 來取代 Home/End, 但對大眾來說, 這個功能需要學習

Opera Mobile 的作法有些小問題, 箭頭按鈕太常出現會有一點惱人, 但捲動速度不夠快不會出來, 部份使用者怎麼用都不會察覺這功能。這一年來的經驗告訴我, 使用者點擊或捲動的速度, 快慢的差距還頗大的, 若只依自己使用習慣設計速度區間, 下場會很慘

ASUS 的作法最直覺, 也沒什麼副作用, 只是實作上辛苦一些就是了。

再回來看 Opera Mobile 的作法, 若需要的功能就只有 Home/End, 這個作法滿直覺易用的, 且會比 ASUS 的作法再好用一點點 (影響不大)。令我好奇的是, Opera Mobile 按下捲動按鈕後, 會快速地逐步捲到底, 而不像桌機按 Home/End 那般跳到底

琢磨了一陣子才想通, 原因是這個功能是捲動後才會浮現按鈕, 按下按鈕後繼續維持捲動的感覺, 但用飛快的速度捲到底, 才有一致的操作感。反之, 若按下去就跳到畫面底部, 會有一點突兀, 使用感覺稍微差了一點。使用者體驗有許多細節沒有搭好, 用起來的感覺就是怪怪的, 但又很難說出來, 滿特別的一門專業

2012年10月23日 星期二

查 C/C++ symbol 定義的方法

記一下目前習慣找 SYMBOL 定義的方法, 只是模糊的片段經驗, 日後再補充完整一些

gj

在命令列打: gj SYMBOL classgj SYMBOL struct 找類別定義

找不到的話, 有可能 SYMBOL 是用 code-gen 或巨集產生的。

  • 檢查一下 mkid 時有沒有漏掉 code-gen 的檔案, 再重建索引: $ mkid -m FILE_LIST ( 格式見 Language map - ID database utilities )
  • 可用 $ g++ -E TARGET.cpp -IINCLUDE_DIR 展開檔案, 再回頭看 SYMBOL 是否是巨集產生的。

另外可用 gj SYMBOL void 找函式定義 (針對無傳回值的情況), 或是 gj SYMBOL 再用 ".h" 過濾只留 header 檔。

Btw, 要找 symbol 被使用的地方的話, 就直接 gj SYMBOL

用 gdb

在 gdb 裡用 ptype VAR 會列出 VAR 的型別資訊

2013-12-08 更新

後來依自己使用習慣在 gj 加了一些功能, 像是用 gj -d1 SYMBOL 找定義或宣告, 用 gj -s LITERAL 找可能的 symbol 名稱, 詳見 gj 在 github 上的說明

2012年10月21日 星期日

記得填好 html meta data 和之前踏過 mobile browser 的雷

雜亂記一下前陣子寫網頁的心得。

之前試了 "margin: auto;" 要做置中效果, 結果在 IE 9 無效, 看到 Margin auto doesn't work in IE? 才知道是忘了填 doctype

另外 一次Ajax查错的经历 提到沒填 http header 沒返回 charset=utf-8, 造成 AJAX 在 IE 上的錯誤。一些 meta data 記得要填好, 避免遇到神奇難解的錯。

順便記一下以前遇到其它的雷:

  • Chrome on mobile 預設 viewport 用 980 來 render 網頁, 若最小頁寬為 1024 會看到畫面 render 後稍微超出畫面, 然後自動縮回螢幕寬, 但是右側邊緣畫面就破了, 最後只好改用 980 為最小頁寬
  • Safari on mobile 不支援 1x1 的 png, 半透明效果會不對, 改用 2x2 就好了, 若不是看到 iphone - Alpha transparent PNGs not displaying correctly in Mobile Safari, 我怎麼也不會想到要這樣解 ...

以前作網站要跨 IE 6, 7, 8 很痛苦, 現在作網站少了 IE 6, 7 的困擾, 但多了 mobile browser 的困擾。只有長期做這行的人, 才知道其中的心酸吧。不過現在 Chrome on mobile 和 Safari on mobile (iOS 6) 都有提供搭配桌機 Chrome/Safari 使用 web inspector, 應該會輕鬆一些, 我還沒試用過就是了。

css 同欄高是件困難的事

若搜尋 css same column height 會看到很多解法, Equal Height Columns with Cross-Browser CSS and No Hacks 是我看到比較漂亮的解法, 並附有詳細說明為什麼如此複雜。

看完後第一印象是想到: Give Up and Use Tables。雖然用 table 好像很遜, 但是有時用 table 可省下不少工夫。為了不要用複雜的方法做同欄高, 也不要用感覺很遜的 table, 最後我是直接用 css 設死欄高 ..., 當然, 這招遇到欄高變化很大時就糗了, 等遇到再回來面對這問題吧。

svn merge 用到的一些指令

備忘一下

svn merge --accept postpone -r BEGIN:END FROM TO

  • --accept 表示之後再處理 conflict, 別一個個問
  • BEGIN:END 表示合併從 BEGIN 開始到 END 的變化, 比方 10:15 表示會含入 11 到 15 的 commits

svn revert -R DIR

放棄更新 DIR 目錄下的檔案, merge 到不知在做什麼時, 用這個全部重來

svn status | grep "^.\{0,6\}C"

  • 找出有 conflict 的檔案

svn resolved FILE

  • 注意是 resolved

https 到 http 沒有 referrer

筆記一下讀到 BobChao the Blogger: HTTPS 的 referrer 狀況筆記 的心得和延伸想法。

近年來強調安全瀏覽, 大網站倡導全程使用 https 連線, 但是從 https 連到 http 時不會傳 referrer, 原因應該是 https 全程加密, 若從 https 連到 http 時有加 referrer, 會讓原本外部不知道的網址流出去。比方我先點 https://a.b.c/, 然後點網頁內連結到 https:/a.b.c/secret.html, 再點連結到外部的 http://x.y.z/, 這樣 x.y.z 網站就會得知 https:/a.b.c/secret.html。

但令我不解的是 https 連到不同網域 https 時, 還是會送 referrer, 所以上述的推論要修正成: 防範的對像不是 x.y.z, 而是其它用 http 的網站 (??) 以及竊聽封包的人。全程使用 https 不會被竊聽到網址, 而最後一步從 https 到 http 時, 若有設 referrer, 就會被竊聽得知。這是我目前想得到唯一合理的解釋了, 雖說我還是覺得沒什麼道理, 挺多 a.b.c 要連到別的網站時, 即使有 https 可用, 仍可選擇 http 以避免送出 referrer, 但這顯然和現今大家關心的方向相反。

於是, 為了兼顧安全性, 以及網站能正確統計 referrer, Google 和 Facebook 會先跳到內部同網址的 http 連結, 再轉到外面 http 的連結。雖然會多加一個 click 增加內部成本, 但為了能讓其它網站了解本站 (Google / Facebook) 為他們帶來的可觀流量, 仍是值得花的代價。

2012年10月19日 星期五

apache 的 response time 很慢

注意到某台 web server 反應速度非常慢, 依序檢查相關資訊如下:

  • tail -f /var/log/apache2/access.log 可知 request per second 頗高, 但不確定有多少
  • vmstat 回報 ram, cpu, io 都沒什麼 load
  • 用 chrome developer tool 看 (Network 頁), 幾乎都耗在 waiting, 傳資料時間仍然很短
  • 家裡電腦 ping web server < 200ms
  • wget localhost 也要等個數秒以上

綜合以上資訊可知, 機器應該可以負擔, 不是 CPU / RAM / disk IO / 頻寬 / 連線不穩等問題。

依照以前寫 network programming 的經驗, 當 concurrent new connection 量很高, 高出接收 connection 的 daemon 能負擔的量時, 得等一陣子才能連上線, 剩下的處理時間到不會增加多少。所以推測此時 web server 應該是類似的情況, 所以要從縮短受理新 connection 的時間下手。

參考 Apache Performance Tuning 的說明, 最後提高 MaxClients, 縮短 KeepAliveTimeout, 情況就變正常了。

文中提到計算 MaxClients 的相關指令挺方便的:

ps -ylC httpd --sort:rss: 得知 httpd 用的 memory
free -k: 得知目前記憶體用量

MaxClients = 可用記憶體 / 單一 httpd 用的記憶體

2012年10月5日 星期五

在 vim 內直接搜尋專案目錄下的字串

以前用 id-utils 為底, 寫了個小程式 gj, 用來搜索目錄下的關鍵字。基本上 gj 已滿足我八成的需求, 不過有時還是覺得若能在vim 內搜尋就更方便, 不用複製目前游標下的字串、開新視窗、貼上字串、執行 gj

最近找到 ack.vim, 發現完全符合我的需求, 只不過它是用 ack 搜尋, 不是用 id-utils, 檔案多的時候搜尋速度較慢。ack.vim 用 vim 的 grep + quickfix 達成目的, 只要改用 id-utils 作索引和搜尋即可, 剩下部份可直接拿來用, 沒花多少工夫就改好了。

安裝方式如下:

  1. sudo apt-get install id-utils
  2. wget https://raw.github.com/fcamel/configs/master/.vim/plugin/ack.vim && mv ack.vim /.vim/plugin/
  3. wget https://raw.github.com/fcamel/configs/master/bin/gid_with_col.py && chmod +x gid_with_col.py && mv gid_with_col.py /bin
  4. $ cat << EOF >> ~/.vimrc
    let g:ackprg="gid_with_col.py"
    nnoremap <silent> <Leader>g :Ack<CR>
    EOF
    

  5. $ cd /path/to/project && mkid # 建立索引
接下來在專案目錄下用 vim 編輯檔案, 按 \g 就會搜索目前游目標下的字串, 在下方開啟索尋結果。或是用 :Ack <pattern> <pattern2> ... 做更細的搜尋。

用法參見 ack.vim:

o    to open (same as enter)
go   to preview file (open but maintain focus on ack.vim results)
t    to open in new tab
T    to open in new tab silently
v    to open in vertical split
gv   to open in vertical split silently
q    to close the quickfix window
不過我將 t 的功能註解掉了, 需要的人請自行修改 ack.vim 去掉註解。

2012年10月2日 星期二

Ubuntu 遇錯誤訊息時畫面會閃礫並失去 focus

比方說在 bash 打 "ls alksjdkl" 再按 tab, 或在 Eclipse 裡搜不到字串時, 系統會閃礫一下然後失去 focus, 還得 alt-tab 切回來很麻煩。

狀況實在太難描述不知該怎麼 google, 只好自己想想到可能的原因, 還有亂搜一些關鍵字。最後發覺這和 PC 喇叭的嗶聲有關, bash 在 inputrc 裡關掉嗶聲後就好了。

但 Eclipse 不知要怎麼關掉嗶聲, 這個情況降低音量不會有效, 只要有觸發嗶聲就會發生這現象。換句話說, 在 bash 上打 echo -e '\a' 就會如此。

後來想到該不會是以前為了除錯移掉 pulse audio 的副作用, 試著重裝 pulse audio 沒效, 只好先還原 VM 到移除 pulse audio 之前。果真 echo -e '\a' 不會閃礫, 確認和 '\a' 以及聲音驅動有關。

下一步就是用 dpkg -l | grep audiodpkg -l | grep audio 找出以前裝過的套件, 然後安裝回目前的 VM:

sudo aptitude install gstreamer0.10-pulseaudio libcanberra-pulse libpulse-browse0 libpulse-dev libpulse-mainloop-glib0 libpulse0 libsdl1.2debian-pulseaudio pulseaudio pulseaudio-esound-compat pulseaudio-module-bluetooth pulseaudio-module-gconf pulseaudio-module-x11 pulseaudio-utils gstreamer0.10-pulseaudio libcanberra-pulse libpulse-browse0 libpulse-dev libpulse-mainloop-glib0 libpulse0 libsdl1.2debian-pulseaudio pulseaudio pulseaudio-esound-compat pulseaudio-module-bluetooth pulseaudio-module-gconf pulseaudio-module-x11 pulseaudio-utils

最後問題就解決啦!

ps. Gnome Linux Disable / Turn Off Hardware Beep Sound For Terminal:有提到各種關掉嗶聲的作法。

2012年9月10日 星期一

iptraf filter 用法

我用 ssh 連上 server 跑 iptraf, 但 ssh 資料一直更新 (因為畫面顯示要傳資料回來, 所以就無限循環 ...), 得濾掉 ssh 資料才行

參考資料:

注意事項:

  • 試了半天, IP filter 好像只有影響到 IP traffic monitor, 結果這句話就寫在官方文件的開頭 ...
  • Filtler -> IP ... 會看到右側寫 "IP filter active" 或 "No IP filter active", 表示目前有無採用 IP filter
  • Edit filter 後要再按 Apply filter 才會生效的樣子
  • 可定義多組 filter, 但一次只會 apply 一組 filter
  • 同一組 filter 裡, 採用 first match 的樣子
  • 要濾掉 ssh, 要先寫一則 exclude port 22, 再寫一則 include all

2012年9月9日 星期日

CSS 雜項心得 (2)

重要的基本觀念如以前CSS 雜項心得所寫, 還是 box model, float, position 等, 但用熟開發工具, 寫網頁會更快。看完這篇 Modern Web Development Part 1 – The Webkit Inspector 後功力至少增加個一甲子, 才懂得怎麼運用 box model 的觀念配合 Chrome Developer Tool 的 Metrics 來看元素占用空間的情況, 遇到「破掉」或對不齊等問題, 用 Metrics 檢查一下, 就知道是 padding 還是 margin 出問題, 或是因內部的子元素往上或擠出空間等。相較以前, 除錯省了不少時間。

過了兩年, 如今不用擔心 IE 6 或著也不用擔心 IE 7 了, 不過 HTML5 和 CSS3 多了不少東西, 支援的瀏覽器變多後, 也就更值得學。所以, 要學的東西也許是不會變少吧

用 CSS 寫出多欄等高的版面

找了一下如何用 CSS 寫出多欄等高的版面, 看到這篇Equal Height Columns with Cross-Browser CSS, 是找到幾個方法裡最完備且乾淨易懂的作法。不過實際應用上會有些小地方要注意, 也難怪有人 Give Up and Use Tables的主張了 ...

2012年9月1日 星期六

使用 PIcasa 批次改圖

兩個重點

API 使用 http 做為底層 protocol 的好處

以前對 http-based 的 API 一直有疑問 (免責聲明: 我沒有弄懂 RESTful 和這點的具體差別), 為什麼要綁在 http 這個不太相關的協定上? 尤其是在試用 Solr 後, 發覺因為使用 http 提供 api 的緣故, 即使是 localhost 連線做很簡單的查詢, 也有一定的 latency 成本。

不週在多了一些 network programming 經驗後, 對此有了不同想法。

首先, network programming 很痛苦, 一開始要處理兩個問題: 第一點是在 streaming-based 的資料上, 提供 datagram 的概念, 這樣才有辦法傳遞和接受參數。streaming 不同已有完整資料的檔案一般, 資料回傳時間不一定, 每次讀回的 byte 不一定, 提供 datagram-like 的介面, 要費點心思。

第二個問題是, 要將資料 serialization, 這件事本身也很苦, 若要求效率, 要用 binary 格式, 實作苦除錯更苦; 若用 text 表示 (如 JSON), 也許有一天會有效能問題, 若需要傳 binary file, overhead 太大。

第一和第二問題在面對多種語言的 client 時 (比方 PC 上有人想用 Python 或 C++, mobile 上有 Objective C 和 Java), 實作成本更高。

解決這些基本問題後, 若幸運地服務順暢, 用戶成長個 10 倍, 有什麼方法可以擋住 10 倍的使用量? 也就是 scalability 的問題, 這又是一串的麻煩開始。10 倍擋住後, 變 100 倍怎麼辦?

反之, 使用 http-based solution, 以上統統不是問題。有成熟的 web server 包含 scalability 都解決了, http client lib 也是遍地開花, 沒有找不到套件的問題, 只有太多種版本不知選那個好的困擾 (看看 python 那票 urllib, urllib2, httplib, httplib2 以及其它各家 3rd party 實作)。更可以直接用 browser 試用 API, 連寫程式都不用。

照這樣來看, 因為綁定 http, 而「稍微」多些 latency, 似乎不是什麼不得了的負擔。

ps.

  • kcwu 補充說明使用 http 還有減少被阻擋的機率, 有許多地方會擋 port 80、443 以外的 port。此外, http 有許多現成工具提供 encryption, compression, cache, proxy, load balance, authentication 等功能。
  • Socket Programming HOWTO 值得一讀, 簡要地描述 network programming 的基本知識以及實作會遇到的問題。

2012年8月19日 星期日

在 iPad/iPhone 上連續抓 screenshot

雖然 iPad 可以按 sleep button + home button 抓一次 screenshot, 但要抓第二次 screenshot 卻要等個數秒以上, 不夠應付我的需求。於是改查如何錄下 iPad 上的畫面, 得知有 AirPlay 這個好東西, 可以將 iPad 畫面轉到 Mac 或 PC 上。詳細介紹見 How To Record Your iPhone Screen or Record Your iPad Screen, 作法複製到這裡:

1. Install AirServer on your PC or Mac.
2. Make sure your PC/Mac is on the same wireless network as your iPhone or iPad.
3. Start AirPlay on your iPhone or iPad
4. Record your computer screen using Camtasia, Screenflow, etc.

裝 AirServer 時, 會要求裝 DirectX 和 Bonjour, AirServer 試用期有七天。之後參考 FAQ 說明, iPad 進 Airplane Mode 一陣子, 再取消, 然後雙擊 home button, 將下方畫面移到最左側, 就會看到 AirPlay 的 icon (進出 Airplane Mode 前, 我沒看到這個 icon)。

再來就找套可以一按 PrintScreen 就直接存下圖片的軟體, 就差不多達到目的了。

2012年8月7日 星期二

cookie 雜記

讀完這兩篇就差不多通了

做實驗的話, 配合 Firefox View Cookies 很方便。

基本知識

  • http 本身是 stateless, 理由大概是利於實作, server overhead 也小。若 server 想維持使用者登入狀態、個人偏好、追蹤瀏覽過程等有狀態的功能, 得透過別的方式, 也就是 cookie
  • cookie 是 http header 裡的其中一個欄位, server 透過 http response header 裡的 Set-Cookie 要求 client 記下資訊; client 之後看到 URI 符合 domain + path 時, 會在 request header 裡加上 Cookie 傳送當初用 Set-Cookie 存下的 key-value 回去
  • cookie 裡有個 expire 欄位表示什麼時候失效。有設這個欄位稱作 persistent cookie, 沒設的話稱作 session cookie, 表示關掉 browser 時, browser 要自行清除它

restore session 會一併回復 session cookie

  • Chrome 和 Firefox 在使用 "restore session" 的功能時, 不只會回復關閉時的頁面, session cookie 也會一併復原, 這是符合此項需求的設計, 不是 bug, 要 100% 重現關閉時的狀態
  • facebook 登入框下面有個 "keep me logged in", 還有 Google account 登入有個 "stay signed in", 有勾的時候會用 persistent cookie 記錄登入資訊, 沒勾時會用 session cookie。但若平時用 Chrome / Firefox 有開啟回復關閉時的頁面, 勾不勾都沒差

third-party cookie 和廣告商

  • 對於 domain A 來說, 可能嵌入其它網站的圖片、iframe 等, 這些物件也會有 cookie, 同時送出 http request 取得這些物件時, 會帶有 Referer 欄位指向目前網址 (即 domain A 下的某一 URL)。這類非 domain A 的 cookie, 稱為 third-party cookie
  • 因此, 網站加速技巧之一, 是將圖片放到另一個 domain, 不只可以增加 browser 同時取得檔案的連線數, 也可以避免不必要的 cookie 傳送
  • 反過來說, 如 Facebook、DoubleClick 這類會在很多網站嵌入他們物件的網站, 可故意放 cookie 來得知使用者更多資料, 因為使用者每到一個有嵌入網站 B 物件的網站, 都會送出目前所有搜集到網站 B 的 Cookie 資訊出去, 同時帶有目前網站網址 (透過 Referer 欄位)

CLOSE_WAIT 與 TIME_WAIT

《TIME_WAIT and its design implications for protocols and scalable client server systems》 這篇真是不可思議的詳細易懂, 讀完後大概理解 TCP 結束時的前因後果了。

首先要理解的前提是, 無論 process 如何結束, 只要網路連線正常, TCP 會保證另一端得知連線結束

接著問題要視主動 close 和被動 close 兩個方向來看。被動 close 比較單純, 會進入 CLOSE_WAIT, 程式要記得呼叫 close() 然後就沒事了。由 man page 得知, recv() 回傳 0 bytes 表示另一端結束連線。在 blocking IO 的情況下很單純, 不太會忘了 close。 non-blocking 的情況有可能寫出 bug, 而一直沒呼叫 close(), 這時用 netstat 可看到這類的連線狀態一直卡在 CLOSE_WAIT。

主動 close 的時候, 一切順利最後會進入 TIME_WAIT, 然後就是等一段時間才真的釋放使用的 port。對於同時需要主動連線出去 (做為別人的 client) 的 server 來說, 短時間內大量累加的 TIME_WAIT 有可能造成 port 不足而無法連線出去, 所以這篇的作者建議在設計 protocol 時, 盡量讓 client 主動結束連線。

綜合以上的訊息, 我理解的架構是: 盡可能讓 client 關掉連線, 再加上一個合理的長時間 timeout, 只有在 timeout 的情況下 server 才會主動關掉連線。發生 timeout 有兩種可能: client 其實「還在線上」, 只是很久沒傳資料; 或是 client 斷網後, 長於 timeout 的時間沒有連上網路。但不論何者, 為了減省 server 資源 (# of fd, memory, thread, etc), timeout 仍是必要的。

我之前在理解 TCP 斷線議題時, 一直卡在程式要怎麼知道對方斷線了? 搞清楚 kernel 實作 TCP 這點並做了些實驗後, 加上讀完這篇, 才恍然大悟。

TCP 與斷線

這件事困擾我一陣子, 一直搞不懂「網路斷線」是怎麼一回事。看了些資料做些實驗, 才發覺我搞錯斷線的意思。

TCP 是 kernel 實作的, 不管程式是自己掛掉還是被 SIGKILL 掛掉, 只要網路是通的, TCP 會如預期做結束連線的動作。即使中途 client 網路斷線, 接著 client 掛了, 待網路接通時, client 還是會送出結束連線的訊息給 server。偉哉! TCP!!

反過來說, 若網路真的不通, 在網路另一端的程式, 沒有方式可以得知這裡已經「斷線」, 挺多只能在接收或傳送資料時設個 timeout, 時間到沒回應就視對方為斷線。

總結來說, 只要另一端有辦法能連回網路, 無論發生什麼事, 都會通知對方結束網路連線, 這是由 kernel 保證的, 和 process 怎麼結束無關。但在網路不通的情況下, 雙方都無法得知對方是否真的結束了, 只能設個 timeout, 時間到就自己結束連線。

Btw, VM 真是測試網路斷線的好幫手, 方便在一台機器上做網路中斷的測試

2012年7月10日 星期二

使用 ffmpeg 壓縮影片

UbuntuCompilationGuide – FFmpeg 說明如何在 Ubuntu 上安裝 ffmpeg 並包含 libx264 (支援 H.264)。全部安裝和移除的指令都有了, 超方便的。實測的結果, 用 libx264 壓出來的檔案又小畫質又好, 是必要的元件。

H.264 web video encoding tutorial with FFmpeg 一步步解釋如何用 ffmpeg + H.264 來壓縮, 並解釋各項參數的意思。雖然 man page 都有說明, 但是實在是太~~長了。先從別人的文章找到關鍵的參數, 有必要時再回頭查, 輕鬆許多。

我最後用的指令如下:

$ ffmpeg -i INPUT.MOV -vcodec libx264 -b:v 3000k -r 25 -vf "transpose=1" -threads 0 -acodec copy -y OUTPUT.mp4

參數位置有影響

下錯的話 ffmpeg 不會有抱怨, 然後參數也無效。有時則是抱怨奇怪的訊息, 因此被誤導。這點讓我卡了最久。上篇文章作者建議的順序是:

ffmpeg [input options] -i [input filename] -vcodec [video options] -acodec

用原本的 audio codec 避免爆音

原本壓完有出現爆音, 改用 -acodec copy 就正常了。反正最主要的壓縮來自 bitrate, 這樣做沒什麼損失。

壓縮率主要最決於 bitrate (bits/s)

作者特別強調畫面大小不會影響檔案大小, 我也就沒試了。基本上就是調整 -b:v, 從參數和影片長度即可推論出最後大概的大小。2000k 畫質就很不錯了。

注意 FPS

不知為什麼我上回用 iPhone 拍的影片 fps 正常, 這回拍的 tbr 變成 600, 上回是 24。結果壓縮的時候, 顯示的 fps 變 90 多, 最後壓出來的影片雖然不大, 播放卻超級頓。從 ffmpeg -i INPUT.MOV 來看, fps 應該是 25 左右, 加上 -r 25 重壓就可以順暢播放了。

旋轉畫面90度

用 iPhone 直拍的話, 在桌機上播畫面會歪一邊, 可加上 -vf "transpose=1" 將它轉回來。視當初拍的角度, 參數要換不同值, 細節見 man ffmpeg 的 transpose 那節。

用多個 thread 加速壓縮速度

-threads 0 表示盡可能壓榨 CPU, 加這個參數後, 壓得超快的。

其它

  • 若改用 2-pass 效果可以更好 (待試)
  • 設定 buffer size 等參數可以讓較差的裝置播得順 (待試)

若要說出社會工作幾年後的最大改變, 就是我快變成 100% 需求驅動的學習方式了, 沒需求的話就暫時不想去試。Btw, 最近想試看看, 有沒有簡單的工具能降低影片裡的背景雜音。

2012年6月30日 星期六

用 /etc/hosts.allow 設定允許的連線

man hosts.allow 有說明, 或直接看這篇的說明, 寫這篇只是強化自己記下關鍵字。

現在設定允許的網段連線實在太簡單了, 記得十多年前好像很複雜, 然後記憶就停在十多年前的情況, 一直懶得看怎麼弄 ...

2012年6月28日 星期四

no space left on device

今天踏到 inode 用完的雷, "no space left on device" 除用完空間外, 也有可能是用完 inode , df 看還有剩空間時, 記得用df -i 檢查。

若要個別檢查那個目錄占掉最多 inode, 不知有沒有適當的指令, 我是用 "find DIR | wc -l" 來粗估那個目錄占掉最多個 inode。

2012年6月16日 星期六

如何監控和測量網路流量

兩個月前做的事, 現在已忘了細節, 留些連結備忘。

從 system call 下手

networking - How to measure network performance (how to benchmark network protocol) 提到可以用 strace 抓網路相關的 system call 來了解傳輸量。概念不錯, 但實作用起來很麻煩, 有以下的問題:

  • socket 也是 file descriptor, 不只可以用 send/recv 相關函式, 也可以用 read/write。要做到完全正確, 要追蹤開啟的 fd 是否為 socket
  • 這個傳輸量不等於真正的傳輸量, 因為 TCP 有可能重傳 packet。

優點大概是可以量得很細, 像是各 thread 的傳輸量如何。若 thread 各自有特定的工作, 也許會有幫助。

從網卡下手

tcdpdump:

看完的感覺是, wireshark 似乎不如 tcpdump 強? 附帶一提, tcpdump 名為 "tcp"dump, 但是是監控網卡, 實際上可看各種不同 protocol, 不只 tcp, 滿容易使用的。

IPTraf

附 screenshot 的簡短介紹: 唉呦~MIS先生: 好用的流量監控程式--IPTraf, 可觀看即時流量。很容易操作, 功能也很彈性。不過若要用在反覆執行的測量程式, 可能還是配合 tcpdump 再寫分析的 script 比較方便。

使用 Mac 產品觸控輸入的心得

和 MacBook Air 以及 iPad、iPhone 相處的時間多了一些後, 覺得 Apple 在輸入方式下的工夫真是相當深。

一樣是觸控的輸入方式, 可分成考慮螢幕所在位置和不考慮兩種用法, 比方說 MacBook 的 mouse pad 可用三指向左右滑, 切換畫面; 還有用兩指上下左右滑, 以捲動視窗。前者沒考慮螢幕游標的位置, 後者有。

在 iPad 上可用四指左右滑切換 App, 但在 iPhone 上找不到開啟這功能的選項, 也許是覺得在 iPhone 上用四指不合一般使用電話的姿勢, 寧願讓使用者透過 home 鍵切 app。

題外話, 使用這些裝置的過程中我也才明白實體鍵盤設計的巧妙之處。實體鍵盤的觸感讓我 100% 確定手指在的位置, 還有敲鍵的次數。所以我可以安心地看著另一個裝置的網址, 盲打一串長網址而不會輸入錯。

但反過來要用 iPad / iPhone 輸入網址時, 成功率實在太低, 以致於我寧願先用縮網址服務, 再回到 iPad / iPhone 上輸入縮址。若這個「將桌機的網址「傳到」行動裝置」的使用情境很廣的話, 也許值得針對此點寫個功能, 目前是覺得不值得就是了

寫 linux daemon 的注意事項

一直以來都很納悶為什麼寫 daemon 時需要 double fork, double fork 是必要的嗎? 最後才發覺我問錯問題了, 而問錯問題永遠不會得到對的答案。我該問的問題是, 成為一個 daemon, 必須該做那些事?

答案是:

  1. chdir("/"): 避免有任何目錄因為還有 process 在路徑內, 使得目錄已被移除, 卻無法釋放空間。
  2. 避免成為 zombie: 若 daemon 的 parent process 是 init 的話, daemon 結束時 init 會自動 wait 它, 避免成為 zombie。反之, 則有這點顧慮。
  3. 脫離 terminal: 因為關閉 terminal 的時候, kernel 會送 SIGHUP 給 session leader [*1]。以 shell 的實作來說, shell 是 session leader, 它在收到 SIGHUP 時, 會送 SIGHUP 給它所產生的所有 process group, 而 SIGHUP 預設行為是 terminate。但這不是 daemon 希望的行為, 相較於因此改變 SIGHUP 的行為, 不如脫離 terminal 更省事, 還可視需要保留 SIGHUP 做別的用途 [*2]
  4. 同上, 脫離 terminal 可避免不小心讀寫到 terminal 而收到 SIGTTIN 和 SIGTTOU。它們是 background process 讀寫 terminal 時觸發的, 藉由這樣告知 process 它目前是 background process 並嘗試要讀寫 terminal。雖然也可以改變這兩個 signal 的處理行為 (預設為 stopped), 但是不如完全不會發生來得省事。

有了以上需求, 就有不同的作法。POSIX 沒有定義 daemon() 函式, 不過 BSD 體系包含 Linux 裡有 daemon() 函式, 用來成為 daemon。eglibc 的實作方式如下:

  1. fork 一次, 讓 parent process 掛掉, 以滿足 (2)
  2. 呼叫 setsid() 成為新的 session leader
  3. chdir("/"), 以滿足 (1)
  4. 重導 0, 1, 2 到 /dev/null 以滿足 (3、4)

但 daemon() 沒有做第二次 fork(), 所以它仍是 session leader, 有機會開啟新的 terminal。若要避免這種可能, 呼叫完 daemon() 後再呼叫一次 fork(), 並讓原本的 parent process 離開, 這樣 daemon process 就會處在一個沒有 terminal 也沒有 session leader 的情況, 會更安全。

從再呼叫一次 fork() 來看, 會更能理解為什麼 daemon() 要開新的 session, 這樣在呼叫完 daemon() 後有比較大的主導權, 看是自己想開新的 terminal, 或是再做一次 fork() 確保不存在 session leader, 都沒有問題。

PS

  • *1 看了一些文件, 感覺上 session 是針對 terminal 而有的概念。而 process group 則和使用 signal 有關。
  • *2 有些 daemon 收到 SIGHUP 會重讀設定檔。由於 daemon 不可能觸發原本 SIGHUP 的使用情境, 可以安心拿它來做自己的用途。

2012年6月13日 星期三

在 Linux 上找執行時間的瓶頸

記錄一下自己加速程式的心得。重點是: 有個可靠且易於重覆執行的方式, 來計算執行時間。

logging

我最常用的方法是手動 logging 計算函式的執行時間。通常我有兩種作法

  • 在函式的開頭取得系統時間, 離開時再最得系統時間, 兩者相減得到執行的時間。 [*1]
  • 使用一個會自動附上 timestamp 的 logging function, 會記錄這次到上次 log 的時間差, 或事後用外部 script 算出各筆 log 之間的時間差。這樣可以在懷疑的函式不同地方做 log, 了解各區塊的執行時間。

PS *1 實作細節

  • 像 Python 有 decorator 的語法, 可以寫個 log_time(), 內容是「取得時間、執行目標函式、取得時間並記錄相減結果、傳回目標函式的傳回值」, 再用 decorator 的語法加到有興趣的函式上。
  • C++ 的話, 可以寫個 class LogTime, 在 constructor 以及 destructor 計時, 達到同樣效果。然後在目標函式的第一行宣告 LogTime log。

優點

  • 可以明確抓出函式花的時間, 對於「加快程式」來說, 是最確實的反應。不論是真的 CPU bound 或 lock 或 I/O 導致緩慢, 都會反應在 log 裡。
  • 可自行控制 log 函式的細膩度, 比方說先抓出 sort() 最花時間, 再來到 sort 裡放 log, 觀察在 sort 的那部份最花時間。

缺點

  • 得自己手動在懷疑的部份加上 log 函式, 不方便大範圍無腦偵查。

使用 profiler

linux - Alternatives to gprof 列了不少工具, 大概掃過了這篇, 也試了一下, 這部份的經驗很淺, 下面可能會有些錯誤, 像是對工具的錯誤認知, 或少用了什麼功能而覺得不好用。

感覺上這類工具只能反應出真的 CPU bound, 若原因是 lock 或 I/O 拖慢效能, 需要用其它管道查明源頭。

我是在 VirtualBox 裡的 Ubuntu 11.04 試用。選用的前提是對實際執行的負擔要低, 愈接近原本情況愈好。

perf

perf 很容易使用, 基本指令:

  • perf record CMD # 執行並記錄 CMD
  • perf record -p PID # 記錄 process PID
  • perf report # 看報告
  • sudo perf top # 顯示即時全系統狀況

但不知為啥占最多時間的總是奇怪的東西, 我寫個 for-loop 一直做加法, 結果部份時間花在 snd_ac97_codec (聲音?)。移除 PulseAudio 後, 變成花在 binfmt_misc。對於其它不是像 busy loop 的程式, 有可能最花時間的部份變成 snd_ac97_codec 或 binfmt_misc。讓我不知是否能相信它的結果。

2013-04-17 更新

在實體機器 (非VM) 上執行 perf 後, 就沒有一堆怪東西了。試用的結果相當滿意, perf 明顯的好處有兩點:

大概會讀一些組語的話, 用 perf 的效果更好, 可以在 perf report 後再觀看各函式最花時間的位置, perf 會嘗試對回組語附近的原始碼。注意 perf 觀察到的是 sample 結果, sample 量愈多, 可以採信的程度愈深。比方說 sample 量「不夠多」的話, 可以參考那個函式最花時間, 但可能不適合參考函式內最花時間的程式是那幾行。

oprofile

oprofile 也不難用, 但在 VM 裡會有些問題, 且無法發揮完整功能。解決的方法是在啟動 oprofile daemon 前就執行 sudo modprobe oprofile timer=1。若有先執行過 oprofile, 即使照著上面說的執行 --deinit, 結果在執行 modprobe 時會卡住。用 strace 看的結果, 像在 busy-waiting oprofile 結束。重開機後, 在執行 oprofile 前先執行 modprobe 就 ok。

小結相關指令如下:

  • sudo modprobe oprofile timer=1 # 可配合 dmesg | grep oprofile 看目前是否有用 timer interrupt
  • sudo opcontrol --start --verbose --no-vmlinux # 見後面的說明
  • sudo opcontrol --reset # 將記錄歸零
  • sudo opcontrol --dump # 將記錄寫到硬碟
  • opreport # 顯示整體摘要
  • opreport -l PROGRAM # 顯示 PROGRAM 的取樣情況
  • opreport -l PROGRAM -g # 加上原始碼位置和行號
  • sudo opcontrol --shutdown # 結束 daemon

啟動 daemon 後, 之後就是一直

  • ./PROG
  • sudo opcontrol --dump
  • opreport -l ./PROG
  • sudo opcontrol --reset

但是在沒有 vmlinux 的情況, 會看到有大部份時間花在 no-vmlinux (kernel 裡)。所以除非自己寫的部份真的相當忙, 幫助也是有限。我在 Ubuntu 上找了一陣子, 有文章提到裝別人弄好的 vmlinux, 不過沒有成功, 就先忽略這件事了。

對照 perfoprofile 反應的數據, 兩者各有一塊不明的部份占去最多時間, 其它項目的相對大小很接近, 多少算是提示。但我會懷疑數據反應出的實際影響。同一個函式, perf 可能說 20%, 但 oprofile 說 15%, 結果我也無法明白它們實際的執行時間有多長。

GooglePerformanceTools

gperftools 看起來也滿不錯的 --- 直到我抓下來看到 INSTALL 裡對於 x86-64 的注意事項後。於是我決定之後再來試用它, 至少先玩過 tcmalloc 後, 再回來看這個吧。

2012年6月11日 星期一

注意 ptrace 的權限設定

gdb、strace 都是用 ptrace 來監控另一個 process 的情況, 經 wens 提醒關於權限設定的事。

基於安全考量, Ubuntu 10.10 開始, 即使 effective uid 相同, 預設只能對後代 process 用 ptrace。其它情況得用 root 權限執行才可以。在自己的開發機上, 為了方便起見, 可以取消這個限制:

  • 更改目前設定: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope sysctl
  • 開機後自動生效: 改 /etc/sysctl.d/10-ptrace.conf

2012年5月26日 星期六

atexit() 和 exit() 的注意事項

有時候程式不正常結束時, 希望透過 atexit() 最做低限度的善後程續。但試用的結果是, atexit() 本身滿好用的, 正常結束後會符合預期做事。但是呼叫 exit() 隱藏的問題, 卻不那麼直覺。

目前遇過兩個狀況:

  • 在 signal handler 內使用 exit(): 想在收到異常通知時透過 exit() 善後。結果可能會讓程式卡住掛不掉。這也許和註冊的 callback 做什麼事有關。不過 exit() 不是 async-signal-safe functions (見 man 7 signal), 文件也提到在 signal 內用非 async-signal-safe functions 的行為是未定義的, 所以這種狀況出包也沒什麼好探究的了。
  • multi-thread 的處理: 註冊的 callback 裡有和 thread 相關的善後機制, 然後在錯的 thread 裡或錯的時機呼叫 exit() 造成 dead-lock。當狀況異常想呼叫 exit() 時, 其實也沒太多心力將 thread 之間的行為弄得更好。

另外關於 _exit(), man page 提到它不會呼叫 callback, 但有一些 implementation dependent 的行為, 感覺上不怎麼踏實。可以確定的是, _exit() 會關掉 file descriptors 而可能造成未知的延遲 (文件又提到可用 tcflush 避免這點, 但看不懂到底是有效還是 implementation dependent)。所以若要確保程式會立即結束, 用 abort() 或 kill(getpid(), SIGKILL) 應該比較穩吧, 只是就無法提供 exit status 了。

Android 的 audio delay

遇到這問題後上網查了一下, 結果發現哀鴻遍野, 許多人提到 audio 從播放到真的出來, 有 500ms 的延遲。我以為聲音播放下去應該要立即出來的, 沒想到有這麼嚴重的延遲。

目前沒有對策, 只好從一堆討論中, 挑了幾個比較有代表性的連結備忘:

官方 ticket

其它來源

這兩篇是 2011 年底寫的, 提到最佳 Android 裝置也有 100ms 的延遲, 並且有附聲音和影片展示 audio 延遲是怎麼一回事 (*1)。

這個站提供測試軟體和各裝置測出來的數據, 這裡有人提到運作的原理是播出聲音後用內建麥克風錄回聲音, 藉此算出 audio delay。以這麼高的延遲時間來說, 應該可忽略計時中間的一些 overhead。我用 Galaxy Nexus 測出來的數據會在 200ms ~ 500ms 之間跳來跳去。

備註

*1 可怕的是, 我習慣這些延遲行為後, 竟然覺得第二個影片的展示感覺還好。這或許也可解釋開發者感受到的問題嚴重程度, 有時不如使用者高, 因為開發者已習慣這樣的表現, 或潛意識覺得這很難處理, 因此反應比較遲頓。

2012-05-26 更新

ScottG+ 上回了不少相關知識, 貼到這裡備忘:

(後退一步,講些對你解問題無立即幫助的觀察)

1. 在簡單的 interrupt driven PCM samples 播放運作中,系統 wakeup 次數與 audio delay 成反比。故增加 audio latency 可省電。

2. 漂亮的解法是應用可以隨需要將系統調成 interrupt 與 polling mode,且 polling 時可調整 timer interval 的觀念,寫一個『latency 需求低時省電,有 app 需要 low latency 時才常 wakeup 以達成 low latency』的系統,如:http://0pointer.de/blog/projects/pulse-glitch-free.html

3. 這是 Android 將 Linux userspace 全部砍掉重練的後遺症,desktop Linux 反而無『latency 與 power consumption traodeoff』問題:http://arunraghavan.net/2012/01/pulseaudio-vs-audioflinger-fight/

Apple 在 OSX 原本就有 low latency / soft realtime 的 audio API 所以 iOS 也比較了解、有顧到這邊的需求。

4. 各個 Android 裝置 audio delay 會不同是因為 audio codec 晶片廠商與手機系統廠有在測『播音樂、電影時系統電池可撐多久』。且 Android 靠近 audio device driver 這部份的架構讓他們可以微調,但這些廠商隨手也寫不出 timer based audio scheduling,軟體架構上鼓勵他們改的也只是『專屬於某硬體』的部份。

2013-07-13

情況總算有些進展, 見 Google I/O 2013 - High Performance Audio 了解細節。

iPhone 網路緩慢的原因

最近測一些網路連線的東西, 發覺 iPhone 4S 的行為硬是和各版的 iPad 不同, 照理說 iPhone 4S 比 iPad 新這麼多, 應該表現要更好才對。最後懷疑是無線晶片的問題。查了一下, 發覺不少人抱怨 iPhone 的網路問題, 甚至有人的情況是 Wi-Fi 的表現比 3G 糟。

去除 iOS 裝置的無線晶片良率問題 (*1), 原因大致上有兩種

  • 網路晶片的參數和無線 AP 有衝突, 修改 AP 參數後可改善
  • 網路晶片有省電功能, 會視負載率調整效能

第一點我沒打算嘗試, 從 app 的開發者來說, 就算我的 iPhone 因此變快但使用者的不會自動受惠, 也沒什麼意義。針對第二點做了些實驗, 發覺 MacBookAir、iPad 各型和 iPhone 4 和 4S 都有省電功能, 只是省電的作法有差, iPhone 的效能相對地不穩。

我的測試方法是

  • 下載 Ping 之類的 free app (我用 Network Ping Lite), 用 iOS 裝置 ping 區網或外面的機器, 比較 round-trip time。結果 iPhone 4S 可到 >400ms, 但桌機 <200ms。
  • 由 Network Ping Lite 得知 iOS 裝置的 IP, 用桌機 ping 在同一個區網下的 iOS 裝置。比較閒置時的 round-trip time 和使用 YouTube app 時的情況。結果在閒置時也是有較高的 rtt, 到幾百 ms 都有。但大量使用網路時, 表現符合預期, rtt <5ms, 只是偶而會跳出一個上百 ms。

從 OS 角度來看, 網路用得愈兇網路會愈順, 可讓電力用在刀口上。不過從開發者的角度來看, 這表示若希望一直保有 low latency, 要盡可能持續用網路; 或著, app 必須設計成可容許偶而的 low latency。這兩個選項還挺極端的, 不知系統有沒有可能提供選項可建議放寬網路省電機制。

參考資料

PS

* 我強烈懷疑 iOS 的無線晶片良率有問題。我第一個借來玩的 iPad, 它的 Wi-Fi 很不穩, 於是就沒什麼用它。再沒多久, 它變成根本連不上了, 但樣情況下用 G1 沒有問題。後來又試了別台 iPad 和 iPad 2, 一樣是 AP 放客廳, 我在房間裡使用, 結果 iPad 符合預期可以用, 但 iPad 2 在客廳轉角要進房間前就收不到訊號了。

2012年5月2日 星期三

SIGCHLD 和 zombie

關於 zombie

  • zombie: ps 列的 status 為 Z, 表示 process 已結束, 資源也釋放了, 等待 parent process 呼叫 wait/waitpid 回收它的 exit status, 裡面包含 exit code
  • zombie 是無法被 kill 的, 僵屍無敵啊!!
  • zombie 會占據極小的系統資源, 如可 forked 的 process 數
  • 不註冊 SIGCHLD = 註冊 signal handler 為 SIG_IGR => OS 會在 child process 掛掉時直接回收, 不會變 zombie

關於 SIGCHLD 收發時機

  • 即使 child process 收到 SIGKILL, 它也不會當下就掛掉而讓 parent process 在當下收到 SIGCHLD。process 得等取得 CPU 時才會「處理」 SIGKILL 而掛掉。總之, 系統不保證 SIGCHLD 收到的時間
  • child process 正式掛掉後, 當 parent process 取得 CPU 時, 系統會盡快讓它收到 SIGCHLD。換句話說, 若 parent process 被停住的期間「收到」 SIGCHILD, resume 後會立即收到 SIGCHILD
  • SIGCHLD 如同其它 signal, 不會被 queued, 只有保證在有註冊 handler 的情況下, 至少會呼叫一次 handler (細節說明請查: signal mask/block )
  • 註冊 handler 前收到 SIGCHLD 的話, 不同系統作法不同, 可能會補送也可能不會補送

關於 handler

  • signal handler 會被繼承 (見 man fork 開頭), 所以若 A 有註冊 SIGCHLD 則 A 的 child process 和後續 grandchild process 都有 SIGCHLD handler。寫 handler 時要注意是否能沿用下去, 或是明確在每次 fork 後重設一次 child process 的 signal handler
  • 標準寫法如下, status 裡會記錄許多有用資訊, 見 man waitpid:
int status;
while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
    // Do something
}

2012年4月8日 星期日

使用 vim script 協助閱讀程式碼

開啟多個 window 顯示各個符合搜尋結果的位置

在讀原始碼時, 我常常想知道某個變數或函式, 在這個檔案裡被用在那些地方, 而且希望在 vim 裡一次開多個 window, 每個 window 顯示一個位置, 方便同時觀看。若能一併尋找其它檔案更好, 不過那會難許多, 第一步先在同一檔案做到這點, 應該就很有幫助。

針對這個需求, 順便研究一下寫 vim function 需要知道的一些基本知識, 寫好的東西見 ShowMatched(pattern) 相關的程式

以編輯 eglibc 的 stdlib/setenv.c 為例, 將游標移到 LOCK 後, 按下 \f, 會開個新 tab, 並呈現如下的畫面:

\f 最多會開兩個 tab, 一個 tab 最多六個 window, 待試用一陣子後再來調整它的行為。可用 :wind q 關掉目前 tab 內所有 window。由於很常用到這功能, 我會設個巨集做這件事:

map qq :q      # 關掉目前 window
map qa :wind q # 關掉目前 tab 的全部 window

vim script

在這裡整理一下寫 vim script 時需要知道的基本常識。

基本上 script 的每一行如同在 ex mode 執行:

g/^$/d  # 等同於 :g/^$/d, 砍掉空行

若需要使用 normal mode 的指令, 要配合使用指令 normal:

normal ggdd  # 砍掉第一行
execute "normal \<c-w>l"  # 切到右邊的 window

若需要讀取指令的輸出結果, 可用 redir 導到 register:

redir @a
:g/printf/p
reidr END
put a  # 將搜到 printf 的顯示結果寫到目前的檔案裡

若在導到 register 時, 不希望同時輸出到螢幕, 得將指令先包在函式裡, 再用 silent call FUNCTION:

silent call MyFunc

知道以上幾件事後, 剩下的就是熟悉語法、找些小範例、查查內建的函式, 就可以延伸原本熟悉的 vim 指令成為 script:

如此一來, 不用花多少力氣, 即可讓 vim 的威力增加數倍。

2012年4月6日 星期五

Gtk+ 入門 (2)

上篇提到一點皮毛, 又寫了一陣子 Gtk+ 後, 筆記一下其它入門心得。

Gtk+ multi-thread

GDK Reference Manual - ThreadsIs GTK+ thread safe? How do I write multi-threaded GTK+ applications?Multi-threaded GTK applications – Part 1: Misconceptions - Operations and other mysteries

心得如下:

  • 要在 gtk_init() 以前呼叫 g_thread_init(NULL) gdk_threads_init()
  • 總之就是在 main thread 裡做事, 若真的萬不得已想在 main thread 以外做事, 記得先呼叫 gdk_threads_enter(), 結束時再呼叫 gdk_threads_enter(), 這會取得和釋放 GDK 的 global lock。
  • callback 不需呼叫 gdk_threads_..., 因為在進 callback 前後會呼叫。
  • 注意 GDK 的 global lock 沒有加上 PTHREAD_MUTEX_RECURSIVE, 所以在 callback 裡呼叫 gdk_threads_enter() 會 deadlock。有 PTHREAD_MUTEX_RECURSIVE 會方便許多, 不確定 Gtk+ 這樣實作的考量為何。

另外我觀察 Gtk+ 的執行狀態, 平時就是兩個 thread, 一個是 main thread, 另一個大概是負責收 UI event, 而有任何 UI event, callback 會在 main thread 裡執行。雖說是廢話, 不過自己觀察過後感受更踏實。

上述提的 multi-thread, 是指有再另開 thread, 也有用到 UI 裡的東西時, 才需注意的事項。

Tutorial

The GTK+ programming tutorial 超級實用, 每篇文章精簡易讀, 附有簡短完整範例, 並且有 screenshot 可參考, 快速掃過一遍就能找到很多好東西。剩下不清楚的部份, 可用在 devhelp 查詢關鍵函式, 或 google 關鍵函式, 再連到 GTK+ 2 Reference Manual, 可以搞定多數情況。

devhelp 可透過 aptitude install devhelp 安裝。

2012年3月26日 星期一

Effective C++ item 31: 將檔案間的編譯依存關係降至最低

C++ 相較於 Java, 可以直接在 stack 上配置物件, 而不是全部都是 pointer (reference), 因此, C++ 要求在 include class 時, 得知道該 class 的所有 member field, 才知道要配多大的空間。這帶來一個問題, 即使 header 檔改的是 private member field/method, 全部有引入該 header 的檔案都要重編, 專案變大後, 是頗為痛苦的事。

解決這問題有幾種作法, 基本精神就是架空 class, 讓它不會有更動 private member field/method 的機會。書上提出兩個作法:

  • 使用 handle class, 或稱 pimpl idiom (pimpl: pointer to implementation)
  • 使用 interface class (abstract base class)

以 class Person 為例, 第一個作法是在 Person 裡加上一個 private member: PersonImpl *pImpl, 然後所有方法都透過它執行, 例如:

std:string Person::name() const
{
    return pImpl->name();
}

interface class 則是全部方法都宣告為 virtual 並不提供實作 (別忘了提供 virtual destructor), 這樣 Person 不能初始化, 效果如同 Java 的 interface (不過有彈性可提供 method 和 field), 像這樣:

class Person
{
public:
    virtual ~Person();
    virtual std::string name() const = 0;
    ...
}

然後再寫個 class RealPerson 繼承 Person, 提供真正的實作。

於是, 不管用 handle class 或 interface class, 只要沒有加減 interface 的 method, 相依 Person 的程式都不需重編, 邏輯上也做到令用戶端程式相依於介面而非實作, 達到良好的封裝效果。當然, 相對於直接實作 class Person, 兩者的代價都是要多花一小點記憶體, 還有呼叫函式時多一點成本, 也失去 inline 最佳化的機會。但當程式愈寫愈大後, 這些代價都是值得的, 必要時再針對瓶頸最佳化較划算。

書上還提到其它的小細節, 像是將 header 拆成 X.h 和 Xfwd.h, 讓用戶端 include Xfwd.h, 然後各自再 include 會用到的實作定義。有些 class 有一長串 method, 各 method 用到各種不同 class (比方說用到 name() 的就要 include string 的 header), 但不是每個用戶端都會呼叫到這些 method, 自然也沒必要相依那些 method 傳入或傳回的 class 的 header。

2012年3月25日 星期日

vim 快速開啟 .c / .cpp / .h 的相對檔案

搜尋這個需求的話, 會看到網路上一個廣為流傳的版本:

map <f4> :p,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp,

開 .cpp 後按 F4 就可以開啟同目錄下的 .h; 反之亦然。只要一行就搞定, 沒有 if-else, 頗為神奇的。

但是有時候我開 .cc 或 .c 時, 就沒辦法叫出 .h 檔, 所以剛才花了點時間研究一下, 才搞懂它怎麼做的。

修改後的版本如下, 這個版本在 .cpp / .c / .cc / .C 檔案裡按 F4 會開 .h; 在 .h 裡開 F4 會開 .cpp:

map <F4> :vs %:s#\.cpp$#.XY_CPP_XY#:s#\.h$#.cpp#:s#.XY_CPP_XY#.h#:s#\.cc$#.h#:s#\.[cC]$#.h#<CR>

% 表示目前的檔名, 在 vim 打 :help :%s, 可看到 :%s 的用處, 它是用來取代檔名的指令, 功能同 s/.../.../, 特別的是, 它可以重覆使用。

原作者的巧思在於, 先將 .h 轉成一個大概不會出現的字串 (.X123X), 然後放心的將 .cpp 轉為 .h, 再將那個不會出現的字串轉回 .cpp。由於是字串代換, 代換的目標沒有出現, 也不會有不良影響。這裡的順序很重要, 替代字串的順序對的話, 就可應付各種情況。

所以要支援其它副檔名轉為 .h, 只要在後面直接多加 :s 即可。其它就只是細部小修改, 沒改也沒什麼影響。

2012年3月14日 星期三

執行檔和檔案路徑注意事項

daemon 執行的時候, 通常會在程式裡 chdir 到 / 再開始做事, 藉此避免之後 daemon 所在的目錄無法被砍掉 (即使從檔案系統上下指令砍掉該目錄, 實際上它仍存在, 要等 daemon 結束才會真的釋放)。

程式執行中若需要寫入一些暫存檔, 要考慮到權限問題, 可以寫到 /tmp 最省事, 若需要永久保留, 考慮寫到 $HOME/.PROG/ 下, 不要直接寫到執行檔目錄下。平時開發寫到執行檔目錄下沒有問題, 但當需要將程式打包成套件, 裝到系統目錄時 (如 /usr/bin、/usr/local/bin), 就會有權限問題而無法寫入。

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.

2012年1月31日 星期二

gdb: 維持永久的 watchpoint

若想觀察特定變數的變化, watchpoint 比 breakpoint 易於使用。但 watchpoint 有個小問題, 一但離開 scope 後, gdb 會自動刪掉 watchpoint

若該變數是 member field, 就無法持續追踪不同 method 怎麼讀取或寫入它的值。這篇提供一個小技巧, 取用目標變數的指標, 再觀察指標取值的變數, 藉此讓 gdb 認定它不是區域變數, 借用該文的例子如下:

(gdb) p &var1
$1 = (int *) 0x41523c0
(gdb) watch *(int *)0x41523c0
Hardware watchpoint 1: *(int *)0x41523c0

若真的是區域變數的話, 可用 commands 定義簡短的指令, 之後進入函式時, 自動加回 watchpoint, 借用該文提供的例子:

(gdb) break func
(gdb) commands
>   watch var
>   continue
> end

commands 看來很方便, 還有許多適合應用的情境, 像是追踪網路連線輸出關鍵資訊, 可避免手動操作太久造成 timeout。

2012-02-04 更新

gdb 7.4+ 支援 watch -l, 就不用自己取位置再設 watchpoint 解套。在 7.4 之前想用這功能, 可使用 Scott 寫的 gdb-watch-location.py。用法是

$ wget https://raw.github.com/scottt/scottt-gdb/master/gdb-watch-location.py
$ gdb -x gdb-watch-location.py PROG

在 gdb 裡會新增幾個指令: watch-l、rwatch-l、awatch-l 對應到 watch -l、rwatch -l、awatch -l。

2012年1月29日 星期日

讀懂函式庫的 man page

初學 Linux 時, 覺得 man page 真不是寫給一般人看的, 到像是寫給已經懂的人備忘用的。即使現在已比較習慣讀 man page 了, 還是這麼覺得。

以前只知道 man function 看要引入什麼 header, 了解函式的參數、傳回值。最近才明白裡面的其它資訊。

背景知識可以搜尋 "how to read man page" 或看 Wikipedia 介紹得知。這裡只針對使用函式庫講基礎知識。另外這裡有顯示彩色 man page 的設定

man 3 sqrt 為例:

SQRT(3)                                          Linux Programmer's Manual                                         SQRT(3)

NAME
       sqrt, sqrtf, sqrtl - square root function

SYNOPSIS
       #include <math.h>

       double sqrt(double x);
       float sqrtf(float x);
       long double sqrtl(long double x);

       Link with -lm.

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sqrtf(), sqrtl():
           _BSD_SOURCE || _SVID_SOURCE || _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L;
           or cc -std=c99
  • #include 表示需要的 header
  • 下面有函式的 signature
  • Link with -lm 表示使用 gcc 編譯時要加 -lm, ld 才會找到 libm.so。詳細的運作過程見《ld, ld.so 和 ldconfig 的行為》
  • Feature Test Macro 是 UNIX 跨平台用的一套規範, 見 man feature_test_macros 了解 feature test macro 的作用, 以及 glibc 認得的類型。feature test macro 要定義在檔案的一開頭才行, 或用 gcc -D 定義。

關於 feature test macro, 這裡指出要定義 _BSD_SOURCE、...、_XOPEN_SOURCE 等, 才能使用 sqrtf() 和 sqrtl() (math.h 才會引入它們的宣告}}}。反之, sqrt() 有跨所有平台, 可直接使用。

這裡這裡有相關說明, 解釋 -std=c99-std=gnu99 的效果, 會定義不同的 feature test macro。若沒有考慮可攜性的話, 用 c99 編不過時改用 gnu99 可編過, 不用擔心會有什麼副作用。

所以, 要使用特定函式時, 要先看 man FUNCTION 了解需要定義的 feature test macro, 讓 compiler 可以編過。再來從 man FUNCTION 得知 static linking 需要的參數。

要檢查目前程式定義了那些 feature test macro, 可以用:

gcc -E -dD MYPROG.c

除查看 feature test macro 外, 用來查引入的常數也很方便, 比方說忘了 stdin 的 file number 的常數, 引入 unistd.h 後, 用 gcc 展開看一看, 就會找到#define STDIN_FILENO 0

使用 RTLD_NEXT 實作 wrapper

先貼程式:

// RLTD_NEXT is only supported in _GNU_SOURCE.
#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>

void *malloc(size_t size) {
    void* (*m)(size_t);
    // 「標準」取得 function pointer 的寫法. 見 TLPI 42.1.2 p863 的說明
    *(void**)(&m) = dlsym(RTLD_NEXT, "malloc");
    printf("local malloc\n");
    return m(size);
}

int main(void) {
    int *t = malloc(sizeof(int));
    *t = 3;
    printf("%d\n", *t);
    return 0;
}

編譯和執行結果:

$ gcc m.c -ldl -o m
$ nm m | grep malloc
0000000000400604 T malloc
$ ./m
local malloc
3

若去掉 malloc(), 加上 #include stdlib.h, 用 nm 看則是:

$ nm m | grep malloc
                 U malloc@@GLIBC_2.2.5

表示在 m 裡面沒有定義 malloc, 它存在 glibc 裡。malloc 後面的 "@@GLIBC_2.2.5" 是 version tag。當 shared library 裡有多個版本 malloc 時, 可用 version tag 來區別要用的是那一個 malloc, 詳情見 TLPI 42.3.2。

這裡的關鍵是 glibc 定義了擴充功能 — 假的 library handle: RTLD_DEFAULT 和 RTLD_NEXT。用前者取函式 (變數) 就和原本 ld.so 找 symbol 的方式一樣; 而後者則會找「下一個」, 這是針對實作 wrapper 的需求而定的。

由於是 glibc 的擴充, 編譯時要在開頭加上 #define _GNU_SOURCE 或在命令列加 -D_GNU_SOURCE, dlfcn.h 才會載入 RTLD_NEXT。關於 _GNU_SOURCE 的說明, 見《讀懂函式庫的 man page》

ld, ld.so 和 ldconfig 的行為

TLPI ch41 相當值得一看, 從開發者使用 library 的角度說明 library 的生成、靜態連結、動態連結 (載入) 的行為, 內容不多不少, 正好就是我想知道的, 省了看 linker、loader 的時間。

shared library 的名詞介紹

  • soname: 記錄在 shared library header 裡的名稱, 格式為 libX.so.MAJOR。要有同名檔案, 供之後程式載入 shared library 時使用
  • real name: shared library 的檔名, 格式為 libX.so.MAJOR.MINOR.NUMBER
  • linker name: 對 library X 來說, 就是 libX.so, 一般會是 symbolic link 指向最新的 major shared library

以 libjpeg 為例, 對應如下:

libjpeg.so -> libjpeg.so.62.0.0     # linker name
libjpeg.so.62 -> libjpeg.so.62.0.0  # soname
libjpeg.so.62.0.0                   # real name

這是我在 Ubuntu 裝好 package 後的樣子, 照理說 libjpeg.so 指向 libjpeg.so.62 應該會更彈性。

讀出 soname:

$ readelf -d libjpeg.so | grep SONAME
 0x000000000000000e (SONAME)             Library soname: [libjpeg.so.62]

static 和 dynamic linker

  • ld (ld.bfd) 是 static linker。Google 開發的 gold 是取代 ld.bfd 的 static linker。用 gcc 連結 shared library 或 executable 時就是呼叫 ld, 並將需要的參數傳給它。不論連結的是 static library 或 shared library, 都是 static linking。
  • ld 在連結 shared library 或 executable 時, 會將需要的 shared library 的 soname 寫入結果檔裡。注意, 只有 soname 而已, 沒有完整路徑。
  • ld-VERSION.so 是 dynamic (runtime) linker, 執行程式時, 由 runtime linker 載入 executable 開始。若 OS 用的 glibc 版本為 2.13, 就叫 ld-2.13.so。用 ldd 看所有執行檔, 都會找到它 (某個 symbolic link 連到 ld-2.13.so)。

以連結 libm.so 為例, 執行 gcc -lm prog.c -o prog 中間的部份行為如下:

  1. gcc 透過 -lm 的指示告知 ld 要連結 libm.so
  2. ld 會找到某處的 libm.so 指向 /lib/x86_64-linux-gnu/libm.so.6, 確認要用到的 symbol 都有, 沒有 link error
  3. ld 從 libm.so.6 的 header 讀出 soname "libm.so.6", 寫入 "libm.so.6" 到 prog 的 header。

執行 prog 時, ld-2.13.so 會從 prog 讀出 "libm.so.6", 再到預設的路徑上找檔名 "libm.so.6"。注意, static linking 時需要 libm.so, 但之後執行 prog 時用不到它, 因為記錄的 soname 為 "libm.so.6"。

關於 static linking 找檔名的順序, 可用 strace 觀察:

$ strace -e open,execve -f -o gcc.trace gcc -lm prog.c -o prog

在 gcc.trace 裡可看出一二。

ps.

  • 使用 execve 的目的是知道 child process 是那一個程式, 目標是看 ld 開敋的檔案
  • 可由 man 2 exec<TAB> 得知 system call 使用的 exec 函式為 execve。

ldconfig

執行 ldconfig 後, 它做的事如下:

  1. 讀出 /lib, /usr/lib, /etc/ld.so.conf 內的路徑之下的 shared library (ldconfig 會略過 symbolic link), 將結果寫入 /etc/ld.so.cache。之後 ld-2.13.so 會用 ld.so.cache 的記錄來找 shared library。
  2. ldconfig 會自動產生 symbolic link "libX.so.MAJOR" 指向最新版本的 shared library。例如 /lib/libfoo.so.2.0.1 的 soname 是 libbar.so.2, 執行 ldconfig 後, 它會產生 /lib/libbar.so.2 指向 /lib/libfoo.so.2.0.1。

之前困擾我許久的事就是第二步, 而 man ldconfig 裡沒提到這點。

結論是別隨便手動更新 soname 的檔案, 執行 ldconfig 後可能會出問題。裝套件後, 系統工具會自動跑 ldconfig 更新目錄, 可能會蓋掉自己手動更新的同檔名檔案。另外 ldconfig 沒有管 linker name, 若是自己編的 shared library, 要自己產生。

其它

若想連到舊的 major 版本 shared library, 得在 gcc 參數指定舊版檔名。還有可用 rpath 的參數寫入搜尋 shared library 的路徑到 shared library 或 executable 裡。關於這些細節, 還有 static linker 以及 dynamic linker 尋找 shared library 的完整順序, TLPI ch41 講得相當清楚。ch42 描述 dlopen, 之後再來翻翻。

2015-09-05 更新

加上 Scott 補充關於 ld-VERSION.so 和相關工具的說明:

fcamel: ld-2.21.so, ldd, ldconfig 屬於 glibc 是什麼意思啊? 我以為 glibc 是 standard C + POSIX lib

Scott: Linux 下動態連結器是 C Library 的一部分,例如 Android 不用 GLIBC 所以其 Dynamic Linker (/system/bin/linker64) 是 Google 自己寫出來、自己維護的 https://github.com/android/platform_bionic/blob/master/linker/Android.mk

這個知識可用來解釋 Android 動態連結器有許多「特異功能」,例如有註冊 SIGSEGV handler,所以 native code 記憶體存取錯誤會有 backtrace 等。 https://github.com/android/platform_bionic/blob/master/linker/debugger.cpp

至於動態連結器為何是 C Library 的一部分,從「實作 C library 的人」的角度看:凡不是 Kernel,但又是系統「執行期間」需要的低階 Library,就擺在 C Library 內了。

ldd 與 ldconfig 是 GLIBC 作者寫出 ld-2.21.so 動態連結器時「順便」寫的工具,是隨 GLIBC 一起安裝的。系統不用 GLIBC 的話,可能沒有 ldd 與 ldconfig。

2012年1月27日 星期五

C99 好用的語法

之前只有用到 C99 的 loop initial declarations (在 for 的初始化部份宣告變數), 看 Scott 提到才知道有其它好東西, 順便來掃一下 C99 的功能

stdbool.h

定義 bool、true、false, 實際上是將 bool 對應到 C99 定義 _Bool

stdint.h

定義了整數範圍、int16_t、int32_t、int64_t 等型別, 再也不用查 short/int/long 等在 32/64 bit OS 上的大小為多少。

designated initializers

可攜又易讀的初始化 (ref.)

// array
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
// struct
struct point { int x, y; };
struct point p = { .y = yvalue, .x = xvalue };

像要用建表實作 isspace() 的話, 這樣寫超清楚的:

bool myisspace(int ch) {
    static bool whitespace[256] = {
        [' '] = true, ['\t'] = true, ['\f'] = true,
        ['\n'] = true, ['\r'] = true
    };

    if (ch < 0 || ch >= 256)
        return false;
    return whitespace[ch];
}

其它

像 snprintf、inline、variable-length array (例如 int array[n]) 也很實用。

signal 小知識

TLPI 長知識。

signal 設定和 fork

fork 後會繼承 signal 全部設定, 而 exec 後會保留 ignore 的設定, 但因為 address space 不同, 會將有設過的 signal handler 改回 default handler(見 man 7 signal)。

先送 SIGTERM 再送 SIGKILL 砍程式

若程式有照標準寫, 可能會有 SIGTERM 的 handler, 在收到這 signal 時做些清理動作 (砍暫存檔、釋放資源等), 再自我了結。而 SIGKILL 就直接掛了。所以, 先給人家一個機會掛得優雅一些, 若對方拒絕的話, 再狠一點直接掛了它。

附帶一提, ctrl + c 是送出 SIGINT。

執行 signal handler 的時機

process 得先拿到 CPU 才能執行 signal handler, 換句話說, 若 process P 處於 stop 的狀態 (D、S、T), 或系統太忙撥不出時間給它, 即使已送出 SIGTERM 給 P 了, P 也無法處理。而送 signal 的一方只能由 signal system call 的返回值判斷 P 仍活著, 有成功送出 signal, 無法得知 P 何時能處理它。

特別的是, SIGKILL 一送出對方就掛了, 不用取得 CPU 再掛掉。沒看到有特別的文獻指出 SIGKILL 這點特別的行為。man 7 signal 只有指出「The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored」。就實用角度來看, 讓 SIGKILL、SIGSTOP 立即生效, 確實比較合理。

SIGHUP 的行為

terminal 斷線 (hangup) 時, terminal 的主控 process 會收到 SIGHUP。而它的預設行為是終結程式, 所以關掉 terminal 時, 裡面的程式會直接結束。

bash 和 ksh 在結束的時候會送 SIGHUP 給背景程式, 若背景程式也沒寫 SIGHUP handler, 那它們也會一起結束。這是關掉 terminal, 全部程式會一起結束的原因。

要避免這個行為, 可用 nohup 執行程式, 它會做 I/O 重導、執行 signal (SIGHUP, SIG_IGN), 然後執行目標程式。或是用 bash 的 disown -h, 再放到背景跑, 或用 gdb attach process, 再輸入 handle SIGHUP nopass

用 SIGQUIT 產生 core dump

在 terminal 按 ctrl + \ 時會送出 SIGQUIT 給前景程式, 這個 signal 的作用是要就該程式自我了結並產生 core dump。當然, 一些設定要先設好, 才會有 core dump

這有許多好處:

  • 程式進無窮迴圈時按 ctrl + \, 再用 gdb 列 backtrace 看卡在那。雖說直接 用 gdb attach 也 ok。若高中練 ACM 知道這個方法, 可省下一些除錯時間 (遠目)。
  • 方便測試 core dump 設定, 不用另寫個小程式故意寫入不合法的位置。

2012年1月23日 星期一

Linux 的 capability

昨天看 TLPI ch39 才知道 Linux 有 capability 可依項目授權, 不用像以前那樣, 使用 setuid 一次大放送。像 passwd 就是 setuid 的典型使用情境, 要讓所有使用者能改自己的密碼, 所以他們要有權限存取密碼檔, 但又不能讓使用者做超出他們該做的事。於是, 使用 setuid 的程式需要很小心地設計, 避免給太多權限, 或是被 cracker 攻破取得 root 權限。

看到 capability 如此地威, 不禁納悶有多少程式有使用 capability, Scott 提供一個不錯的找法: 用 package 管理套件找出有那些程式依賴 capability 的套件, 就知道了。Ubuntu 對應的指令是 apt-rdepends -r libcap2, 也不算少, 有 100 多個套件用到 libcap2。

用 strace 找出 Ubuntu 如何提示未安裝的指令

在 Ubuntu 下執行指令後, 若沒有安裝指令的話, 會出現提示:

$ apt-rdepends
The program 'apt-rdepends' is currently not installed.  You can install it by typing:
sudo apt-get install apt-rdepends

但若直接用 bash 執行, 卻不會有這效果:

$ bash -c apt-rdepends
bash: apt-rdepends: command not found

以前覺得很好奇, Ubuntu 怎麼做到這件事的, 知道 strace 以後, 追這類原因簡單許多, 只要有輸入和輸出訊息, 就可夾擊出一些線索。

  • 在 terminal 1 輸入 echo $$ 取得該 bash 的 PID
  • 在 terminal 2 輸入 sudo strace -obash.trace -f -s512 -p PID
    • -obash.trace 表示將輸出存到 bash.trace, 訊息很多, 通常都會寫到檔案裡
    • -s512 表示輸出訊息最多到 512, 預設行寬有點短, 之後不方便找輸出的訊息
    • -f 表示一起追蹤 child process, 這點很重要, 沒加 -f 就追不到 bash 使用的其它子程序, 而關鍵就在 bash 叫起的子程序
    • -p PID 表示追踪其它 process, 照理說同一個使用者不用 root 權限應該也能看, 不知為啥不通
  • 在 terminal 1 輸入 apt-rdepends, 因為 strace 有用 "-f", 速度會慢很多。等待指令完成
  • 在 terminal 2 按 Ctrl+C 中斷 strace, 觀察 bash.trace

搜一下 "apt-rdepends" 會看到 bash 在嘗試各種 path 後都找不到檔案:

16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/local/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/local/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/sbin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/usr/games/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...
16520 stat("/home/fcamel/bin/apt-rdepends", 0x...) = -1 ENOENT...

之後在別的 child process 呼叫外部程式執行 /usr/lib/command-not-found:

16877 execve("/usr/bin/python", ["/usr/bin/python", "/usr/lib/command-not-found", "--", "apt-rdepends"], [/* 38 vars */]) = 0

若想研究怎麼找出該裝的套件, 可以研究 "/usr/lib/command-not-found"。若想研究 bash 如何判斷在有 terminal 的情況下要多找安裝的指令, 可以自己編含 debug symbol 的 bash, 再用 gdb 找出相關位置, 再讀附近的原始碼。這樣一來, 至少知道需要追的時候該如何進行, 剩下就是增加經驗提昇追程式的速度了。

2012-01-23 更新

wens 提醒, 原來是用 bash 的 hook 做的, 見 /etc/bash.bashrc 了解設定, man bash 在 COMMAND EXECUTION 那節有說明。

2012年1月22日 星期日

gdb 如何找到 debug symbol

先前在《追踪 glibc 裡的程式》提到自己如何亂試, 試出讓 gdb 讀到 debug symbol。昨天聽 Scott 說明, 才知道背後是怎麼一回事。

在 Ubuntu 下以 libm 為例, 在 /lib/x86_64-linux-gnu/libm-2.13.so 裡面, 先看一些相關的 section header:

$ objdump -h /lib/x86_64-linux-gnu/libm-2.13.so | grep gnu
/lib/x86_64-linux-gnu/libm-2.13.so:     file format elf64-x86-64
  0 .note.gnu.build-id 00000024  0000000000000238  0000000000000238  00000238  2**2
  2 .gnu.hash     00000fa4  0000000000000280  0000000000000280  00000280  2**3
  5 .gnu.version  00000298  00000000000038da  00000000000038da  000038da  2**1
  6 .gnu.version_d 0000005c  0000000000003b78  0000000000003b78  00003b78  2**3
  7 .gnu.version_r 00000030  0000000000003bd8  0000000000003bd8  00003bd8  2**3
 27 .gnu_debuglink 00000014  0000000000000000  0000000000000000  000840e4  2**0

幾個重點

  • .note.gnu.build-id 表示 binary id, 之後用來比對 debug symbol 是否出自 shared lib。看起來 Fedora 在找 debug symbol 時, 有用到 binary id; 而 Ubuntu 沒有的樣子, 我用 hexedit 亂改這個 section 的值, 仍能找到 debug symbol
  • .gnu_debuglink 指向包含 debug symbol 的檔案, 若用 hexedit 改掉它的值, 執行 gdb /lib/x86_64-linux-gnu/libm-2.13.so, gdb 會表示找不到 libm 的 debug symbol
  • 可用 objdump -s -j .gnu_debuglink /lib/x86_64-linux-gnu/libm-2.13.so 顯示 section 內容

若用 LD_PRELOAD=/usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so ANY_PROGRAM 執行程式, 結果會 segmentation fault, 所以我推測 Ubuntu 下 X-dbg 裡包的檔案, 可能和 Fedora 一樣, 只有 debug symbol 而不是完整 strip 前的函式庫。不知要如何確認該檔案裡只有 debug symbol 沒有實際的 object code。

至於確認原本的 binary (object file / shared lib / executable) 是否有編入 debug symbol, 除了用 objdump -S 再找看看有沒有出現程式碼外, 更簡單的作法是用 objdump -h | grep debug:

$ objdump -h /usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so | grep debug
/usr/lib/debug/lib/x86_64-linux-gnu/libm-2.13.so:     file format elf64-x86-64
 28 .debug_aranges 00004770  0000000000000000  0000000000000000  000002b0  2**4
 29 .debug_pubnames 00002e44  0000000000000000  0000000000000000  00004a20  2**0
 30 .debug_info   000318ee  0000000000000000  0000000000000000  00007864  2**0
 31 .debug_abbrev 00010fc3  0000000000000000  0000000000000000  00039152  2**0
 32 .debug_line   00018c20  0000000000000000  0000000000000000  0004a115  2**0
 33 .debug_str    000041bc  0000000000000000  0000000000000000  00062d35  2**0
 34 .debug_loc    00062bc5  0000000000000000  0000000000000000  00066ef1  2**0
 35 .debug_pubtypes 00003a11  0000000000000000  0000000000000000  000c9ab6  2**0
 36 .debug_ranges 00003e30  0000000000000000  0000000000000000  000cd4c7  2**0

有上述 section 的話, 表示有含 debug symbols。

備註

1. hexedit 基本指令

  • F1: 等同於 man hexedit
  • F4: 跳到 offset, 對照 objdump -h X 看倒數第二欄使用
  • TAB: 切換 hexadecimal 或 ascii 區, 之後取代內容或搜尋, 和這有關
  • 直接在 byte 上打字取代
  • /: 找字串
  • ctrl+c / ctrl+x: 離開 / 存檔離開

2. 見《The DWARF Debugging Standard》了解 debug 資訊如何存在檔案, 只是留著備忘, 目前應該沒必要去讀。

3. 若是自己編含 debug symbol 的函式庫, 就不是上述那一回事了, 而是直接編進目前的函式庫裡。

4. 《Separate Debug Files - Debugging with GDB》說明 gdb 如何支援分離 debug symbol 到另一個檔案, 另外 man strip 或 man objcopy, 可在 "--only-keep-debug" 的部份看到相關說明。看來要知道到底各個 distribution 怎麼做這事, 去看該 distribution 官方的說明會比較確實。之後再看看吧。

2012年1月19日 星期四

iPad 升級到 iOS 5

不知是否因為和 command 一樣人品不好, 用 Apple 的產品總遇到一堆鳥事, 讓我對 Apple 產品難以建立好感。

照官網和 google 大部份人的說法來看, 就 iTunes 升到 10.5, 將 iPad 接上去, 在 iTunes 裡按 check for update 即可。但 iTunes 回報 This version of the iPad software (4.2) is the current version

有人提到可嘗試手動升級, 下載好 firmware 檔後, 照著指示手動選檔案 restore, 結果出現 "this device isn't eligible for the requested build"。google 看到有許多人提到類似問題, 但有些是 jail break 造成的, 提到的解法看來也不適用於我的情況, 況且我用的 iPad 應該沒 JB。

最後在 restore mode 下, 成功地升到 iOS 5.x 了, 太感謝這個影片, 操作方式講解得非常清楚: 《How to Enter DFU Mode | Restore Mode - Get Out of DFU Mode | Restore Mode - Fast, Safe & Easy - YouTube》

2012年1月18日 星期三

自行編譯含 debug symbol 的套件 (package)

對函式庫 X 來說

  • X-dev 表示讓開發者用的, 有裝 header、文件之類的
  • X-dbg 內容類似 X, 不過有留 debug symbol

比方說 libjpeg, 三種的描述如下:

  • libjpeg62 - The Independent JPEG Group's JPEG runtime library
  • libjpeg62-dbg - Development files for the IJG JPEG library
  • libjpeg62-dev - Development files for the IJG JPEG library

裝了 X-dbg 後什麼事也不用做, gdb 自己會優先用含 debug symbol 的版本。不過若官方沒提供供 X-dbg 的話, 就得自己編。參考官方文件《HowToGetABacktrace - Debian Wiki》, 做法如下:

  • $ apt-get install build-essential fakeroot gdb
  • $ apt-get build-dep X
  • $ DEB_BUILD_OPTIONS="nostrip noopt" fakeroot apt-get -b source X
  • $ dpkg -i X.deb

針對上述指令, 補充幾點

  • 編好套件後, 可用 dpkg -c X.deb 先看裡面裝了什麼
  • 用 objdump --source FILE 查看裡面有沒有含程式碼, 有的話才表示確實有含 debug symbol。這個作法比用 file FILE 看是否有 strip 更確實。二進位檔有可能沒含 debug symbol 也沒 strip。

2016/02/16 更新

遇到 "trying to overwrite ..." 的錯誤 時,可以用 sudo dpkg -i --force-overwrite X.deb 強制安裝。

2012年1月16日 星期一

Python debug 的方法

有別於 C/C++, Python 通常會有原始碼, 除非套件提供者無腦地用 egg 包裝, 不然應該滿好改程式碼的 (我討厭 egg!! 其實這有雙關, 不過 ...)。再加上使用 virtualenv 擁有自己安裝的函式庫, 這樣和別人共用 server 時, 不用擔心改函式庫的程式會影響到別人, 相當方便。

在這樣方便改程式的情況下, 通常我會採下列三種方法之一來觀察程式 (或除錯):

使用 IPython

在要觀察程式的地方寫入

import IPython
IPython.Shell.IPShellEmbed(argv=[])()  # 舊版
IPython.embed()                        # 新版
我甚至寫了個 vim 巨集展開這段。

使用 pdb

import pdb; pdb.set_trace()
效果類以上一個, 不過應該是 ipython 更易操作。或許也可考慮用 IPython pdb。習慣用 gdb 看 backtrace 的話, 用 pdb 滿直覺的。

使用 logger

配合 decorator 的語法, python 要觀察特定函式容易許多, 如這篇提到的做法, 寫個 decorator "log", 配合 lazy initialization 的方式設定 logging。之後只要在有興趣的幾個函式上加個 @log 即可。

這個作法應用的範圍更大, 像在 WSGI 沒 console 可用, 也不能輸出到 STDOUT, 用這招就沒問題。還有, 這個作法也可在 production 環境記錄關鍵函式的執行速度, 協助 profiling。

題外話

若是要除錯的話, 視情況用 unit test 會更好, 原因見《為什麼要寫 unit test?為什麼要先寫測試?》。在見識過 C/C++ 的困難處後, 覺得在 Python 的環境裡寫 test 實在是太輕鬆寫意了, 雖說寫 test 確實還是有它的難處, 不過該寫的東西之前寫得差不多了, 有興趣的人請翻翻舊文。

VirtualBox 改變 vdi 的大小

花了一整個晚上的血淚談。

注意事項

  • 目前 (4.x) 只支援在沒有 snapshots 的情況下改變 vdi 大小
  • 若目前有用 snapshot, VBoxManage 也不會阻止你改變大小, 只是改完就沒救了, 會無法刪掉最早的 snapshot, 推測是因為硬碟上的 vdi 檔案資訊和 snapshot 內的 vdi 不合。可以刪除中間的 snapshot, 因為那沒有涉及硬碟上的 vdi 檔

擴大 vdi

  • 關掉 guest OS
  • 砍掉所有 snapshot, 只剩 current state: 在命令列下使用 VBoxManage modifyhd --resize NUM_MB /path/to/vdi
  • 用 Ubuntu CD 開機 (現在已沒有 "live CD" 的名稱了, Ubuntu 安裝光碟本身就可當 "live CD"), 使用 gparted 調整硬碟大小
  • 重開 guest OS, 可用 df -h 確認實際大小確實有改變 (或用 sudo fdisk -l /dev/sda 看 partition 資訊)。

若不幸在砍 snapshot 前先用 VBoxManage 改了大小, 就無解了。目前只有看到一位仁兄用 Mac 的 Time Machine 還原到他做蠢事前的狀態, 解決這個砍不掉的問題 ... 。

我用另一台電腦的 VirtualBox 實驗以上步驟沒有問題, 確認問題出在我先改了 vdi 大小。

縮小 vdi

我沒試過這個, 順便筆記一下

  • VBoxManage --resize 只支援擴大, 要縮小的話要換指令
  • 先在 guest OS 針對要縮小的 partitoin, 用對應的工具重整 partition (zerofree on Linux)
  • 使用指令 VboxManage modifyhd --compact /path/to/vdi

參考資料

下次用這類 VM 的指令, 一定要熟讀手冊再下手啊 ...

在 Fedora 下裝 id-utils

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