顯示具有 c 標籤的文章。 顯示所有文章
顯示具有 c 標籤的文章。 顯示所有文章

2013年2月20日 星期三

unix network programming: chapter 15 nonblocking IO

這一章特別有趣,因為之前node.js對於nonblocking IO有點消化不良,慢慢地體會到,nonblocking IO的思維之後,覺得這樣的設計對於performance真很有幫助,不過控制也是比較困難,這篇文章不會提到書中如何實作,想了解的網友可以直接翻閱書籍

最讓人直接聯想到的nonblocking IO思維是connect()這個function,如果說只有一個連線,個人認為block反而容易理解,nonblocking沒太大用途。但是想像一個應用,比方設計一個由地理位置分析周遭環境的application,你需要透過超過五十頁的網頁內容來整合,這時候connect()會block就變成了一個效能的瓶頸,因為每個網頁都要等前一個網頁的3-way handshake完成才能繼續工作,如果改用nonblocking,幾乎可以在短時間內就發出五十個http請求,這樣就快得多了

但是nonblocking其實也有些副作用
  • nonblocking非常耗用CPU效能
  • nonblocking的程式碼相對難以掌握,可以參考書中,由blocking/select IO(40 lines)膨脹成nonblocking IO的135 lines,可見一斑,
  • 書中沒有提到的流程控制相對困難,可參考node.js的flow control部分,就知道連流程控制也是一大挑戰
另外一個作者書中提到的效能比較也是一個很好的指標
  • block and wait version : 354(s)
  • select and blocking : 12.3 (s)
  • nonblocking IO: 6.9 (s)
  • fork(): 8.7 (s)
  • thread : 8.5 (s)
作者比較推薦簡易的版本fork()或者thread,因為nonblocking在程式碼控制方面比較困難

回過頭來說,其實上面選用除了第一個之外,各有各的不同考量,我給出的建議如下
nonblocking IO: performance
fork(): different task or paralle task
thread: similar task or share data

如果要同時發出大量的連線需求,使用nonblocking是比較好的選擇,如果資源充裕(memory很多/cpu也很強)使用fork()也比較好,因為thread遇到block IO整個process就被block住,其他thread也失去了活動機會,根本沒有幫助

如果說需要share data,基本上挑選thread會比較好,因為thread在share data表現上比IPC容易一點,雖然也有同步資料的問題,但是同步資料在fork()也是無法迴避的。相同的動作,如http server讀取檔案,使用thread就是不錯的選擇

fork()比thread的缺點就是要配置大量的記憶體空間,還有share data要透過IPC,好處是不會被block住。
此外process在kernel上分配CPU time的機會也比thread好,比方說目前server有一個process,programmer寫一個抓取遠端資料分析的程式,為了增進效能,將其一分為二,一個使用fork(),一個使用thread,那麼一個是三個process,一個是兩個process,如果均勻分配,使用fork()方式的將會佔據67%左右的CPU,另外一個使用thread只有33%。當然這是一種武斷的算法,要看使用的CPU/IO乃至於網路的情況而定

所以到底是該用nonblocking IO或者blocking IO?該用thread or fork()?其實都有不同的考量,由於richard stevens已經去世了,新版(3ed)的不知道有沒有討論到asynchronous IO??這或許再看看,在2ed richard stevens只是輕輕帶過這種IO的存在,事實上,網路上已經有開始有使用asynchrounous IO的文章了,應該是值得期待的

2013年2月18日 星期一

unix network programming書摘 -- Part 1

只有記錄我認為容易忽略的地方,文章內不會有TCP/IP的詳細過程,可以參考richard stevens的另外一本大作TCP/IP Illustrate,另外手邊有的unix network programming一書是2ed

Chapter 1
伺服器端
 1: #include "unp.h"
 2: #include <time.h>
 3: 
 4: int
 5: main(int argc, char **argv)
 6: {
 7:  int     listenfd, connfd;
 8:  struct sockaddr_in servaddr;
 9:  char    buff[MAXLINE];
10:  time_t    ticks;
11: 
12:  listenfd = Socket(AF_INET, SOCK_STREAM, 0);
13: 
14:  bzero(&servaddr, sizeof(servaddr));
15:  servaddr.sin_family      = AF_INET;
16:  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
17:  servaddr.sin_port        = htons(13); /* daytime server */
18: 
19:  Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
20: 
21:  Listen(listenfd, LISTENQ);
22: 
23:  for ( ; ; ) {
24:   connfd = Accept(listenfd, (SA *) NULL, NULL);
25: 
26:         ticks = time(NULL);
27:         snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
28:         Write(connfd, buff, strlen(buff));
29: 
30:   Close(connfd);
31:  }
32: }

