2011年8月29日 星期一

Synchronization and the Java Memory Model 筆記

昨天看 Effective Java 提到 synchronization 同時提供 exclusive execution 和 communication, 但多數人會忽略後面這點。原本不懂這是什麼意思, 看了《Synchronization and the Java Memory Model》才知道是怎麼回事, Java Memory Model 真的和直觀的想法很不一樣。記錄一下筆記。

final class SetCheck {
  private int  a = 0;
  private long b = 0;

  void set() {
    a =  1;
    b = -1;
  }

  boolean check() {
    return ((b ==  0) ||
            (b == -1 && a == 1)); 
  }
}

這段程式有可能 return false, 因為 set() 裡的 a、b 設值可能會順序相反, set 和 check 可能同時被交錯執行。Java 只有保證單一 thread 自己執行的時候, 看起來像按順序由上而下、由左而右執行 (文中用 as-if-serial 表示, 這詞還滿妙的)。

在 multi-thread 時, 情況不同, 各個 thread 有自己的 memory, Java 沒有保證什麼時候 thread 之間才會看到更新後的情況, 這點滿可怕的。但可以用 synchronization lock 或宣告 volatile 強迫 thread 之間同步資料。我想這就是書上說 communication 的意思。

Java 有保證除 long 和 double 外的欄位, 更新值的時候是 atomic 的 (也就是包含 reference), 而 volatile 宣告的欄位也都是 atomic, 包含 volatile long 和 double。這裡要注意幾點:

  • i++ 包含 i + 1 和設值兩個操作, 沒有 atomic。所以設 volatile 也會有危險, 有需要的話還是要用 synchronized, 或照 Effective Java 的建議, 看能不能用 java.util.concurrent.atomic 的類別滿足需求。
  • long、double 外的欄位有 atomic, 但沒保證更新後其它 thread 會讀到新值。若要其它thread 不會讀到 stale value, 要宣告 volatile, 或在讀取前加上 synchronized。
  • array 有 volatile 不表示裡面的元素有 volatile (這還算直覺)。

後面說明各種特殊情況, 還有強調因為 CPU 管理 cache 方式, 有些時候其實 JVM 沒有保證 thread 之間的資料有更新, 但執行起來卻像有這一回事, 讓這種 bug 更難重製。

Visibility 那段值得細讀, 說明什麼情況下可確保 thread 之間有取到或寫入最新的值。像 start Thread 後會確保該 thread 讀到最新的值, 所以, 盡量在 start() 前建立物件, 還有別在 constructor 內 start thread, 避免 thread 執行後取得不完整的狀態。

2011-09-06 更新: 備忘簡短易懂的入門文件: Java Concurrency / Multithreading - Tutorial

2011年8月27日 星期六

Effective Java 讀書筆記: Item 66 - 用 synchronized 確保 concurrency

這節提供一個不直覺的錯誤

package trial;

import java.util.concurrent.TimeUnit;

public class Trial {
    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested) {
                    i++;
                    System.out.println(i);
                }
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

作者說在沒用 synchronization 機制的情況下, Java 沒有保證 thread 什麼時候看到彼此之間的修改。上面的例子, 在作者的機器上不會停。不過我自己試的結果是一秒後結束。作者說原因是 JVM 有可能最佳化 while 那行, 變成只檢查一次 stopRequested, 結果就不會停了。這個 VM 最佳化技巧叫做 hoisting (文中的例子則是改用 stack 上的變數取代 heap 上的)。

解法是宣告 stopRequested 時加上 volatile。不過作者後來接著強調 volatile 很難寫對, 叔叔有練過, 小朋友別亂用 volatile, 乖乖用 java.util.concurrent.atomic 下的類別, 像是 AtomicLong。這個 package 下的東西用了 machine-level 的方法減少 lock overhead, 比用 synchronized 有效率。

當然, 保險的話, 讀寫時都用 synchronized 最安全, 不過就會損失一些效率了。

shell script 處理含空白字元的檔名

以前我習慣寫

for f in $(ls mydir)
do
    ...
done

但遇到檔名有含空白時就炸了, 像這樣

