2011年1月27日 星期四

【C/C++】"嵌入式系統-使用C/C++" 讀書整理(part 2)

【ch10 最佳化】
 必須將程式最佳化,讓程式能在低成本的產品架構之上執行


程式碼效率原則
Inline 功能

查表法
把 流程控制結構 switch 改成查表
int (* func) () nodeFunctions[ ] = (pNodeA, pNodeB, pNodeC);
status = nodeFunctions[getNodeType()]();

撰寫組合語言

暫存器變數

全域變數

輪詢

整數算術運算

程式碼瘦身

避免使用標準函式庫常式

原生 word 大小

Goto 敘述句

記憶體縮減
把常數移到 ROM
降低 stack 大小

2011年1月26日 星期三

【C/C++】"嵌入式系統-使用C/C++" 讀書整理(part 1)


【本書的目的】
利用高階語言,編寫驅動低階硬體的程式。
讀者該學習的是開發系統的程序
與程序精神所在。

【本書內容概要】
1~5章:基礎
5~10章 :精華

【PS】
譯者還頗有趣的,
因過去的開發,並無完善的程式規劃,
少了程式的骨架,而如無法將先前的成果再次運用,
自嘲自己是 coder 而不是 programmer XD。)

先想想看,你為什麼要教導學生這一門課,你想讓他們學到什麼?
最終的成果非關技術,只是一般常識而已。
--理查 費曼

【C/C++】"GNU 程式設計" 讀書整理(part 5)

【ch9. 程式執行計時與統計資料】
時間是程式執行的主要關鍵,減少程式執行時間,是程式設計時最重要的考慮。
執行績效包含許多層面:實際時間、CPU時間、行程及程式段落的執行次數、I/O佔全部執行時間的比例。
時間統計的方法可分為兩種:
* 簡單計時器:/bin/time (主要是針對整個程式,也可再程式中呼叫計時程序)
* 產生報告的統計程式:gprof 換產生扁平統計和呼叫關係圖表(call graph),它顯示了函式間彼此的呼叫關係和等待時間。

簡單計時
time analyze inputdata outputdata
22.3 real  7.1 user  4.5 sys  (執行總共花了 22.3 秒,CPU 花了 7.1 秒再使用者空間,4.5 系統呼叫時間,部份為等待時間)


計時統計(gprof)

再使用 gprof 前,gcc必須用 -pg來編譯程式和連結的務建模組。
編譯時, 會再程式中加入指令來追蹤呼叫函式次數、運行時間。連結時,會加入必要的起始設定及統計程序。
gcc -pg a.c b.c -o c ... (會產生 gmon.out,含有一些統計資料,但不是可讀的)


範例 a.c
#include
#include
#include
main()
{
int total;
echo_hello();
total = count_number(1,10000000);
printf("total is %d\n", total);
}


範例 b.c
#include
#include
#include
echo_hello()
{
printf("Hello\n");
}
count_number(int a, int b)
{
int i;
long int j;
for ( j=0 ; j <= 100000000 ; j++)
        {
        for ( i=a ; i<=b ; i++)
            {
            i+=i;
            }
        }
return i;
}



gprof {options} {exe_file} {stat_file} > output
gprof c > output
-b (省略說明)
-e name (統計圖中不含 name 程序及 name 的子程序,但執行的時間仍會列入加總)
-E name1,name2 (不列入加總)
-f (只列出 name及後代程序的呼叫關係,報告中仍有其他程序的時間統計)
-F (無其他程序的時間統計)
-s (合併多個 stat-file 為一個,取名 gmon.sum)
-Z (產生從未被呼叫的函是列表)

扁平圖解讀及最佳化
 
一般來說,計時最久的函式呼叫匯出現在扁平統計最前面,檢視這些函式以增進效率的方向有三:
* 改進函是的演算法
* 改進呼叫的程序,使函是不會被呼叫如此頻繁
* 改寫呼叫者的方式,刪除最常被呼叫的程序 (可用 inline subsitution 解決)


統計圖解讀及最佳化
* 當函式的的 self 大於 childen,是最佳化的好對象
* 當函式的的 childen 大於 self,不用改進函式本身,減少呼叫次數或改善費時的資程序
* 當無法改進函適時 (可能他再函式庫裡),察看 called 最高的父程序,並減少呼叫次數
* called 很高時,減少被呼叫的次數。called 很低時嘗試去改進程式

2011年1月25日 星期二

【C/C++】"GNU 程式設計" 讀書整理(part 4)

