2010年10月13日 星期三

Linux下time_t,struct tm,還有時間相關的一堆函式

搞不清楚Linux下面的time_t,struct tm,還有時間相關的一堆函式。

其實不是Linux的錯,是大家平常不那麼在乎時間的定義,或者說平常不在乎也沒關係。
時間要分成兩種,本地時間跟世界時間。
本地時間指的是當地的時間,像是台北時間,香港時間,美國時間,
世界各地方的本地時間是不同的。
如果你有看王建民的實況轉播的話,
你就會發現台灣現在半夜三點,可是美國那邊卻是出大太陽在下午三點。
或者是台灣現在已經早上8點了,美國那邊還是晚上8點。

本地時間參考的標準其實跟各地日出的時間有很大的關係。
各地的日出時間因為地球自轉的關係,有先後順序的差別,
拿台灣,英國,跟美國東岸來說,
台灣會先日出,過了8小時才輪到英國日出,再過4小時才輪到美國東岸日出。
因此我們說台灣時間比英國時間快了8小時,比美國時間快了4小時。
所以當人家說"你哪來的美國時間"阿,就是指你動作太慢了。
這也是為什麼每年跨年的時候,
台灣會先跨,然後到了隔天中午12點時,
新聞才會開始報導紐約時代廣場的跨年。
這裡可以參考一下鳥哥的文章http://linux.vbird.org/linux_server/0440ntp.php

而UTC(universal time, coordinated),"世界標準時間",指的就是世界時間,
或是也可以想成絕對時間。
各國的本地時間都可以轉換成世界時間,也就是UTC,
這樣世界各地的人就有一個統一的標準時間可以參考了。
這就像英語是全世界共通的語言,但各地仍可以用當地傳統的語言來溝通,
目的是相同的。
UTC其實就是英國法國的本地時間(因為世界世間是他們想出來的)
所以英國的世界時間用UTC來表示,本地時間也等於UTC。
而台灣的本地時間比英國的本地時間快了8小時,
所以台灣的世界時間用UTC+8表示,
(或是說台灣的日出時間比英國的日出時間快了8小時)
而紐約的世界時間則用UTC-4來表示。
(美國的日出時間比英國的日出時間慢了4小時)

回到Linux programming裡面。在Linux下面要看時間,可不是容易的事情。
在Linux下面要經過三個步驟
    1.程式設計師從電腦那邊取counter
    2.把counter轉換為本地或世界時間
    3.將時間顯示為一般使用者看的懂的時間
才能顯示出人類看的懂的時間。
counter是給電腦看的。程式設計師寫了一些函式把counter轉成程式設計師看的懂的,最後程式設計師再轉換成給一般人看的

[步驟一]
首先要知道在電腦內時間是利用counter來計算。
1970/1/1 00:00:00 counter為0。counter每秒鐘加1。
到2010年10月14日 2點59分41秒
(此處都是以UTC為準,此時台灣本地時間應該是2010年10月14日 10點59分41秒)
為止此counter的值為1287025181。
利用time()函式,我們可以取counter的值。
counter的表示就是用time_t來表示。

[步驟二]
取得counter的值後,接下來要作一個轉換。
轉換為程式設計師看的懂的格式(程式設計師也是人),
程式設計師用struct tm來表示時間。
struct tm {
    int tm_sec;         /* seconds */
    int tm_min;         /* minutes */
    int tm_hour;        /* hours */
    int tm_mday;        /* day of the month */
    int tm_mon;         /* month */
    int tm_year;        /* year */
    int tm_wday;        /* day of the week */
    int tm_yday;        /* day in the year */
    int tm_isdst;       /* daylight saving time */
};
有年月日時分秒,清楚多了。
Linux裡面提供了兩個函式可以把counter轉換為struct tm。
使用的函式是gmtime(),或是localtime()。
用gmtime()會把counter轉換為struct tm,轉換出來是UTC時間。
用localtime()跟用gmtime()很像,會把counter轉換為struct tm。
不同的是轉換出來的UTC時間,會再根據本機電腦時區的設定,再轉換為本地時間。

[步驟三]
轉換完之後接著才是顯示給一般的使用者來看。
也就是要把struct tm轉換給一般的使用者看。
顯示的時候用的函式是asctime(),參數是struct tm
這一連串的用法比較...怪,一個簡單的範例如下:
time_t times;
time(&times);
printf("time() is %ld\n",times);                                                          // time() is 1287025181
printf("asctime(GMT or UTC) is %s",asctime(gmtime(&times)));      // asctime(GMT or UTC) is Thu Oct 14 02:59:41 2010
printf("asctime(local) is %s",asctime(localtime(&times)));               // asctime(local) is Thu Oct 14 10:59:41 2010
這邊有一個函式可以簡化asctime(localtime),那就是ctime()。
範例如下 :
printf("asctime(local) is %s",asctime(localtime(&times)));               // ...Thu Oct 14 10:59:41 2010
printf("ctime equals to asctime(local), it is %s",ctime(&times));      // ...Thu Oct 14 10:59:41 2010
有沒有,雖然一個呼叫asctime(),一個呼叫ctime(),但是輸出卻是一模一樣。

如果程式設計師不喜歡asctime()顯示的方式時,可以改用strftime()這個函式。
範例如下:
char tbuf[64];
strftime(tbuf,sizeof(tbuf),"%Z:%Y/%m/%d %H:%M:%S",gmtime(&times));
printf("%s\n",tbuf);                                                                                             // GMT:2010/10/14 02:59:41
strftime(tbuf,sizeof(tbuf),"%Z:%Y/%m/%d %H:%M:%S",localtime(&times));
printf("%s\n",tbuf);                                                                                             // CST:2010/10/14 10:59:41