$ ls mydir/ | cat
a b
c
$ for f in $(ls mydir); do echo $f; done
a
b
c

解法是設 IFS 這個變數

$ IFS=$'\n'
$ for f in $(ls mydir); do echo $f; done
a b
c
unset IFS

也可用這個方式在 shell script 裡做到等同於「字串陣列」

files=$(cat <<EOF
1st line
2nd line
3rd line
EOF
)
IFS=$'\n'
for f in $files
do
    echo $f
done
unset IFS

最後附上 man bash 裡的說明:

The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is ``<space><tab><new‐ line>''.

virtualbox 使用 USB 裝置

2012-12-16 更新

  • 現在 (4.x 版) 似乎無需做任何設定, 只要有裝 Oracle VM VirtualBox Extension Pack, 在 VirtualBox 視窗右下角按 USB 的圖示, 再點目標裝置, 即可加入或移除該裝置
  • 同一時間只有 host 或 guest 可擁有該裝置, 所以從 guest OS 移除, 相當於接回 host OS
  • 目前 VirtualBox 只支援 USB 2.0 的插槽, 若偵測不到時, 注意一下是否為這個問題
  • 有時拔拔插插, VirtualBox 會進入奇怪的狀態, 接上去 guest OS 無法連接且跳出 device is busy 的錯誤訊息。試看看拔除該裝置, 重開 guest OS
  • (續上則) 若重開 guest OS 無效, 並且 host OS 已移除該裝置, VirtualBox 的 USB 清單卻仍顯示 "captured", 試看看拔除該裝置, 重開 host OS

原文

網路上搜一下, 比較多是 Ubuntu 當 host 的解法, 我的情況是 Win7 當 host, Ubuntu 當 guest。

這兩篇說明很詳細

現在的版本圖形介面很好用了, 不用像第二篇說的那樣用指令操作。這裡記下我的操作步驟:

  1. 關掉 guest OS
  2. 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB -> Enable USB 2.0
  3. 會出現訊息框, 說明要安裝 Oracle VM VirtualBox Extension Pack。下載後安裝它
  4. host OS 插入 USB 隨身碟
  5. 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB, 點右邊有綠色 "+" 的 USB 頭的圖示, 選擇該 USB 隨身碟, 加入它的 filter
  6. 從 host OS 移除 USB 隨身碟
  7. 開啟 guest OS
  8. 插入 USB 隨身碟, 於是 guest OS 會自動偵測隨身碟, 掛到 "/media/USB DISK"

相關查看的指令:

  • 用 sudo fdisk -l 查看所有硬碟裝置, 我的情況是, 原本只有 /dev/sda, 隨身碟自動掛上去後, 多了 /dev/sdb。另外 ls -l /dev/disk/by-id 可看到 USB 隨身碟的名稱, 用 soft link 連到 /dev/sdb。
  • lsusb 列出 USB bus 資訊, 不清楚還能查什麼。裝完 Extension Pack 後多了 Linux Foundtation 2.0 root hub, 原本只有 1.1
  • 之後可自己用 sudo umount ... 和 sudo mount /dev/sdb1 SOME_DIR 來掛載和移除 USB 隨身碟

備註

  • 加好 USB filter 後, 插入該 USB 隨身碟都是掛到 guest OS, 若要將它掛到 host OS, 大概要關掉 guest OS, 等有需求時再看看怎麼處理較好
  • 加入 USB filter 時, 我一開始是先點藍色的 "o" 圖示, 加入一個空的 filter。原本猜說是全部放行的意思, 不過試了以後, 在插入 USB 隨身碟時, Win7 就當了

2011年8月21日 星期日

用 Sphinx 寫文件

以下是最近試用 Sphinx 的心得, 有錯還請指正。

我原本很納悶, 專案文件是用 Wiki 寫好, 還是用 Sphinx 寫來得好。為啥要特別做個工具來產生文件呢? 抱著實驗的性質試了 Sphinx, 試下去才明白, 難怪不少專案用 Sphinx 來寫。