【ch6. C及C++程式除錯器】
gdb 可讓你執行程式,再程式中設定中斷點,再執行過程中檢視並修改變數,呼叫函式,並追蹤程式的執行過程。

gdb 的編譯
gcc編譯程式時必須啟用 -g 選項來產生符號表以 debug。-g 及 -O 並不衝突,可同時兼顧效能和除錯,但經過最佳化的程式,除錯會比較困難。

啟動 gdb
gdb test [core-dump]-q (忽略開場白和版權訊息)
-d (到 dir 路徑尋找原始檔案,當程式分散再多個路徑下時 )

gdb 基本指令
list (列示檔案)
list line1,line2
gdb 無法分辨 #ifdef 和 #include 造成的流程改變

run (執行程式)
參數可使用,標準輸出入 < 及 >,及 shell 萬用字元 (*, ?, [, ])。
run -b < invalues > outtable
若 run 沒給予參數,預設會使用上次的 run 參數。可用 set args 改變參數,也可用 show argd來察看參數。

backtrace (回溯堆疊內容,以顯示程式當掉時的行為)
whatis (顯示檔案宣告,資料結構的名稱)
ptype (資料結構的定義)
print (除了可列印變數外,還印出函式、資料結構、歷史值($1, $2...)、假陣列(印出h後10各元素 h@10,注意這個震列會變成歷史值,若要參考它的第7個元素 $13[6],看接下來的10個元素$13[0]@10)。若要垂直印出可 set print array。)
print trans::foo
print 'trans.c'::foo
set variabel (設定變數的值)

break line-number ()
break function-name ()
break routine-name ()
break function-name if condition ()
break 46 if testsize == 100

tbreak (暫時的中斷點,使用一次便 disable,可用 enable 再啟動)
continue (從中斷點繼續執行)
info breakpoints (察看中斷點)
disable number
enable number
enable once number

watch (設定監看點 watchpoint,類似 break if,但若無硬體的支援,效能會慢上 100 倍)
watch testsize > 100000 (設定當變數大於 100000 時停止)
一些除錯的方法
* 再可能當掉的地方設定 breakpoint
* 設定 watchpoint
* 用 continue 讓程式繼續執行
* 讓你的程式執行一整夜

單步執行有兩種
* next 若碰到函式呼叫,會執行完整個函式
* step 則會浸入函式,一步一步執行
break main
run
測試 step 和 next

call name (呼叫函式)
finish (結束目前函式並印出傳回值)
return value (取消目前函式執行,並傳回 value)

commands number
...list-of-commands
...list-of-commands
end
(自動執行命列,命令列依附再第 number 個中斷點/監看點上 )

機器語言功能
info line 121
disassemble 0x260c 0x261c
info registers
現代處理器幾乎都有4個通用暫存器
* $pc:程式計數器
* $fp:堆疊框指標
* $sp:堆疊指標
* $ps :處理器狀態

gdb 能抓到所有的系統信號
handle (命令控制信號的處理)
參數至少有兩個:信號名稱,收到信號後處理的動作
nonstop (收到信號傳給程式,但不停止程式)
stop (收到信號後停止程式,來除錯)
nonprint (收到信號後不顯示訊息)
print
nopass (停掉程式,但不讓信號傳給程式)
pass (將信號傳給程式,讓你的程式去處理)
handle SIGPIPE stop print

signal SIGINT (送參數給程式)

使用 scripts
source setbkpts.gdb

附加到現有行程除錯
test &
ps | grep test
gdb test 2912
 

【C/C++】"GNU 程式設計" 讀書整理(part 3)

【ch5. 函式庫】
UNIX 提供許多C語言函式庫(標準輸出輸入、數學、字串等函式庫),這些函式庫與 ANSI C 和 POSIX 版本相容。
 
再本章會教導兩重系統呼叫,錯誤處理和信號。
錯誤處理
透過 errno.h 提供的 errno 變數檢查錯誤,當系統呼叫失敗時,他會儲存一個錯誤碼到 errno 變數,你的程式變可以檢查這個變數來判斷錯誤的原因。
perror 函式提供一種不需檢查錯誤馬的方式,他會回傳一段描述錯誤訊息到標準輸出入流(standard I/O stream)。
#include
#include
#include

FILE *fd;
int fopen_errno;

