C 函式庫:
(作者: 謝東翰)
--------------

A. 簡介:

由於我們的程式中經常需要處理許多特定的動作,例如搜尋與排序、行程管理與通訊、
檔案格式轉換、甚至繪圖 .... 等等,而這些動作可能很不單純,若每個動作細節都
從要從頭開始設計撰寫的話,往往相當耗時耗力,而且很不經濟。因此,程式設計師
便著手將程式中常常需要的一些動作分類、整理,將它們寫成一個個「函式庫」,供
程式開發者使用,而不必每次都重新發明輪子。因此,「函式庫」可以說是程式開發
工作中重要的寶藏,也許一般使用者並不覺得,但它們也是支持系統中所有應用程式
正常運作的背後無名英雄。

函式庫的種類相當繁多,而且用途各自不同。以 UNIX 系統而言,其最底層,而且也
最重要的函式庫,莫過於 C 函式庫了。由於 C 語言可以說是 UNIX 作業系統的母語,
早年當 UNIX 還在 AT&T 草創之初,為了讓整個系統在各硬體平台間的移植工作更為
容易,同時儘可能地保持系統的執行效能,才因應而開發了此程式語言,並將整個作
業系統以此語言重新改寫。目前據估計在一般的 UNIX 系統中約 90% 以上是由 C 或
C++ 程式碼寫成的,其中包括大部分的系統核心、系統函式庫、大部分的系統工具、
視窗系統等等皆是。

標準的 C 語言中並不包含如資料的輸出入、記憶體管理、及其他進階的系統服務 ....
等其他程式語言中常見的元件。相反的,C 語言將這些元件留給作業系統來實作,而
當我們的程式需要使用這些元件時,必須經由作業系統提供的 C 函式庫 (libc) 來取
得這些服務。因此,C 函式庫可以說是除了系統核心以外,所有應用程式賴以執行的
基底環境,同時它也是應用程式與系統核心溝通的橋樑。

一般而言,一個標準的 C 函式庫會包含兩類的函式群,一為與核心溝通的系統呼叫窗
口,二為一般常用的函式物件。「系統呼叫」可以說是應用程式可以由作業系統中取
得的最低階操作,是由系統核心直接提供服務,它提供一組系統呼叫介面供應用程式
使用,而系統呼叫的入口處就在 C 函式庫的某些函式中。因此,應用程式藉由連結 C
函式庫的這些系統呼叫窗口,進而與系統核心連繫。而一般常用的函式物件則包括字
元、字串處理、數學函式庫、搜尋排序演算法、標準輸出入 .... 等等。不論是系統
呼叫窗口或是常用的函式物件,為了達到程式在各 UNIX 平台的可移植性,這中間必
須要有一定的規格存在,供系統開發者依循。這些規格日後演生出許多不同的標準,
詳見後述。

除此之外,函式庫與應用程式間的連結形態可分為「靜態函式庫 (static library)」
與「可分享函式庫 (shared library)」兩種。前者只是單純將所有的函式與物件打包
成一個函式庫檔,而使用此函式庫的程式在編譯時,必須將其用到的所有函式與物件
的程式碼自函式庫檔抽出來,然後與程式本身的程式碼連結在一起,如此才能形成一
個完整的可執行檔。然而,由於函式庫中內含的函式與物件在許多程式中都會用到,如
果每個程式都必須連結一份所需的函式與物件時,顯然相當佔空間。因此,才有後者的
「可分享函式庫」的出現。

在支援分享式函式庫的系統環境下,程式編譯時不需要將所需的函式與物件靜態連結
進來,而是等到程式要執行時,才由作業系統自動做連結的動作。由於很多程式都會
用到相同的函式物件,故事實上作業系統在執行時只會在記憶體中保存一份函式物件
而已,當有程式在執行中要用到它時,就自動跳過來執行,執行完畢後再跳回原程式
中。因此,同一份函式物件可以讓所有的程式分享,這不儘可節省硬碟的儲存空間,
同時也節省執行時整體的記憶體使用量。

所有的 UNIX 系統都提供了它們的 C 函式庫,而在新近設計的 UNIX 系統中,多半會
有分享式函式庫的支援,但其形態可能各異,支援承度也有所不同。一般而言,可能以
UNIX 系統實驗室 (UNIX System Laboratories, USL) 所開發的 ELF (Executable and
Linking Format) 格式最為常見,而該格式也已被認可為「可移植系統」的標準之一,
就連使用其他形態的分享式函式庫的 UNIX 系統中,也開始提供與 ELF 格式相容的使
用及呼叫介面。

以下,我們就以 GNU/Linux 系統為主,介紹其 C 函式庫的規格與特色。



B. GLIBC 的規格:

在 GNU/Linux 系統中,其 C 函式庫的發展史點出了 GNU/Linux 演進的幾個重要里程
碑,由此可以突顯出 C 函式庫在系統中的地位與重要性。早期的 GNU/Linux 系統並
不支援可分享函式庫,因此所有的應用程式都是以靜態連結的方式存於系統中。直到
1995-1996 年 libc5 問世以後,系統才開始支援 ELF 可分享函式庫,同時該版的 C
函式庫也實作了其他 UNIX 上大部分的功能與函式群,可謂 GNU/Linux 發展上的一大
進步。