主要流程填寫sockaddr_in結構,socket()=>bind()=>listen()=>accept()=>read()/write()=>close()

客戶端

 1: #include "unp.h"
 2: 
 3: int
 4: main(int argc, char **argv)
 5: {
 6:  int     sockfd, n;
 7:  char    recvline[MAXLINE + 1];
 8:  struct sockaddr_in servaddr;
 9: 
10:  if (argc != 2)
11:   err_quit("usage: a.out <IPaddress>");
12: 
13:  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
14:   err_sys("socket error");
15: 
16:  bzero(&servaddr, sizeof(servaddr));
17:  servaddr.sin_family = AF_INET;
18:  servaddr.sin_port   = htons(13); /* daytime server */
19:  if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
20:   err_quit("inet_pton error for %s", argv[1]);
21: 
22:  if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
23:   err_sys("connect error");
24: 
25:  while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
26:   recvline[n] = 0; /* null terminate */
27:   if (fputs(recvline, stdout) == EOF)
28:    err_sys("fputs error");
29:  }
30:  if (n < 0)
31:   err_sys("read error");
32: 
33:  exit(0);
34: }
流程為填寫sockaddr_in結構,socket()=>connect()=>read()/write()=>close()

Chapter 2
重要的為TCP以及UDP的特色,以及richard stevens書中那張state diagram,中間牽涉到如何透3-way handshake建立連線,以及何時會斷線,為何TIME_WAIT狀態要等2MSL


  • 個人認為重要的觀念是TCP/UDP為全雙工,也就是系統如何處理這樣的概念,從這裏面衍生出了不少問題
  • TCP使用socket pair的概念來辨識連線,也就是Server IP+Server Port+Client IP+Client Port,因為UDP沒有連線概念的約束(也可以自行實作,但是TCP天生幫programmer處理這些)
  • 同時開始考慮,一台機器可能不只一個網路介面,或者說不只一張網路卡,一個service(如FTP)可以同時在所有卡啟動,只要指定socket結構內為INADDR_ANY即可,如果要綁定數張,就只能一個一個來
  • 另外書中提到kernel緩衝區的可以用SO_SNDBUF/SO_RCVBUF(setsockopt())來調整,TCP因為有window size的關係,照理不應該有buffer用盡的問題,但是UDP卻會,又加上UDP是unreliable,buffer一旦滿了就只會drop,連通知都不會有

Chapter 3
  • 因為unix/linux設計socket不只可以用在ethernet還可以用在domain socket上面(一種IPC),所以網路上用的是sockaddr_in結構但是在connect()/bind()/accpet()...參數用的卻是sockaddr結構(一種所謂generic socket結構),所以在呼叫這些api往往形態要轉型
  • bind()、connect()、sendto()是將資料由user space傳送到kernel space
  • accept()、recvfromgetsockname()、getpeername()則是由kernel space回傳資料到user space
  • byte order的問題,網路上使用的是big-edian,但是intel cpu是little-edian,所以有這問題,通常有四個函數來處理htons()、htonl()、ntohs()、ntohl(),h表示hostn表示networks表示16bits、l表示32bits
  • 新的由IP字串取得address的function為intet_pton()以及反向函數inet_ntop()其中p表示presentation
  • 作者時做了好一些輔助函數,很有參考價值

Chapter 4
重點之一,流程圖,書中逐一解說各個function用途


  • socketaddr_in要轉型的原因如前一章,另外要注意,因為可以合乎各種family,所以在connect()/bind()/accept()除了轉型之外,還需要傳入結構大小的指標
  • 這裡衍生出一個重要的觀念,TCP server一定要bind()嗎?答案是不一定,可以由系統指派,之後在用getsockname()取回sockaddr得知,而client的sockaddr資訊則可以用getsockpeername()取回
  • fork()解決一小部分 socket為了全雙工所面臨的問題,因為程式不能因為系統呼叫而block住