main (int argc, char **argv)
{
if ((fd=fopen (argv[1], "r")) == NULL)
    {
    fopen_errno = errno;  // 儲存錯誤碼

    if (fopen_errno == ENOENT)  // ENOENT 表示無此檔案或目錄
        {
        printf("no such file");
        }
    else
        {
        perror("Invalid input file");
        exit(1);
        }
    }
exit(0);
}
信號(Signal)
系統換傳送信號來指出(1)程式的錯誤、(2)使用者要求的中斷/interrupt 及(3)其他狀況。
信號是簡單的 IPC (Interprocess communication 行程間通訊),類似 POSIX.4 定義外部信號的功能。
例如當按下 CTRL-C,就是發出 SIGINT 信號給程式,告知程式必須結束。當成是試圖存取非法的記憶體位址,系統會發出 SIGSEGV 信號給程式。
當信號到達時,程式通常會
* Ignore:忽略並繼續執行
* Terminate:結束,可能造成記憶體傾印 (core dump)
* Stop:暫時停時執行,但也可能繼續執行
* Continue:目前停止的程式繼續執行
* Execute handler:預先準備好的信號處理常式
UNIX 系統通常會設定一組 32 種信號,底下為最重要的 6 種(其他系統也會有的信號)
* SIGABRT:預設動作-結束,程式呼叫 abort 時送出
* SIGFPE預設動作-結束,浮點運算錯誤
* SIGILL預設動作-結束,不合法指令
* SIGINT預設動作-結束,中斷發生;當使用者按下 CTRL-C
* SIGEGV預設動作-結束,記憶體片斷重複;不合法記憶體使用
* SIGTERM預設動作-結束,結束的要求(由軟體送出)
 SIGKILL 也是值的討論,幾乎所有系統都俱備。但它的行為較特殊,是以"極端傷害的方式來結束",要求行程立刻停止而結束。
另外許多系統提供 SIGUSR1、SIGUSR2,讓使用者使用自訂的信號。

有兩個函式可以處理信號,(1)raise-程式用它來送信號給自己;(2)signal-程式使用他來改變預設的動作。
* raisee 使用,若成功將回傳0,失敗為1 (此例子因導致結束,raise 將不會 return)
#include
#include
#include

main()
{
int ret;
printf("test raise 1\n");
sleep(5);
ret = raise(SIGINT);
printf("test raise 2, ret = %d\n",ret);
}

* signal 使用,signal(signal, whattodo),whattodo通常有三種 (1)SIG_ING 忽略、(2)SIG_DEF 執行預設的動作、(3)handler
此範例當使用者按下 CTRL-C,inthandler會印出訊息
#include
#include
#include

void inthandler(int signal)
{
    printf("received signal %d\n",signal);
    abort;
}

main()
{
    signal(SIGINT, inthandler);
    sleep(60);
}
系統介面的問題
免費的軟體函式庫不提供函式庫與系統的介面,(例如 read 及 write 函式)。這些函式庫可以再作業系統的C函式庫裡找到。Kernel的必須支援這些函式呼叫。

跨平台編譯可能需要一些特殊技巧:
* 編譯的目的擋可以執行所有函式,但你卻沒有系統中可用函式庫的權限 (此系統是你編譯的環境) -> 解法:找出你需要的函式庫放進編譯系統,保證 gcc 和 gld 可以找到,可以避免從系統上cpoy的問題。
*編譯的目標程式可能無法再系統呼叫達成 (若你正在開發某種內嵌入式的軟體,你的目標程式可能一點"作業系統"的感覺也沒有,他不和 POSIX 相容) -> 解法:(1) 開發一個經簡的函式庫,使你的程式可以編譯和執行最基本的功能,一旦呼叫精減函式庫無法提供的系統呼叫,變會傳回錯誤訊息。(2)開發一個原全與 POSIX 相容的函式庫,提供所有你需要的功能。

跨平台編譯環境的 POSIX 函式
 _exit:跳出但不清除
Close:關閉檔案
Execve:啟動新的行程
Fork:建立新程序
Fstat:取的檔案狀態
Getpid:取得程序識別碼
Isatty:I/O 敘述子是終端機嘛
Kill:送出信號
Link:建立新檔案
Lseek:設定檔案內部指標
Read:讀取檔案
Sbrk:延伸資料段
Stat:取得檔案狀態
Times:取得時間統計
Unlink:刪除目錄項目
Wait:檔帶子程序
Write:寫入檔案
執行時期(run-time)環境
gcc 假設有一個啟動模組稱為 crt0.o,用來設定記憶體,進行起始工作、轉移控制權到 main 常式,並再 main 返回時進行清除工作。也可自行開發自己的 ctr0.o。
crt0.o 的動作有:
* 定義 start,第1個指令開始執行的位置
* 設定 stack pointer
* 設定程式 bss  (未初始化資料) 段的記憶體為 0
* 進行硬體及軟體依些初始化動作
* 呼叫 main