2013年1月27日 星期日

System V的IPC

APUE在這方面似乎沒有給出範例,必須要到他另外一本著作去看
System V的IPC除了PIPE之外主要就是message queue, semaphore, shared memory,三者使用方式都很類似,主要是藉由identifier=>key=>id
identifier指的是一個可以產生唯一key的物件,書內有幾種使用方式,identifier大致上就是一個整數,或者由其他方式產生的整數

1. 直接寫在檔案內,所有include到的程式就可以使用到,但是有名稱衝突的問題
2. 藉由產生物件的程式指定,輸出到檔案,其他程式再去讀取
3. 寫在程式內,利用parent/child關係共享,但只能適用於fork()
4. 使用事先知道的整數跟一個路徑,再利用ftok()產生唯一的key

有了key_t key之後,用來產生物件,他會回傳物件的ID,之後就是使用ID來做操作了,大致上操作為,新增為xxxget(),修改屬性以及刪除則是xxxctl()

結論可以講在前面,作者認為除非必要,不然使用PIPE跟shared memory就可以了,message queue跟semaphore,一則效能沒有太優,又有其他問題semaphore則是沒有良好的控管,維護比較困難

接下open server框架,簡直是整本書最精華的部分了,看了好幾次才看懂他的意圖,如果不了解作者的意圖,可以參考figure 17.1,裡面牽涉到process控制、IPC、terminal,整本書大多數的技巧都用上了

2013年1月26日 星期六

pipe

在linux底下,有anonymous pipe以及named pipe,兩者差異是anonymous pipe是不具名,所以在不同process之間無法共同使用,他存取主要藉由process的parent/child來存取,也就是在parent下先宣告一個pipe(),就可以同時在child下面共享;或者也可以由一個parent下的兩個child分享也是可以的
pipe的特性是,他是半雙工,也就是只有一個process通常只處理一端read(or write),另外一個process處理write(or read)的一端。現在許多新的系統也可以是全雙工的。

對pipe或許可以想像成是多個processes之間共享的queue,這樣就比較好理解了

richard stevens的書籍給出了許多的應用,比方說: process同步、filter...,可想而知pipe在unix/linux環境下的應用
在process建立parent/child關係,通常是透過fork(),所以動作變得有點麻煩,比方說

  • 宣告pipe()
  • fork()
  • 分別在parent, child中關閉
  • 必要的時候呼叫dup()/dup2()對應stdin跟stdout

popen()跟pclose()提供一個偷懶的方式XD,藉由popen等同fork()出一個process並且同時使用exec執行指令,跟著再把stdin跟stdout做好對應

底下是richard stevens的範例,pipe(fd[2])的參數[0]表示輸出,[1]表示輸入,程式就是將hello world由paretn送給child

#include <unistd.h>
#include <sys/types.h>

#define MAXLINE 80

int main(void){
    int n,fd[2];
    pid_t pid;
    char line[MAXLINE];
    if(pipe(fd)<0){
        printf("pipe failed!\n");
        exit(1);
    }   
    if( (pid=fork())<0 ){
        printf("fork error!\n");
        exit(1);
    }else if(pid>0){
        close(fd[0]);
        write(fd[1],"hello world\n",12);
    }else{
        close(fd[1]);
        n=read(fd[0],line,MAXLINE);
        write(STDOUT_FILENO,line,n);
    }   
    exit(0);
}
這裡必須要提到的是在一般stdin/stdout/stderr是FILE型態,而STDOUT_FILENO則是int型態,差別在於型態

2013年1月22日 星期二

select作為socket多工

select作為socket多工,這是基本上的精神,但是有兩點要注意

  • listen()呼叫的時候,socket已經ready,accept()則是資料來到queue中,所以用select可以得知狀況大多在listen()之後accept()之前
  • 呼叫select()之後並不表示accept()不會block process,在處理資料過程,如果使用到任何block IO,整個process還是會被block,所以有很多程式使用fork()


參考資料:
http://fanqiang.chinaunix.net/a4/b7/20010913/0900001283.html
http://www.tenouk.com/Module41.html

setsockopt的應用

setsockopt參數很多,我沒辦法一一記下,在這裡整理一些,等到有遇到的時候補上
SO_REUSEADDR : 在bind的過程就會出現:Address already in use的錯誤訊息,必須等待TCP TIME_WAIT state的時間(2 * MSL)才能使用這個Address,設置這個參數可以避免這個問題