chapter 5
  • 書中提到了系統的問題,也就是signal會干擾正常流程,以及如何避免child process變成zombie的問題,使用waitpid()來解決,wait()因為kernel不queue signal可能還是會產生zombie
  • SIGPIPE有可能是因為網路上遇到RST訊號,這屬於網路問題,但是unix反應到process上變成signal,必須妥善處理才能避免連線的問題

chapter 6
  • 解說了blocking/nonblocking IO跟IO multiplexing、還有signal IO、asynchronous IO
  • select()函數示範了IO multiplexing,這個函數可以簡單的處理網路的IO以及一般的輸入,但是中間呼叫其他system call還是有可能會block整個process,所以要小心
  • select() -- read ready
    • 正常狀況,資料高於低水位,可以透過setsockopt()的SO_RCVLOWAT調整低水位
    • 對方已經關閉連線,會回傳EOF
    • 在完成connected的queue中有可用的socket
    • 有sock error存在,read()回傳-1,利用getsockopt()取得SO_ERROR資料
  • select() -- write ready
    • TCP在socket已經連線的時候可以寫入,或者UDP/TCP寫入資料多於低水位的時候,使用setsockopt()的SO_SNDLOWAT調整
    • 連線端已經關閉寫入,會產生SIGPIPE
    • 網路有sock error存在,write()回傳是-1,利用getsockopt()取得SO_ERROR資料
  • shutdown()函數以及setsockopt()的SO_LINGER可以調整關閉socket時候的動作,必須考慮到網路的封包。
  • close()只是關閉write/read fd並沒有關閉網路連線(或者TCP沒有送出FIN)
Chapter 7
介紹常見的socket options,分成幾個層次,一般socket options、IPv4 options、IPv6 options、ICMP options、TCP options。一般socket options指的是大多與protocol獨立無關,由kernel透過與protocol無關的code設定
我摘要了幾個前面常常碰見的options,有些options大多集中在書本前面,且偏向一般socket options

  • SO_BROADCAST : 廣播
  • SO_ERROR : 網路出錯
  • SO_KEEPALIVE : 兩個小時透過封包確定TCP連線還存活著
  • SO_LINGER : FIN訊號的處理
  • SO_RCVBUF/SO_SNDBUF : 設定TCP/UDP的buffer,TCP不用特意調整,他有window size機制
  • SO_RCVLOWAT/SO_SNDLOWAT : 資料的低水位控制,有關效率以及select()
  • SO_REUSEPORT
  • SO_RESUEADDR : 只要IP不同,特別不用等待2MSL就可以重新bind()
  • TCP_KEEPALIVE : 要開啟這個選項就要同時開啟SO_KEEPALIVE
  • TCP_NODELAY : nagle演算法實作


Chapter 8
UDP介紹,如同TCP一樣,有張流程圖,不過不是一定的

  • 不是一定的理由是,用戶端可以呼叫bind()跟connect(),當然兩者也可以都忽略
  • 書中探討到了UDP失去了TCP特色的問題,比方UDP無法得知連線IP/Name,UDP沒有流量控制,UDP沒有重傳機制
  • UDP因為不做3-way handshake,所以無法確定client/server是透過哪個IP或者介面卡傳輸,兩邊都可以從自己主機上的任一IP或者介面卡回應另一方
  • UDP可以透過IP比對,或者DNS比對(多個IP對應到單一domain name),來驗證資料來源
  • UDP可以透過前一點機制與Timeout設定來確定封包有沒有送達
  • UDP可以透過SO_RCVBUF改善流量問題
  • UDP的好處之一,可以透過一個client同時對多個server提出要求,但是會引發一個問題是ICMP的訊息將是非同步產生
  • 其他可以參考我之前寫的socket FAQ

2013年2月14日 星期四

select()的細節

直接先看code

 1: #include <sys/types.h>
 2: #include <sys/select.h>
 3: #include <stdio.h>
 4: #include <stdlib.h>
 5: #include <string.h>
 6: 
 7: #define BUFSIZE (256)
 8: 
 9: int main(void){
10:     fd_set read_fdset;
11:     int maxfd=1;
12:     char buff[BUFSIZE];
13:     while (1) {
14:         FD_ZERO(&read_fdset);
15:         FD_SET(0, &read_fdset);
16:         int result = select(maxfd + 1, &read_fdset, NULL, NULL, NULL);
17:         if (0 > result){
18:             printf("select error\n");
19:             exit(1);
20:         }   
21: 
22:         if (FD_ISSET(0, &read_fdset)){
23:             printf("data is ready\n");
24:             int c=getc(stdin);
25:             printf("%c",c);
26:         }   
27:     }   
28:     return 0;
29: }
結果是?輸入test,按下兩次enter,得到的結果,如下圖
如果可以一眼看出答案,表示對select()有深入了解。容我賣個關子,這裡問題出在於stdio對於buffer以及kernel buffer的認知上面