Sphinx 寫有幾個好處:

  • 文件原始碼可和程式碼放在同一個 repository, 也可藉此順便做版本管理。不過我覺得以版本管理來說, 還是沒 Wiki 方便。
  • 可透過語法載入程式碼內的註解, 這點 Wiki 就沒輒了, 我覺得這是用 Sphinx 的最大優勢。
  • 一堆好用的 plugin, 像是畫圖、寫數學式子、畫 graph等。將 graphviz 原始碼直接寫在文件裡, 還挺方便的, 不過這點 Wiki 也辦得到。
  • 嵌入 ipython 語法, 顯示 ipython 的結果, 包含自動填入執行結果等功能。這個相當好用, 適合寫執行的範例 (寫實例演練的必備工具), Wiki 就沒有啦。
  • 產生的結果是純 html, 並有提供文章搜尋功能 (實作方式很妙, 建好 index 存成 js 程式碼, 透過 js 執行搜尋)。架網站提供文件時, 不需另裝任合套件 (PHP / CGI / Web framework / etc)。
  • 功能彈性, 若是 Python guy 的話, 缺什麼語法可自己寫 extension 補一下。Sphinx 也有提供巨集功能自定語法。

相較之下, Wiki 最大的好處是編完存檔就看到結果, 省掉 make html 的步驟, 方便大家隨時一起共筆, 提供 lock 避免多人同時編輯相衝, 方便查閱版次之間的差異。Sphinx 得透過 VCS 做, 也不方便提供 html 的版次差異。所以若是多人同時共筆, 偏重於功能說明, Wiki 還是較方便。

這裡列幾個熱門專案選擇的做法:

看完後發覺 ........, 一時好像也看不出個什麼頭緒, 之後再慢慢觀察吧。

Sphinx 入門不難, 以下是幾個相關網站:

  • sampledoc: 一小份入門文件, 說明必要的部份, 快速上手。
  • 線上試語法
  • Read the Docs: Python Software Foundation 提供的網站, 設好後, 會自動 fetch codes, 並重編 Sphinx 文件, 還有支援顯示各個版本文件。有了這個站後, 就不用擔心用 Sphinx 還得自己另外找網站放文件。
  • 官網: 東西多, 自然也較難找, 不過文件滿完整的

Sphinx 寫文件後, 我才明白文件包含兩種不同的內容: tutorial 和 library reference。像 Java Doc、Epydoc 這類工具用來抽註解產生 library reference, 避免在兩個地方寫文件。Java Doc 我沒實際試用, Epydoc 雖說產生的結果很炫, 卻不方便用作者自己的觀點組織整個專案的套件, 描述它們的互動, 或是提供常見的使用情境等。

使用者第一時間需要的是一個可立即執行的 tutorial, library reference 則是備查。Sphinx 好用的地方就在於, 它可以自由組織 tutorial, 並提供語法引入 library reference (用 autodocEpydoc extension )。所以用 Sphinx 可同時滿足這兩種需求。

除 tutorial 和 library reference, 開發者可能會需要 system context 和 architecture overview 了解整個架構。這部份就得自己另外用工具畫, Power Point 之類的軟體滿適合的, 重點是容易上手。最近看了《How to make Awesome Diagrams for your slides》 覺得相當有幫助, 順便列在這備忘。

2011年8月18日 星期四

Sphinx 的 autodoc 載入問題

為了使用 autodoc, 我在 conf.py 裡這樣寫:

sys.path.append('/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext')
extensions = ['graphviz', 'autodoc']

結果跑出:

autodoc documenter <class 'autodoc.ModuleDocumenter'> must be a subclass of Documenter

正確的寫法是:

extensions = ['sphinx.ext.graphviz', 'sphinx.ext.autodoc']

開 pdb 追下去, 發現是 sphinx.application 和 sphinx.ext.autodoc 之間 circular import 造成的問題。

在 sphinx.application 裡:

def add_autodocumenter(self, cls):
    from sphinx.ext import autodoc
    autodoc.add_documenter(cls)
    self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)