code formatter

有些code從網路下載下來格式凌亂不堪,手邊有個code formatter美化一下,容易閱讀的多
http://astyle.sourceforge.net/

跟vim整合
http://www.jukie.net/bart/blog/vim-and-linux-coding-style

2013年1月21日 星期一

login & terminal

linux在/dev下面一堆terminal的devices,搞得有點頭昏眼花,因為過去老舊的電腦常常靠著COM port與主機相連,所以在multiple user的狀況下,就是如何與terminal/tty做溝通囉,直接看richard stevens的圖
流程大致如下

  • 在開機的時候init會根據/etc底下的設定,init會fork出新的process並且exec tty(看有設定與有多少個COM port)
  • 找到對應device,並且將0, 1, 2這些IO對應好,跟著顯示exec login這個程式,也就是提示user/password輸入,這些程式都具備有root身分
  • 當對應完成的時候,將exec login_shell,可能是bash shell或者其他,使用setuid()...等等,將process權限設定為使用者的權限,shell會與terminal連結好,使用者就可以開始進行操作了

現在大家大多使用網路,以前開發者的想法蠻不錯的,就是只要做出pesudo的tty,讓程式以為自己在跟tty溝通,那麼過去的程式都不用動就可以移植過來,所以圖形就變得如下
rlogind也可能是inetd主要作為pty (pesudo tty)中間的媒介

這下子終於大概了解到/dev底下一堆tty以及pty在做啥用的了

signal的省思

剛開始接觸signal沒多久,就一頭栽入了那些signal與訊號之間的關係,然後開始對應process生命週期與signal之間的聯繫,完全忽略了signal的本質asynchronous!!

asynchronous是一個"可怕"的詞,他有著可怕的效能以及不可捉摸的特性,人們最近對效能的貪婪在node.js可見一班,然後又發現他很難使用同步的功能甚至於說次序(order),很難確定事件之間發生的次序。

signal由於是asynchronous也就是表示他隨時會發生,所以不能對process做任何的假設,如果把signal拿來做為異常處理的手段之一,恐怕只有exit跟longjmp(結果通常也是跟著接exit)是說的上安全的。比方說,當一個process在執行malloc到一半的時候,被signal中斷,跟著signal裡面不管是使用該記憶體或者重新配置該記憶體,這都會引發不可臆測的問題

當然也可以介入更多的努力,力保signal產生的時候,這時process因為某些條件來說,是可以假定他沒有問題的,不過這通常伴隨著效能低落的問題

話說回來,很多programmer對signal的處理方式是忽略,少數採用類似cpu對deadlock的手法,也就是假設signal可以正常的運作,不正常的狀況很少,這是一個trade off

其實這也是一個reentrant的問題,在IBM的文章有討論到,的確有點難解
http://www.ibm.com/developerworks/linux/library/l-reent/index.html
或許最單調的處理方式就如文章內提到的使用sigprocmask()去block所有signal,形成一個critical section來保護

所以signal對unix/linux是必要的,因為這是系統機制的一部份,可是在使用上卻是要小心謹慎的,signal本質上應該是類似一個notify,而不應該再signal handler上處理太多的動作。

P.S. 一個有趣的定義是#define SIG_IGN ((void (*)(int))1);事實上我們應該不可能有address為0x1的pointer,這是隸屬於kernel space的地方,所以猜測應該是在kernel某個部分藉由判斷signal pointer為0x1就把它忽略吧,有人說在do_signal()這個function內,可是我在翻閱kernel的signal.c裡面卻沒有這個判斷式

參考資料:
http://zh.wikipedia.org/wiki/%E5%8F%AF%E9%87%8D%E5%85%A5

2013年1月20日 星期日

C++最佳化與inline跟一個小問題

inline的語意有時候有點模糊,其實主要因該是在最佳化的時候的解釋方式,compiler會試著將inline直接展開,也就是類似MACRO,好處是不會需要處理stack frame,這表示效能將會提升,同時大多數的書籍建議inline不宜過大,這樣才不會導致text區段太過肥大,聰明一點的compiler也會自行將肥大的inline變成function,所以這才導致了inline的語意有些模糊。同時,inline也可能因為展開的關係從symbol table移除,這樣一來在gdb不能使用b function_name,必須直接使用行號,這樣的小問題。