2013年2月13日 星期三

fork() or pthread

pthread已經是很久以前碰過的東西(十年以前了),當然這是一個歷久不衰,甚至愈來愈興盛的library,然則有許多人把他當成效能改進方案,是否完全正確!?說到效能,就不得不提到最近崛起的方式則是asynchronous IO,當然兩者不是完全競爭性的存在,但是卻是效能提升的選擇

如richard stevens在unix network programming提到的,fork()需要配大量資源,有其包袱,如果要溝通parent/child則必須透過IPC機制,thread則沒有這些問題,但是相對應的thread有同步的問題

我想介紹兩點使用thread但是不使用fork() process所可能帶來的問題,藉此想說明,不要過度依賴thread
  • thread遇上system call可能block所有threads,這可能是一個programmer意想不到的
  • thread在CPU分配上有先天的問題,因為linux本身是以process為單位分配,也就是如果一個programmer希望他的program有較高的效能,可能用fork()比較好
有上面的問題(issue)也不表示捨棄thread,個人認為應該更加精細的控制thread與process才能達到更好的效能,比方說將批次的工作做一process,每個process在分成若干threads,當然這些方式往往必須programmer付出更多的精力來達成,但是在目前追求效能的風氣下,這是一種解決的方案

以第一點舉例來說,參考之前雲端投影機,當client提出一個投影片的需求的時候,server必須將slides轉換為images,這是一個耗用大量CPU以及block IO的工作(或許可以將這個過程使用asynchronous IO來處理,但表示連libpng之類的lib或者使用到任何tool都必須支援或者轉換為支援asynchronous IO),即使使用thread或者select()都無法解決的,最後我選擇的解決方案是fork()

asynchronous IO則是還沒機會使用C語言實作過,但是倒是在javascript的node.js上體驗過,一個使用event queue來理解這個機制比較容易,但是asynchronous IO似乎在流程控制上比較困難,如果有興趣可以參考我之前寫的文章

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

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日 星期日

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日 星期五

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月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/

2012年4月22日 星期日

static function in C

 static functions are functions that are only visable to other functions in the same file.  
也就是說static function在C語言內並不跟static variable的語意一致,static在java上面是一致的
C語言static function的控制是visability,但是在static variable則是life time

2012年3月8日 星期四

Posix Message Queue (1)

Richard Steven的大作:

header file: mqueue.h
mqd_t mq_open(const char *name, int oflag, ...);
int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);
回傳一個handler,型態為mqd_t,close表示關閉,unlink並不會真正刪除,而是隨著系統對close作count,直到count=0,也就是最後一個process使用close,才會刪除message queue

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *attr,mq_attr *oattr);
struct mq_attr{
long mq_flags;
long mq_maxmsg;
long mq_msgsize;
long mq_curmsgs;
};
可以取得各種屬性,其中mq_maxmsg跟mq_msgsize都只能在mq_open的時候設定,免得硬要把已經在msg queue內的message縮小,這樣的操作屬於不合理

message queue收送
int mq_send(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);
ssize_t mq_receive(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);

書中有提到一個問題就是message queue資料型態被定義為char*,其實建議應該定義為void*
另外就是在receive的時候必須先使用mq_getattr取得資料的大小,如果太小將會傳回錯誤,書中的程式figure 5.7就是這麼作,其實我認為應該是使用最大的msg size最為配置大小才不會產生錯誤,因為如果多人同時讀取message queue難保沒有race condition,不然就得加上file lock的機制

message queue跟FIFO一個很大的差異就是優先權以及個數,對FIFO來說,他就是一個stream,可是message是很多個package組成,再者FIFO並沒有優先權的概念,message queue有

另外必須注意到系統message queue的限制,書中提到可以使用sysconf來讀取