會載入 sphinx.ext.autodoc, 但我原本的寫法, 讓 sphinx 透過 __import__ 載入 autodoc, 結果產生兩個不同的物件, 但都是 autodoc, 結果剩下的一些程式操作, 就發生了悲劇。

在 autodoc 裡的這段程式就丟出 exception:

if not issubclass(cls, Documenter):
    raise ExtensionError('autodoc documenter %r must be a subclass '

cls (即 ModuleDocumenter) 真的不是 Documenter 的 subclass, 因為這個 ModuleDocumenter 是其中一個 autodoc 的, 而 Documenter 卻是另一個 autodoc 的。

若硬註解掉這兩行, 雖然 autodoc 的部份指令能用, 卻也會部份失效, 像 :members: 就會完全沒效果, 原因也是出在有兩個 autodoc 上, 結果找 members 的那個 autodoc 的 AutoDirective._registry 是空的, 造成它找不到能處理 members 的 Documenter。

備註: autodoc.AutoDirective 負責處理 directive auto*。AutoDirective 依指令名稱從 _registry 中找出適用的 class 來處理, 比方說 automodule 就會找 ModuleDocumenter 來處理。正常的程序下, autodoc.setup() 會填好 AutoDirective._registry, 但在上面說的錯誤設定下, 會變成 sphinx.ext.autodoc.AutoDirective._registry 有設好, 而 autodoc.AutoDirective._registry 是空的。

下面是用 pdb 看設錯情況發生的狀況, 滿有趣的:

> /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(1177)run()
-> documenter.generate(more_content=self.content)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(715)generate()
-> self.document_members(all_members)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(981)document_members()
-> ModuleLevelDocumenter.document_members(self, all_members)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(609)document_members()
-> for (mname, member, isattr) in self.filter_members(members, want_all):
(Pdb) l
1172            # process the options with the selected documenter's option_spec
1173            self.genopt = Options(assemble_option_dict(
1174                self.options.items(), doc_class.option_spec))
1175            # generate the output
1176            documenter = doc_class(self, self.arguments[0])
1177 ->         documenter.generate(more_content=self.content)
1178            if not self.result:
1179                return self.warnings
1180
1181            # record all filenames as dependencies -- this will at least
1182            # partially make automatic invalidation possible
(Pdb) documenter.__module__
'autodoc'
(Pdb) id(AutoDirective)
21622640
(Pdb) down
> /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(715)generate()
-> self.document_members(all_members)
(Pdb) id(AutoDirective)
20956032
(Pdb) import sys
(Pdb) id(sys.modules['autodoc'].AutoDirective)
20956032
(Pdb) id(sys.modules['sphinx.ext.autodoc'].AutoDirective)
21622640

表示一開始在 sphinx.ext.autodoc, 但 documenter 是從 autodoc 取來的, 結果就無聲無息的掛了。

2011年8月14日 星期日

《CPython 源碼剖析》讀書心得 - ch2 - int

PyIntObject

展開巨集後的定義

typedef struct {
   Py_ssize_t ob_refcnt;
   struct _typeobject *ob_type;
   long ob_ival;
} PyIntObject;

基本操作

這篇記了加法的實作: 加完後檢查溢位,若溢位的話, 改用大數 PyLongObject。

PyInt_AS_LONG vs. PyInt_ASLong

在運算過程中, 視情況會用不同方式取出 ob_ival:

  • 前者是巨集, 沒檢查 type, 速度快
  • 後者是函式, 有檢查 type, 速度慢

其它 type 也會看到類似的操作。

immutable

不可變的物件有許多好處, 包括可以任意被所有程式共享, 不用擔心造成依賴關係, 會不小心互炸。代價是各項操作後, 時常需要產生新的物件, 可能會拖慢速度。若會用到大量物件的話, 通常會搭配 object pool 減少生成物件的次數, 藉此簡省計算時間, 或是進一步在生成前先檢查是否已存在同樣物件, 有的話直接共享同一物件, 不生成重覆的物件。

在後面的例子會看到, CPython 有用 object pool 處理 int, 但也付出無限擴大記憶體的代價; 另外, CPython 只針對小部份範圍的數字共用物件, 以取得效率平衡。

memory management

intobject.c 開頭寫下為了減少 malloc 次數, 而採取的作法, 以及它造成的問題:

/* Integers are quite normal objects, to make object handling uniform.
(Using odd pointers to represent integers would save much space
but require extra checks for this special case throughout the code.)
Since a typical Python program spends much of its time allocating
and deallocating integers, these operations should be very fast.
Therefore we use a dedicated allocation scheme with a much lower
overhead (in space and time) than straight malloc(): a simple
dedicated free list, filled when necessary with memory from malloc().

block_list is a singly-linked list of all PyIntBlocks ever allocated,
linked via their next members. PyIntBlocks are never returned to the system before shutdown (PyInt_Fini).

free_list is a singly-linked list of available PyIntObjects, linked via abuse of their ob_type members.
*/

CPython 配了一塊記憶體自己管 int object 的生成和回收, 這塊記憶體只會愈長愈大, 永遠不會變小, 換句話說, 在 64 bit OS 上, 同時用到五千萬的整數後 (e.g., range(50000000)), 即使之後不會再同時用到五千萬個整數, 還是會占去 8*3*50000000 = 約 1G 不會回收的空間。

PyIntBlocks 和 free_list

#define BLOCK_SIZE  1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE  8   /* Enough for a 64-bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) /
sizeof(PyIntObject))