最佳化的技巧很多,有的compiler會將function的parameter直接放到register上面,這樣可以加速處理速度同時節省stack frame,當然會面臨到參數個數跟register個數的問題,其實有時候還會引發concurrency的問題,比方說,在平行處理的時候,最佳化有可能因為將變數對應到register,導致本來用來做critical section判斷的variable,但是兩個thread擁有不同的register,所以程式怎樣看都沒問題,但是執行起來就有問題,這樣的bug非常難以找到

setjmp、longjmp以及C++ exception

之前略讀的一些linux driver以及linux kernel的書籍,對於底層稍微有一點點的了解,現在回頭過來看richard stevens的大作advanced programming the unix envrionment,真的又是別有一番風味阿~很多細節慢慢在心裡一一浮現

在其中提到了setjmp以及longjmp,為了克服goto不該使用,而且goto無法跳躍function call,其實主要原因應該是卡在stack frame的部分,在組合語言中,基本上程式可以跳到任意位置,但是stack frame是各家程式引入的機制,不遵循恐怕會有一些副作用,所以引入setjmp以及longjmp,此外書本中也提到了global variable以及register的狀態必須要注意,setjmp以及longjmp並無法回復他們的狀態,但其實還有更嚴重的是~~這個機制掠過了所有該free或者delete的空間@@a,這點書中就沒有提到了

跟著C++引入了exception,讓大家可以很歡樂的使用try~catch跟throw exception來處理這件事情又可以避開setjmp/longjmp的問題,C++是怎樣做到的,其實他偷偷在stack frame上面塞入了一個link list結構,每個list node又配置了一個exception handler,用來處理結束該stack frame之後該處理的事情

C++塞入了這些結構,表示programmer跟program在執行的時候必須要付出一些額外的代價,但是總比setjmp跟longjmp來的好

話說回來,不知道哪裡看來的一句話,一直縈繞在我的心頭:programmer不應該把例外處理當成程式流程的一部份。我一直無法參悟這句話,是說例外處理就是例外,不應該把例外處理當成程式流程中的一個處理過程?但是例為處理本身就程式的一環(?)難道寫成是不應該考慮例外嗎?還是例外就是意料之外,所以例外處理本身該做的就只有收拾善後跟列印錯誤??

2013年1月18日 星期五

一些關於安全的說法

常常聽到一些hacker或者病毒利用軟體的一類漏洞,叫做buffer overflow或者stack over run,然後可以達到入侵的目的,這些執行的結果往往是為了跳到特定的位址執行某些程式碼。啥是特殊位置?buffer overflow跟stack overrun是為了啥?

以前聽得很模糊,對linux的loader跟process有了解之後,大概可以解釋這種行為的意圖,先看網路上抓來的兩張圖片,一張是virtual process的空間配置是意圖,另外一張是process各資料區段的示意圖

一般來說,hacker想要存取的是kernel space的地方,因為畢竟user space的權限不大,進入kernel space就比較可以隨心所欲,但是一般而言,是無法直接存取kernel space的;此時就是必須利用如heap之類的空間,讓寫入的資料超出本來該寫入的地方,並且寫到kernel space上;又或者想把辦法蓋掉text的程式碼,讓他跳至特定的kernel space內,去執行一些意想不到的code
c string由於沒有邊界檢查,是最常被用來突破的點,但是現在防範的手法已經愈來愈高明了,邊界檢查也是愈來愈嚴苛,可能會規範heap的大小範圍,一旦範圍超出規定的上限或者下限位址,程式就當掉
當然還是有百密一疏的時候,畢竟防禦的人要擋下所有攻擊,但是進攻方只要找到一個突破的點,難易程度也是不對稱的。
當然話說回來,不是只有這種入侵手法,這只是最常見的。

linux上的stack frame

有些文章介紹了stack frame,大多提到的是對應x86底層的實作,即使有些OS跟組合語言的概念,但是還不是很能理解,慢慢的追蹤組合語言才了解了大概,這邊寫下幾個容易混淆的地方