後面開始探討message非同步的問題,這就牽涉到system call以及非同步訊號安全函數的使用,等後面有空再寫

2012年3月6日 星期二

Pipe與FIFO

header file : unistd.h
int pipe(int fd[2]); 正確回傳0錯誤回傳-1
相當單純的一個函數,fd[0] 供 read,fd[1]提供write
使用於fork()的時候,通常provider關閉fd[0],只有使用fd[1],相反的comsumer則是關閉fd[1],使用fd[0],關閉pipe則是使用close()
pipe是半雙工的模式,如果要使用全雙工,richard steven建議使用兩組pipe來模擬

header file: stdio.h
FILE *popen(const char *cmd, const char *type);
int pclose(FILE *stream);
類似使用檔案一樣,只是執行一道command,然後由stdout讀入資料,或者使用寫入模式,經stdin將資料輸入command中

Pipe有兩個問題,第一個就因為file desriptor只有在程式執行間產生,也就是大致上只能適用於parent/child之間的關係,對於不相關的process就無法溝通,第二個問題是沒有權限的管理,因為設計的關係,本身不遭遇/提供這樣的情境


FIFO的設計就是用來解決這問題
header files: sys/types.h, sys/stat.h
int mkfifo(const char *pathname, mode_t mode);

由於是輸入路徑,所以只要知道檔案路徑就可以在process之間的做溝通,取得回傳值之後就可以使用open來開啟,但是FIFO不支援lseek操作,會回傳ESPIPE錯誤
關閉一樣使用close就可以,但是因為mkfifo會產生file,所以必須使用unlink()來刪除

至於FIFO還有其他的特性,如nonblock,晚點有時間再寫

IPC object的life time

IPC Objectlifetime
FIFOprocess
Posix mutexprocess
Posix conditional varprocess
Posix read/write lockprocess
fcntl recorder lockprocess
Posix message queuekernel
Posix named semaphorekernel
Posix memory semaphoreprocess
Posix shared memorykernel
System V message queuekernel
System V semaphorekernel
System V shared memorykernel
TCP socketprocess
UDP socketprocess
Unix domain socketprocess

明顯的kernel將會隨著有無釋放相關的IPC物件而影響lifetime,另外一個影響因素就是為是否為kernel等級,如果是kernel的lifetim,該物件如果沒有釋放,將會被保留到系統結束為止,換句話說,他會相當的佔用系統的資源

底下是兩大主流Posix與System V所支援的IPC的head file
IPC ObjectHeader filefunctions
Posix msg queuemqueue.hmq_open、mq_close、mq_closemq_getattr、mq_setattrmq_send、mq_receive、mq_notify
Posix semaphoresemaphore.hsem_open、sem_close、sem_unlinksem_init、sem_destroysem_wait、sem_trywait、sem_post、semgetvalue
Posix shared memorysys/mman.hshm_open、shm_unlinkftruncate、fstatmmap、munmap
System V msg queuesys/msg.hmsggetmsgctlmsgsndmsgrcv
System V semaphoresys/sem.hsemgetsemctlsemop
System V shared memorysys/shm.hshmgetshmctlshmatshmdt
在Posix系統,藍色為建立/開啟予刪除的function,而System V則是只有建立,同樣綠色都為屬性操作的函數,System V大多把刪除的部分交給綠色的function,最後紅色則是物件操作,看的出來system V大概應該是依賴輸入的flag/definition去作為辨識,雖然感覺function比posix少,但是操作起來語意可能反而不明顯,但是也換來了一個好處,Posix擴充可能必須新增function name,但是System V只要擴充參數的意義跟定義就好

2012年3月4日 星期日

Shared Memory in C (2)

今天翻出了Unix Network Programming Vol. 2,看了shared memory
原來還有分posix跟跟system v阿,之前用的是system v的share memory,主要利用到幾個呼叫函數
#include<sys/shm.h>
shmget 建立一塊share memory
shmat 將share memory attached到某個process的空間內
shmdt 分離share memory與該行程
shmctl share meory的操作,包含刪除的部分

一直誤shared memory是parent/child或者child/child之間的資訊交換使用,事實上他可以超越許多process,也就是兩個processes之間沒有關係也可以使用shared memory做溝通,不過中間有個重點就是key的產生,在使用shmget的時候會需要用到一個唯一的key去產生,只要知道這個key value,就可以取得相對應的shared memory