struct _intblock {
   struct _intblock *next;
   PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;  // the head of next-to-new int object

static PyIntObject *
fill_free_list(void)
{
   PyIntObject *p, *q;
   /* Python's object allocator isn't appropriate for large blocks. */
   p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
   if (p == NULL)
       return (PyIntObject *) PyErr_NoMemory();
   ((PyIntBlock *)p)->next = block_list;
   block_list = (PyIntBlock *)p;
   /* Link the int objects together, from rear to front, then return
      the address of the last int object in the block. */
   p = &((PyIntBlock *)p)->objects[0];
   q = p + N_INTOBJECTS;
   while (--q > p)
       q->ob_type = (struct _typeobject *)(q-1);
   q->ob_type = NULL;
   return p + N_INTOBJECTS - 1;
}

這裡用了不少 trick, 若沒先看書, 大概要讀一陣子吧。首先讓 PyIntBlock 不要占超過 1k, 大概是這樣 kernel 比較好找空間, 效率比較高。

fill_free_list 用 ob_type 當作 singly-linked list 的 next, 反正等產生 PyIntObject 時, 就會將它指到正確的值, 開頭的註解有先消毒了, 說會 "abuse of their ob_type members", 好奇 grep 了一下 2.5.2 的程式, 幸好 "abuse" 只出現 29 次 ...。

PyObject *
PyInt_FromLong(long ival)
{
   register PyIntObject *v;
   /*--- Begin of #1 ---*/
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
   if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
       v = small_ints[ival + NSMALLNEGINTS];
       Py_INCREF(v);
       return (PyObject *) v;
   }
#endif
   /*--- End of #1 ---*/
   if (free_list == NULL) {
       if ((free_list = fill_free_list()) == NULL)
           return NULL;
   }
   /* Inline PyObject_New */
   v = free_list;
   free_list = (PyIntObject *)v->ob_type;
   PyObject_INIT(v, &PyInt_Type);
   v->ob_ival = ival;
   return (PyObject *) v;
}

先略過 #1 的部份, 後面再解釋。free_list 指到 linked list 的頭, 準備產生 int object, 在 deallocation 時, 會將 free_list 改指到用不到的 PyIntObject, 所以這塊 int object 的記憶體不會有 memory leak, 只是當 free_list 這串超長, 一堆空間用不到時, 它也不會釋放空間就是了。

small_ints

CPython 初始化時, 會先配置一塊空間存 [-5, 256] 的整數, 改變巨集重編 CPython 可改變這個範圍。

之後用到這範圍整數時, 不會重新產生新的 PyIntObject。上面 #1 那塊程式碼就是從 small_ints 取值, small_ints 也是用 PyIntBlocks 裡的空間。