這裡呼叫的function叫做caller,被呼叫的稱為callee,比方說main(caller)呼叫foo(callee)


  • %ebp是stack frame的底部,%esp是stack frame的頂部
  • 但是%ebp是在高位址,但是%esp是在低位址
  • 增加local variable的時候,%esp向低位址減少
  • 存取parameters的時候卻是向著高位址前進,比方%ebp+8, %ebp+12,存取的是上一個caller的stack frame
  • 存取local variable使用的是%esp-0x04這種格式,這就是存取自身的stack frame
  • 一般function開頭push %ebp是為了保留上一個stack frame的位址,等到後面可以回復,跟著就把callee的%ebp設定為caller的%esp,接著sub 0x10,%esp,表示未local varabiles配置空間
  • 注意組合語言call, leave, ret對register的影響,回傳值是透過%eax傳遞


其實intel並沒有規定這樣stack實作模式,之所以會變成這樣,主要是根據ABI的規範
上面的行為可以透過objdump, gdb中的info register, x來觀察

2013年1月17日 星期四

C++的mangle與debug

其實稍微懂得loader的人都知道這個機制,也就是避免名稱空間碰撞的機制,用於C++也可以避免兩個不同class但是有相同的member function名稱碰撞的問題,但設這樣來說,在gdb內很喜歡用的b function_name如何使用?
先把namgle後的名稱找出來,使用之前nm可以列出,如果還不確定,可以使用c++filt轉出prototype,c++filt之所以可以反轉,乃是因為namgle是有規則的(好像有點廢話)
比方說code如下

class foo{
    int a,b;
public:
    void func(int x,int y);
};

void foo::func(int x,int y){
    a=x;
    b=y+2;
}

int main(void){
    foo f1, f2;
    printf("f1:%p, f2: %p\n",&f1,&f2);
    f1.func(5,1);
    f2.func(-4,2);
    return 0;
}


找出foo這個function在被namgle之後的名稱可以用
nm name | grep foo | c++filt
或者
nm -C name | grep foo
可以看到名稱為_ZN3foo4funcEii
進入gdb,這下子就可以用b *_ZN3foo4funcEii來設定break point了,跟之執行run
停止後gdb很好心的印出了function call的prototype,會發現多了一個this,這也是C++的object pointer來源

2013年1月16日 星期三

GDB指令筆記

留一下備忘錄
  • break/b : 設定break point,break main, break 10, break test.C:10
  • run/r : 執行
  • backtrace/bt : 列印出stack frame, bt 10, bt full 10
  • print/p : 列印出變數數值
  • info register : 列印出regiseter內容
  • next/n : step by step
  • step/s : step into
  • continue/c : 繼續執行
  • watch expression : 監視變數
  • delete # : 刪除break point或者 watch point
  • set variable var=expression : 設定變數內容
  • generate-core-file
  • info proc : 印出在/proc內容
  • break #breakpoint if condition : 設定中斷點條件
  • condition #breakpoint condtion : 為中斷點設定條件
  • condition #breakpoint : 取消中斷點的條件
  • clear : 清除所有中斷點
  • disable : 暫停使用所有中斷點
  • enable : 啟用中斷點
  • info b : 列出所有中斷點
  • set history/show history : 顯示出使用過的指令
  • set history save/show history save : 儲存使用過的指令
  • source file_name : 載入設定檔案

commands #breakpoint
...
...
end
在中斷點中斷時執行的指令
define #command
...
...
end
document
...
...
end
可以用來設定指令,比方說
define li
  x/10i $pc
end
document li
  list machine instruction
end

help li則是印出指令說明

使用core file當作gdb的輸出的時候,必須加上-s exec_file或者在進入gdb後file exec_file,才可以載入symbol table

attach pid將某個process加入gdb模式,使用detach離開

參考資料:
http://www.study-area.org/cyril/opentools/opentools/debug.html

2013年1月14日 星期一

你一天寫多少source code?

LOC(lines of codes)雖然是一個非常不精準的指標,卻也是一個非常好用的指標,我大概計算過,我每天只能產出100行程是,跟大多數的人差不多,就是第一天就可以生產五百到一千行,然後開始debug,一個星期大概只有一天拿來寫code,跟著有兩三天用來檢視設計跟重新規劃code(我不大喜歡看到兩個同樣的code blocks),有兩三天用來debug,結果平均下來就只有一百行勉強算是bug-free(畢竟現實專案沒有人可以宣稱他的code是bug-free的)