因此讓使用者看到目前時間的整體流程圖大約如下所示:
Machine - time() -> time_t -> gmtime() or localtime() -> struct tm -> asctime() or strftime() -> User

[reference]
http://zh.wikipedia.org/zh-tw/UTC%2B8
http://en.wikipedia.org/wiki/Coordinated_Universal_Time
http://linux.vbird.org/linux_server/0440ntp.php
http://linux.die.net/


 

2010年9月5日 星期日

Makefile-把多個程式連結在一起的寫法

執行make的時候總會去找一個叫做Makefile的東西。
========
Makefile範例
====
CC = gcc
CFLAGS =
INCLUDE_PATH = .
SRCS = red.c blue.c
OBJS = $(SRCS:.c=.o)
PROGRAM = exe
all:
<TAB>make $(PROGRAM)
$(PROGRAM):$(OBJS)
<TAB>$(CC) $(OBJS) -o $@
%.o: %.c
<TAB>$(CC) $(CFLAGS) -I$(INCLUDE_PATH) -c $< -o $@
clean:
<TAB>rm -rf *.o
<TAB>rm -rf $(PROGRAM)
========
在執行make時會有這樣子的輸出:
make exe

make[1]: Entering directory `/usr/src/local/test/fp'
gcc -I. -c red.c -o red.o
gcc -I. -c blue.c -o blue.o
gcc red.o blue.o -o exe
make[1]: Leaving directory `/usr/src/local/test/fp'
 
在執行make clean時會有這樣子的輸出:
rm -rf *.o
rm -rf exe

函式指標(function pointer)-用途

我喜歡用"寫程式的先後順序"來理解 函式指標。

假設兩位程式設計師Red,Blue同時合作撰寫一個"顯示時間"的專案。
Red負責取得系統時間,Blue負責顯示(電子鐘,復古鐘,給寵物看的鐘,...whatever)。
這時候程式的寫法大約是這樣
========
Project : SHOW CLOCK IN VARIOUS TYPE
====
//blue.c written by blue
void blue_show_clock_digitial(time_t t){
  printf("Now:%02d:%02d:%02d\n",t.hour,t.min,t.second);
}
void blue_show_clock_1(time_t t){/*Not implemented yet*/}
void blue_show_clock_2(time_t t){/*Not implemented yet*/}

void blue_show_clock_3(time_t t){/*Not implemented yet*/}
//...//
====
//main.c written by red
time_t red_get_clock(){
  /*
   *Do something to get time
   */
  return time;

int main(){
  /* ... */
  time = red_get_clock();
  blue_show_clock_digital(time);// This function is implemented by programmer 'blue'
  /* ... */
}
========
這時候注意程式碼撰寫順序的問題。
理論上來說red所撰寫的程式未來幾乎是不太需要再做更改了,而blue所撰寫的程式卻有可能因為使用者的需求而有所變動。
當blue變動的時候,red就需要把blue_show_clock_digital(time)這一行改掉,改成blue所撰寫的函式(blue_show_clock_1()...等等)。

如果我是red,改個一兩次還沒關係,天天都來改的話,我一定會抓狂。(使用者一定會叫blue改,blue就叫我來改...)
每次blue改程式,我就要跟著改,但是事實上我red寫的程式核心幾乎沒有動耶。

這時候就是函式指標派上用途的時機了。看以下範例的改寫
========
Project : SHOW CLOCK IN VARIOUS TYPE by FUNCTION POINTER
====
//blue.c written by blue
void blue_show_clock_digitial(time_t t){
printf("Now:%02d:%02d:%02d\n",t.hour,t.min,t.second);
}
void blue_show_clock_1(time_t t){/*Not implemented yet*/}
void blue_show_clock_2(time_t t){/*Not implemented yet*/}
void blue_show_clock_3(time_t t){/*Not implemented yet*/}
//...//
red_display = blue_show_clock_digitial;
====
//main.c written by red
void (*red_display)(time_t);//red_display is a function pointer
time_t red_get_clock(){
/*
*Do something to get clock
*/
return time;
}
int main(){
/* ... */
time = red_get_clock();
red_display(time);// red_display is a function pointer
/* ... */
}
========
 red先宣告一個函式指標red_display(),然後請blue把這個函式指標red_display()納進blue.c裡面。
每次blue收到使用者需求需要新增一個新的函式時,就把這個新的函式assign給red宣告的函式指標red_display。
 
函式指標需要三個部份,說穿了他只是一個長的比較怪的參數型態:
void (*red_display) (time_t);
回傳值     函式指標名稱          函式參數型態
 
函數指標的assign是這樣:
red_display = blue_show_clock_digitial;
簡單來說就是把return type跟參數type跟括號通通拿掉,然後再用`=`連在一起就好囉。
比較標準的寫法是這樣  :
red_display = &blue_show_clock_digitial;
兩者皆可。


如此一來當使用者修改需求時,只需要blue更改blue.c裡面就可以囉。
對red來說,我以後都不需要再改跟我無關的程式。
對blue來說,比較不需要麻煩別人。
對其他人而言,程式的可讀性(red.c裡面的red_display()是固定的)和彈性(blue修改時不需要跟red同步)變的好多了。
 
這樣子的特性讓函式指標常常會用在做所謂callback function : 先實作的函式(red_display)"call back"後實作的函式(blue_show_clock_xxx)。
由於函數指標可以像是變數一樣,可以在執行時期才做assign的動作,所以就稱為late-binding,late到執行時期,才binding指定的函式。
在大型專案中就會常常發現,像是Linux核心內,隨便撿都有。