然而 libc5 在程式國際化 (I18N) 與本土化 (L10N) 方面的支援很差,故那個時候若
要開發所謂中文化的程式,就非得自行實作一套標準不可。直到一兩年後,GNU/Linux
換用了 GNU 所開發的 glibc-2.0 做為其 C 函式庫後,其國際化與本土化的支援才開
始起步,後來歷經 glibc-2.1,到了現在的 2.2 版,整個多國語文的開發環境才大至
成熟。

採用 glibc 做為系統的 C 函式庫,是 GNU/Linux 演進的一個重要里程碑。在 GNU
的計畫中,開發 glibc 原本是要給他們尚未問世的核心 GNU/Hurd 用的,由於當時幾
乎 99% 的 GNU 系統工具已開發完成,就獨缺核心 Hurd,而恰巧 Linux 核心在
Torvalts 的帶領下已逐漸成熟穩定,而且可以順利執行所有的 GNU 系統工具。故
GNU 團隊便順應 Linux 核心的特性,改寫了他們的 glibc,使其可以適用於 Hurd 核
心與 Linux 核心。如此,在這兩個平台上就有了一致的程式開發環境,使得所有的
GNU 程式可以在這兩個平台之間順利移植。

比起過去的 libc5,glibc 系列 (一般又稱之為 libc6) 除了有完整的國際化與本土
化支援外,同時還符合許多標準與規格,使得在 glibc 下開發的程式可以很容易移植
到其他 UNIX 平台去。這些標準包括:

1. ISO C:

   ISO C 是 International Standard for the C programming language 的縮寫,此
   標準明定了 C 語言的語法,標準 C 函式庫應具備那些標頭檔、巨集定義、函式與
   物件 .... 等等,幾乎在任何平台上的 C 語言 (包括非 UNIX 平台) 都支援此標準。

2. POSIX:

   POSIX 是 Portable Operating System Interface for Computer Environments 的
   縮寫,它是 ISO C 的延伸,明定了一個可移植的作業系統所應具備種種條件,其範
   圍不只有系統函式庫而已,還同時包括一些標準的工具程式、系統核心應有的特色
   與實作、以及在 C 函式庫中某些與作業系統相關的低階控制支援 (如系統呼叫窗口) 
   等等。由於 glibc 是完全按照 POSIX 的標準來實作的,同時搭配了符合 POSIX 標
   準的 Linux 核心,故在此環境下開發的程式可以做到完全符合 POSIX 的規格。

3. Berkeley Unix:

   Berkeley Unix 泛稱柏克萊大學所開發的 UNIX 系列作業系統,包括 4.2 BSD、
   4.3 BSD、4.4 BSD 以及早期的 SunOS。這些系統的 C 函式庫中有許多傑出的設
   計,但卻沒有在上述兩個標準中,包括 select() 函式、sockets .... 等等,這
   些在 glibc 中都有支援。

4. SVID:

   SVID 是 System V Interface Description 的縮寫,它是一份描述 AT&T UNIX
   System V 系統規格的文件,它是 POSIX 標準的延伸。Glibc 實作了大部分的
   SVID 規格要求,其中較重要的包括了行程之間的通訊標準以及分享式記憶體
   (shared memory),至於其他的部分則較不常使用。實作 SVID 主要的目的是希
   望可以做到與 UNIX System V 的相容與程式的可移植性。

5. XPG:

   XPG 是 X/Open Portability Guide 的縮寫,是由 X/Open Company, Ltd. 所發
   表,同時 X/Open 還擁有 UNIX 商標的版權。而這份規格不但是 POSIX 標準的擴
   充,同時也明定了一個 UNIX 作業系統所應符合的要求。其中包括了 iconv() 字
   集轉換介面,以及部分 BSD 與 SVID 的特色。

除了上述的規格外,glibc 還內含了 GNU 特有的特色,稱之為 GNU Extension。這
些特色在某些情況下可以方便程式的撰寫與維護,但就不見得可以移植到其他 UNIX
平台上,故在可移植性的考量下使用時必須留意。



C. GLIBC 的內容:

由於 glibc 囊括了幾乎所有的 UNIX 通行的標準,可以想見其內容包羅萬有。而就
像其他的 UNIX 系統一樣,其內含的檔案群分散於系統的樹狀目錄結構中,像一個支
架一般撐起整個作業系統。以 glibc-2.2 為例,這些檔案群主要包括:

1. 分享函式庫群:

   這是 glibc 的主體,分佈於 /lib 與 /usr/lib 中,包括 libc 標準 C 函式庫、
   libm 數學函式庫、libcrypt 加密與編碼函式庫、libdb 資料庫函式庫、libpthread
   行程多執行緒函式庫、libnss 網路服務函式庫 .... 等等。這些都是可分享函式
   庫,檔名都以 .so 做結尾。

   其中,/lib/ld*.so 是程式與函式庫連結的工具。有的用於程式編譯時將程式與函
   式庫內的函式物件連結,在只支援靜態連結的系統中,此連結方式就是直接將所需
   的物件自函式庫中抽出來與程式的可執行檔相連,而在支援可分享函式庫的系統中,
   在程式編譯時期的連結只是在執行檔中紀錄了那些函式物件是存在那個函式庫檔案
   中,等該程式開始執行時,則由另一個負責動態連結的 ld*.so 將所需的函式庫連
   結好並執行。

   一般而言,負責程式編譯時期的連結器檔名為 ld.so,而負責程式執行時的動態連
   結器檔名為 ld-<version>.so 或 ld-linux.so (在 GNU/Linux 系統中)。

2. 函式庫標頭檔與程式開發元件:

   這些標頭檔檔名都以 .h 為結尾,全部在 /usr/include/ 底下,其內容為函式庫中
   各函式的宣告、巨集定義、資料物件的型別 .... 等等,這些都是程式開發者不可
   或缺的部分。

   除此之外,在 /usr/lib/ 中還有若干 .o 與 .a 的檔案,這些是程式編譯過程中要
   連結為可執行檔時所需的元件,有些則為上述可分享函式庫的靜態連接版本,而後
   者可以在某些特殊場合下需要靜態連結程式時使用。

3. 函式庫說明文件:

   在一般的 UNIX 系統下,這些說明文件是放在 /usr/man 或 /usr/share/man 底下,
   統稱為 man pages,其底下還分若干章節,其中第二章 (man2) 講的是系統呼叫,
   而第三章 (man3) 講的就是 libc 標準函式庫,這些都是系統開發者重要的參考資
   料。

   而在 GNU 的系統中,除了 man pages 之外,還有一套稱為 info 的文件資料系統,
   而且裡頭的說明往往比 man pages 還要詳盡,這在 glibc 中也不例外。glibc 的
   info 文件位於 /usr/share/info/libc.info* ,本文中有許多素材就是取自這些
   文件的內容。

4. 字集轉換模組與區域化資料庫:

   這些是與程式國際化與本土化相關的部分,主要可分成四大塊: /usr/lib/gconv/
   內含大量的字集轉換模組,大部分是各種字集及編碼方式與系統的基底字集之間的
   轉換。第二塊是 /usr/lib/locale,內含以系統基底字集寫成的區域化資料庫
   (locale),像是 LC_CTYPE、LC_TIME .... 等等。第三塊是 /usr/share/locale/,
   內含可跨平台使用的區域化資料,主要是各應用程式的訊息翻譯部分。而最後一塊
   是 /usr/share/i18n/,其內容是各區域化資料庫的原始碼,以及系統支援的內碼
   對應表 .... 等等。

5. 時區資料庫:

   主要分部於 /usr/share/zoneinfo 底下,內含世界各地時區與格林威治時間的轉
   換資料。

6. 其他工具程式與設定檔:

   工具程式分佈在 /usr/bin 與 /sbin 底下,包括一些轉碼與區域化資料庫相關的
   程式如 iconv, locale, localedef 等,以及用來顯示應用程式與可分享函式庫相
   依關係的 ldd, 還有可分享函式庫搜尋路徑管理程式 ldconfig .... 等。而其相
   關的設定檔則位於 /etc 底下。


D. GLIBC 的函式物件:

從程式開發者的角度來看,glibc 內含的函式物件可分為底下幾大類,它們分別依據
上述的各種規格來實作的,同時系統呼叫的窗口也分佈在這些函式物件當中:

1. 錯誤處理常式: 包括函式錯誤偵測、錯誤訊號編碼 (errno) 與錯誤訊息顯示等。

2. 記憶體管理與陣列: 包括動態記憶體配置、字串處理、陣列資料處理等。

3. 程式國際化與區域化: 包括區域化資料庫 (locale) 的呼叫介面、字集編碼轉換、
	訊息翻譯的顯示、字元處理與分類等。

4. 輸入與輸出: 包括標準輸出入 (standard I/O)、檔案處理、低階輸出入系統呼叫等。

5. 行程管理與行程通訊: 包括子行程的產生、群組管理等函式群,而通訊方面則包括
	管道 (pipe, fifo)、sockets、分享記憶體、行程間訊號處理 .... 等等。

6. 系統服務: 包括取得作業系統資訊、可用資源分配管理、系統訊息輸出 (syslog)、
	使用者群組與權限管理、各種網路服務如 DNS、NIS .... 等等。

7. 其他函式元件: 包括各種搜尋與排序演算法、字串符號搜尋與正規化表示式、時間
	表示、數學函式群、字串加密演算法、資料庫系統、行程多執行緒支援 ....
	等等。



參考資料:

1. glibc man pages, info libc.

2. ELF 規格書:
   http://www.muppetlabs.com/~breadbox/software/ELF.txt
   http://www.mcsr.olemiss.edu/cgi-bin/man-cgi?elf+3

3. ISC 與 POSIX:
   http://anubis.dkuug.dk/JTC1/SC22/WG15/

4. XPG 規格書:
   http://www.linuxguruz.org/foldoc/foldoc.php?XPG