透過下面的操作說明 small_ints 的效果 (id 會傳回 memroy address):

In [1]: id(-5)
Out[1]: 7747448

In [2]: id(-5)
Out[2]: 7747448  # 沒變, 同一個物件

In [3]: id(-6)
Out[3]: 8558280

In [4]: id(-6)
Out[4]: 8557608  # 變了, 表示這個 -6 是新的物件

In [5]: id(256)
Out[5]: 7753136

In [6]: id(256)
Out[6]: 7753136  # 沒變

In [7]: id(257)
Out[7]: 8557512

In [8]: id(257)
Out[8]: 8557440  # 變了

我原本以為 CPython 像多數語言有共用字串那樣, 有共用所有整數。沒想到只有共用小範圍的整數。仔細想想也有道理, 若每次取數字都要 hash, 效率也會很差吧, 這之間的平衡真難拿捏。

了解 PyIntOjbect 的實作方式後, 才明白為何 CPython 會吃掉這麼多記憶體, 還有為何像 psyco 能在不改 code 的情況下讓 CPython 大幅加速科學計算, 一部份的原因是省下一些生成 PyIntObject 的時間

2011年8月11日 星期四

Sphinx customized theme

官網說明有完整說明, 這裡記個人速記版。

找出 default theme 位置並複制一份出來, 供之後修改:

# 找出 default theme 的位置
$ locate default/static
/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static
/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static/default.css_t
/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default/static/sidebar.js

# 切到自己建的 Sphinx 目錄
$ cd docs

# 複制 default theme 出來
$ cp -r /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/themes/default MY_THEME  

編輯 conf.py 加入:

html_theme = 'MY_THEME'
...
html_theme_path = ['.']

這樣之後改 MY_THEME 下的東西即可。

若只需要改 css 的話, 不需複製整個 theme 出來, 可以改 conf.py 裡的 html_static_path, 改用自己的 default.css 覆蓋 default theme 用的 css。照上面的方式找到 default theme 用的 css, 複制出來放到 _static/default.css, 之後再修改這個檔案即可。

2011年8月10日 星期三

小撇步: 直接看指令的原始碼

~/.bashrc 裡這麼寫:

function see() {
    vi `which $1`
}

complete -c command see

就能打:

$ see apt-file

看 apt-file 的內容, 並支援 completion, 可按 TAB 補完指令名稱。

2011年8月1日 星期一

在 Ubuntu 上使用 CUnit + curses 執行 unit test

安裝

  1. sudo aptitude install libncurses5-dev
  2. wget -O CUnit-2.1-2-src.tar.bz2 http://downloads.sourceforge.net/project/cunit/CUnit/2.1-2/CUnit-2.1-2-src.tar.bz2?r=&ts=1312211219&use_mirror=nchc
  3. tar jxvf CUnit-2.1-2-src.tar.bz2
  4. cd CUnit-2.1-2/
  5. ./configure --enable-curses
  6. make
  7. sudo make install

這樣會裝到 /usr/local/include/CUnit 和 /usr/local/lib/libcunit.*。

編譯

  1. 貼上這裡的範例程式, 存檔為 example.c
  2. 註解掉最下方的 CU_basic_run_tests();, 改為 CU_curses_run_tests;, 並在上面加入#include "CUnit/CUCurses.h"
  3. gcc example.c -lcunit -lncurses -o example

執行

  1. ./example

失敗的話, 檢查一下 /usr/local/lib 是否有在 /etc/ld.so.conf 裡, 加進去後執行 /sbin/ldconfig –v 更新設定。細節參考: 《error while loading shared libraries的解決方法》

若不想用 curses 的話, 就用 CU_basic_run_tests。

若確定用不到 curses, 安裝時可以不裝 libncurses5-dev、configure 時可以少掉 --enable-curses。這樣的話, 編譯時也不用加 -lncurses。

不過用 curses 跑還挺炫的說, 會有綠色的 progress bar!! 之後再來看看是否實用。

找出占用 port 的程式

netstat -nap | grep 8000

會找出使用 port 8000 的程式

在 Fedora 下裝 id-utils

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