講了一堆,如何計算code?簡單的方式有

find . \( -name '*.h' -or -name '*.c' \) -exec cat "{}" ";" | wc -l

其實更廣泛的工具有,他支援多種語言
http://cloc.sourceforge.net/

uCLinux--如果沒有...

最近剛剛拜讀完人家,一些在linux底下的loader的機制,也就是牽涉到如何載入share lib跟OS如何分配管理elf程式的部分,回想uCLinux到底有何不同,想不到真的大大不同!!我把uClinux想簡單了

底下是另外一篇解釋uCLinux跟一般linux不一樣的地方,過去總是以為簡單的沒有MMC跟浮點數(floating point)
http://www.linuxjournal.com/article/7221

因為沒有MMU,就不支援Virtual Memory(VM),在這一節上,想不到會有許多差異

直覺,沒有VM,那麼記憶體應該是直接攤在記憶體中,且應該是連續的
因為沒有MMU,過去2^n配置記憶體,考慮到環境,也不可行,必須改用其他演算法,免得有記憶體破碎的問題
Heap要自己管理,也就是可能直接深入kernel,由kernel控制
沒有fork(),讓我實在意外,uCLinux只有提供vfork,使用 vfork() 會使得 parent process suspend 直到child process結束或執行exec()為止。@@a multi-process認知都有點不一樣了,我看muti-thread就更不用想了
只支援 flat executable format (bFLT), 這有兩種型態:
一、code text & data 都是 relocatable
二、使用 PIC 碼 (Position Independent Code)只有部份 data 需要 relocation 即可。
其中第二項衍生出 XIP (eXecute In Place) ,就是程式可以直接在 ROM上面執行的方式,因為 ROM 是 read only,可以被多個 instance share 使用。在這裡似乎uCLinux佔到了一點便宜,因為位址是固定的,外加用事read-only,那麼從新定位code text變成沒有必要的事情,也就是不用plt.got這個表了。可是事情也沒那麼樂觀,因為不是所有的硬體平台都支援PIC orz

回想想用gcc -shared 選項表示使用loading time relocate,但無法幫你在 bFLT格式中製作shared library。 這裡只提一個重要概念,shared library 必須是 XIP ,否則在每個uClinux application 都會產生一份 copy --- 這種情形比 static linking還要糟糕!

也就是說,在uCLinux底下開發程式要注意的點比一般的linux程式還要多的限制,不是簡單換個distribution那樣而已,也不是簡單的抽換到glibc這個lib

2013年1月1日 星期二

node.js體驗完畢

看圖說故事,外殼已經寫好(對決大多數美工能力不強的programmer,就這樣簡單的畫面吧)

希望達到的功能姑且可以名為:方便日記 現在手機在拍照的時候往往可以用GPS記錄當下拍照的位置,所以回家只要上傳之後,server side/cloud side自動幫忙找出拍照地點,並且完成地名、日期以及描述,這些資料當然通通來自於網頁上,下面則是可以讓使用者瀏覽自己已經上傳的照片。也就是不用動手就可以寫完一篇遊記XD當然在不考量版權的問題之下

核心也完成了beta版本,大致效果如下,只要傳進參數loc的經緯度位置,就可以幫忙找出一些可能的地點,在localhost server效果也還可以



想不到一上傳到雲端伺服器,結果竟然大相逕庭,主要是回傳資料少上取多...真的不知道是在網路的哪個環節被截斷了。


以上就是體驗node.js完成的功能,心得有兩點

  • node.js在流程控制上還要加強,非同步傳輸是node.js的強項,但是同時也不好控制,導致現在有許多async的套件,但是對於層層疊套的async呼叫其實幫助只到一定的程度,有許多套件使用preparse或者precompile處理,但是我有點不大喜歡,如果出了一個coffee script之類的又另當別論,不過我想短時間不容易做到
  • 要把我想要的功能做得很完美,恐怕也是很困難,中間牽涉到網路爬蟲、語意分析、地理位置處理,本來還想用圖片比對,後來發現前兩者就已經很難了,只好先擱置了。另外拿jquery parse網頁並不是一個好主意,畢竟他是操作網頁tag為主,而且是自己產生的網頁比較能控制,拿來控制分析其他人網頁,往往是case by case

體驗玩node.js就可以回到嵌入式系統了XD