在書中使用ftok這個function去產生唯一的key,其實有個小小的陷阱,ftok接受一個存在的檔案路徑,並且產生相對應的唯一key,也就是只要預期兩個相同的檔案路徑就會產生相同的key,但是事實上man page有提到,如果說該檔案路徑被刪除後就有可能不一樣,也就是說假設A, B兩個processes,A使用ftok(path1,1)取得key1,當path1重新被建立(如刪除後又重新新增),這時候B使用ftok(path1,1)可能會取得key2且key2!=key1

效能方面,書中也有所解說,shared memory與pipe/FIFO/message que這些空間主要都由kernel在維護,但是後者是由process A讀取檔案(system call 1)後寫入pipe/FIFO/message queue(system call 2),跟著再由process B取得(system call 3),最後再由process B輸出到(system call4)檔案,也就是後者進行了四次的system call。由於shared memory是直接對於kernel將共享的記憶體映射到process空間,也就是不用透過kernel來管理,這樣只要兩次(system call 1, 4),所以效能會比較好

雖然richard steven大師這樣的解釋讓人比較了解差別跟應用的情境,但是實際上實作的差異還是有分別,如shared memory屬於哪個process?還是屬於kernel?如果實際屬於kernel那就嚴重囉,表示使用者如果讓他產生溢位的話可能會危害到kernel,如果屬於process,那他應該屬於哪個process的?這些疑問等以後有時間再來trace code好了

2012年3月3日 星期六

新玩具完工~雲端投影片~

做了一個雲端投影的應用,弄了許久,終於搞定,這只是為了自己好玩開發出來的。也在網路程式上重新在上了一課,以前老是用高階的程式語言,開發的時候輕鬆許多,但很多時候也有許多限制,必須重新檢視網路的能力,再重新把Richard Steven的書翻出來,有了更深刻的體會,以前一些隱晦不明的地方,隨著對系統的了解也有比較深入的認知

比方以前很少去動到的socket option的設定,這次也用到了,因為在報告過程之中,一個socket可能會保留很久,預設的timeout時間太短了

setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,sizeof(timeout))

另外很多高階的程式語言其實無法很貼近系統,對於一些IPC的操作之類顯得比較無法做細部的調整,畢竟高階語言大多要跨平台,只能就共同的特性做對應

最後就是有些陷阱的部分,比方說使用24bits顯示模式,用程式抓下來的圖片最多不會超過24bits(很多人心裡應該會這樣默認吧),很可惜有些程式很好心的幫我們轉成了32bits,結果在解析圖片過程就可能造成錯誤

也遇到了一個無法解決的問題,那就是開發過程中因為程式的不正確讓connection若入的LAST_ACK的模式,但是client一直沒有反應,這時候即使把server的process殺了,port #還是會被綁住5~6mins之久,而且我也沒有找到解決方式,網路上大多釋放port #的方式是使用kill process的方式,對這個問題是沒有幫助的

2012年3月1日 星期四

signal process、shared memory與IPC

shared memory這手法已經是常見的IPC手法,但是在開發期間往往程式會在無預期的地方出錯,或者是手動ctrl+c給予中斷,這時候必須加入signal process讓程式自行清理,或者類似socket的地方,不然會有port number或者shared memory繼續被占據,下次執行前必須手動清理

常見的signal有
SIGINT : 由ctrl+c產生
SIGTERM : 關機或者由其他方式送入
最後在IPC產生的時候,由child process可以要求系統對於parent process結束的時候送出一個signal給child process
prctl(PR_SET_PDEATHSIG, SIGHUP);
這個只適用於linux,屬於kernel額外提供的功能


但是一旦需要再signal process中回收資源的後,表示資源必須是global variable,因為我暫時想不到其他方式,這某種程度讓程式變得比較不穩定,當然可以想辦法寫入shared memory中,但是這樣一來就面臨shared memory規劃的問題(要小心不要輸入的資料蓋錯地方),還有race condition的議題

愈是深入IPC就愈覺得System Programming因作業系統而異的特色

參考資料:
http://www.win.tue.nl/~aeb/linux/lk/lk-5.html
http://www.cs.cf.ac.uk/Dave/C/node24.html
http://www.c.happycodings.com/Gnu-Linux/code18.html
http://hylcarson.blog.sohu.com/54735006.html