Apache APR可移植運行庫簡介(3)

 
1.4 應用APR
我們首先make install一下,比如我們在Makefile中指定prefix=$(APR)/dist,則make install後,在$(APR)/dist下會發現4個子目錄,分別爲bin、lib、include和build,其中我們感興趣的只有include和lib。下面是一個APR app的例子project。
該工程的目錄組織如下:
$(apr_path)
dist
    - lib
    - include
 - examples
    - apr_app
      - Make.properties
      - Makefile
      - apr_app.c
我們的Make.properties文件內容如下:
#
# The APR app demo
#
CC              = gcc -Wall
BASEDIR         = $(HOME)/apr-1.1.1/examples/apr_app
APRDIR          = $(HOME)/apr-1.1.1
APRVER          = 1
APRINCL         = $(APRDIR)/dist/include/apr-$(APRVER)
APRLIB          = $(APRDIR)/dist/lib
DEFS            = -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -D_DEBUG_
LIBS            = -L$(APRLIB) -lapr-$(APRVER) /
                  -lpthread -lxnet -lposix4 -ldl -lkstat -lnsl -lkvm -lz -lelf -lm -lsocket –ladm
INCL            = -I$(APRINCL)
CFLAGS          = $(DEFS) $(INCL)
Makefile文件內容如下:
include Make.properties
TARGET  = apr_app
OBJS    = apr_app.o
all: $(TARGET)
$(TARGET): $(OBJS)
        $(CC) ${CFLAGS} -o $@ $(OBJS) ${LIBS}
clean:
        rm -f core $(TARGET) $(OBJS)
apr_app.c文件採用的是$(apr_path)/test目錄下的proc_child.c文件。編譯運行一切OK。
1.5 APR的可移植性
正如前面所描述,APR的目前的首要目標就是設計爲一個跨平臺的通用庫,因此在APR的整個設計過程中無不體現了可移植的思想,APR附帶一個簡短的設計文檔,文字言簡意賅,其中很多的移植設計思想都值得我們所借鑑,主要從四個方面談。
1.5.1APR類型
爲了支持可移植性,APR中的一個策略就是儘量使用APR自定義的類型來代替平臺相關類型。這樣的好處很多,比如便於代碼移植,避免數據間進行不必要的類型轉換(如果你不使用APR自定義的數據類型,你在使用某些APR提供的接口時,就需要進行一些參數的類型轉換);自定義數據類型的名字更加具有自描述性,提高代碼可讀性。APR提供的基本自定義數據類型包括apr_byte_t,apr_int16_t,apr_uint16_t,apr_size_t等等。通常情況下這些類型都定義在apr.h中,不過你找遍整個APR包也不會找到apr.h這個文件,不過include目錄下倒是存在類似於apr.h的apr.h.in和apr.hw,這兩個文件是生成apr.h的模版,在apr.h.in中通用APR類型定義如下:
typedef unsigned char              apr_byte_t;
typedef @short_value@           apr_int16_t;
typedef unsigned @short_value@       apr_uint16_t;
typedef @int_value@              apr_int32_t;
typedef unsigned @int_value@           apr_uint32_t;
typedef @long_value@            apr_int64_t;
typedef unsigned @long_value@         apr_uint64_t;
typedef @size_t_value@            apr_size_t;
typedef @ssize_t_value@         apr_ssize_t;
typedef @off_t_value@            apr_off_t;
typedef @socklen_t_value@              apr_socklen_t;
@xxx@變量的值是可變的,不同的平臺其值可能不一樣。其值由configure配置過程自動生成,configue腳本中設置@xxx@變量的部分大致如下:
AC_CHECK_SIZEOF(char, 1)
AC_CHECK_SIZEOF(short, 2)
AC_CHECK_SIZEOF(int, 4)
AC_CHECK_SIZEOF(long, 4)
AC_CHECK_SIZEOF(long long, 8)
 
if test "$ac_cv_sizeof_short" = "2"; then
    short_value=short
fi
if test "$ac_cv_sizeof_int" = "4"; then
    int_value=int
fi
if test "$ac_cv_sizeof_int" = "8"; then
    int64_value="int"
    long_value=int
elif test "$ac_cv_sizeof_long" = "8"; then
    int64_value="long"
    long_value=long
elif test "$ac_cv_sizeof_long_long" = "8"; then
    int64_value="long long"
    long_value="long long"
elif test "$ac_cv_sizeof_longlong" = "8"; then
    int64_value="__int64"
    long_value="__int64"
else
    AC_ERROR([could not detect a 64-bit integer type])
fi
if test "$ac_cv_type_size_t" = "yes"; then
    size_t_value="size_t"
else
    size_t_value="apr_int32_t"
fi
if test "$ac_cv_type_ssize_t" = "yes"; then
    ssize_t_value="ssize_t"
else
    ssize_t_value="apr_int32_t"
fi
.. ..
Configure的具體的細節並不是本書描述的細節,如果你想了解更多細節,你可以去閱讀GNU的AutoConf、AutoMake等使用手冊。
不同的操作系統中各個變量的值如下表所示:
變量類型
硬件平臺
I686
I386
alpha
IA64
M68k
MIPS
Sparc
Spar64
apr_byte_t
1
1
1
1
1
1
1
1
apr_int16_t
2
2
2
2
2
2
2
2
apr_uint16_t
2
2
2
2
2
2
2
2
apr_int32_t
4
4
4
4
4
4
4
4
apr_uint32_t
4
4
4
4
4
4
4
4
apr_int64_t
4
4
8
8
4
4
4
4
apr_uint64_t
4
4
8
8
4
4
4
4
不過不同的操作系統中,定義各不相同,在Red Hat 9.0 Linux中,生成的定義如下:
typedef short                     apr_int16_t;        //16位整數
typedef unsigned short         apr_uint16_t;             //16位無符號整數
typedef int                         apr_int32_t;        //32位整數
typedef unsigned int              apr_uint32_t;             //32位無符號整數
typedef long long                            apr_int64_t;        //64位整數
typedef unsigned long long            apr_uint64_t;             //64位無符號整數
 
typedef size_t                          apr_size_t;          //
typedef ssize_t                        apr_ssize_t;
typedef off64_t                        apr_off_t;
typedef socklen_t                           apr_socklen_t;     //套接字長度
通用數據類型的另外一個定義之處就是文件apr_portable.h中,APR中提供了通用的數據類型以及對應的操作系統依賴類型如下表:
通用類型
含義
Win32類型
BEOS類型
UNIX
apr_os_file_t
文件類型
HANDLE
int
Int
apr_os_dir_t
目錄類型
HANDLE
dir
DIR
apr_os_sock_t
套接字類型
SOCKET
int
int
apr_os_proc_mutex_t
進程互斥鎖
HANDLE
apr_os_proc_mutex_t
pthread_mutex_t
apr_os_thread_t
線程類型
HANDLE
thread_id
pthread_t
apr_os_proc_t
進程類型
HANDLE
thread_id
pid_t
apr_os_threadkey_t
線程key類型
DWORD
int
pthread_key_t
apr_os_imp_time_t
 
FILETIME
struct timeval
struct timeval
apr_os_exp_time_t
 
SYSTEMTIME
struct tm
tm
apr_os_dso_handle_t
DSO加載
HANDLE
image_id
void*
apr_os_shm_t
共享內存
HANDLE
void*
void*
一旦定義了這些通用的數據類型,APR不再使用系統類型,而是上述的APR類型。不過由於系統底層仍然使用系統類型,因此在使用通用類型的時候一項必須的工作就是用實際的類型來真正替代通用類型,比如apr_os_file_t,如果是Win32平臺,則必須轉換爲HANDLE。對於上面表格的每一個通用數據類型,Apache都提供兩個函數支持這種轉換:
APR_DECLARE(apr_status_t) apr_os_XXX_get(…);
APR_DECLARE(apr_status_t) apr_os_XXX_put(…);
get函數用於將通用的數據類型轉換爲特定操作系統類型;而put函數則是將特定操作系統類型轉換爲通用數據類型。比如對於file類型,則對應的函數爲:
APR_DECLARE(apr_status_t) apr_os_file_get(apr_os_file_t *thefile,
apr_file_t *file);
APR_DECLARE(apr_status_t) apr_os_file_put(apr_file_t **file,
                                          apr_os_file_t *thefile,
                                          apr_int32_t flags, apr_pool_t *cont);
前者將通用的文件類型apr_os_file_t轉換爲特定操作系統類型apr_file_t,後者則是將apr_file_t轉換爲apr_os_file_t。
在後面的分析中我們可以看到,對於每一個組件類型,比如apr_file_t中都會包含系統定義類型,APR類型都是圍繞系統類型擴充起來的,比如apr_file_t,在Unix中爲:
struct apr_file_t
{
    int filedes;              //UNIX下的實際的文件系統類型
    … …
}
而在Window中則是:
struct apr_file_t
{
    HANDLE filedes;     //Window下的實際的文件系統類型
    ……
}
因此apr_os_file_get函數無非就是返回結構中的文件系統類型,而apr_os_file_put函數則無非就是根據系統文件類型創建apr_file_t類型。
類似的例子還有apr_os_thread_get和apr_os_thread_put等等。
1.5.2函數
APR中函數可以分爲兩大類:內部函數和外部接口函數。顧名思義,內部函數僅限於APR內部使用,外部無法調用;而外部結構函數則作爲API結構,由外部程序調用。
1.5.2.1內部函數
APR中所有的內部函數都以static進行修飾。通常理解static只是指靜態存儲的概念,事實上在裏面static包含了兩方面的含義。
      1)、在固定地址上的分配,這意味着變量是在一個特殊的靜態區域上創建的,而不是每次函數調用的時候在堆棧上動態創建的,這是static的靜態存儲的概念。
2)、另一方面,static能夠控制變量和函數對於連接器的可見性。一個static變量或者函數,對於特定的編譯單元來說總是本地範圍的,這個範圍在C語言中通常是指當前文件,超過這個範圍的文件或者函數是不可以看到static變量和函數的,因此編譯器也無法訪問到這些變量和函數,它們對編譯器是不可見的。因此內部函數是不允許被直接調用的,任何直接調用都導致“尚未定義”的錯誤。不過潛在的好處就是,內部函數的修改不影響API接口。
static的這兩種用法APR中都存在,但是第二種用法較多。
1.5.2.2外部API函數
對於APR用戶而言,它們能夠調用的只能是APR提供的API。要識別APR中提供的API非常的簡單,如果函數是外部API,那麼它的返回值總是用APR_DECLARE或者APR_DECLARE_NONSTD進行包裝,比如:
APR_DECLARE(apr_hash_t *) apr_hash_make(apr_pool_t *pool);
APR_DECLARE(int) apr_fnmatch_test(const char *pattern);
APR_DECLARE和APR_DECLARE_NONSTD是兩個宏定義,它們在apr.h中定義如下:
#define APR_DECLARE(type)            type
#define APR_DECLARE_NONSTD(type)     type
APR_DECLARE和APR_DECLARE_NONSTD到底是什麼意思呢?爲什麼要將返回類型封裝爲宏呢?在apr.h中有這樣的解釋:
/**
 * The public APR functions are declared with APR_DECLARE(), so they may
 * use the most appropriate calling convention. Public APR functions with
 * variable arguments must use APR_DECLARE_NONSTD().
 *
 * @remark Both the declaration and implementations must use the same macro.
 * @example
 */
/** APR_DECLARE(rettype) apr_func(args)
 * @see APR_DECLARE_NONSTD @see APR_DECLARE_DATA
 * @remark Note that when APR compiles the library itself, it passes the
 * symbol -DAPR_DECLARE_EXPORT to the compiler on some platforms (e.g. Win32)
 * to export public symbols from the dynamic library build./n
 * The user must define the APR_DECLARE_STATIC when compiling to target
 * the static APR library on some platforms (e.g. Win32.) The public symbols
 * are neither exported nor imported when APR_DECLARE_STATIC is defined./n
 * By default, compiling an application and including the APR public
 * headers, without defining APR_DECLARE_STATIC, will prepare the code to be
 * linked to the dynamic library.
 */
#define APR_DECLARE(type)            type
/**
 * The public APR functions using variable arguments are declared with
 * APR_DECLARE_NONSTD(), as they must follow the C language calling convention.
 * @see APR_DECLARE @see APR_DECLARE_DATA
 * @remark Both the declaration and implementations must use the same macro.
 * @example
 */
/** APR_DECLARE_NONSTD(rettype) apr_func(args, ...);
 */
#define APR_DECLARE_NONSTD(type)     type
從上面的解釋中我們可以看出“APR的固定個數參數公共函數的聲明形式APR_DECLARE(rettype) apr_func(args);而非固定個數參數的公共函數的聲明形式爲APR_DECLARE_NONSTD(rettype) apr_func(args, ...);”。
apr.h文件中解釋了這麼做就是爲了在不同平臺上編譯時使用“the most appropriate calling convention”,這裏的“calling convention”是一術語,翻譯過來叫“調用約定”。 我們知道函數調用是通過棧操作來完成的,在棧操作過程中需要函數的調用者和被調用者在下面的兩個問題上做出協調,達成協議:
a) 當參數個數多於一個時,按照什麼順序把參數壓入堆棧
b) 函數調用後,由誰來把堆棧恢復原來狀態
c) 產生函數修飾名的方法
在像C/C++這樣的中、高級語言中,使用“調用約定”來說明這兩個問題。常見的調用約定有:__stdcall、__cdecl、__fastcall、thiscall和naked call
__stdcall調用約定:函數的參數自右向左通過棧傳遞,被調用的函數在返回前清理傳送參數的內存棧。
__cdecl是C和C++程序的缺省調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。函數採用從右到左的壓棧方式。注意:對於可變參數的成員函數,始終使用__cdecl的轉換方式
__fastcall調用約定規定通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的內存棧)。
thiscall僅僅應用於"C++"成員函數。this指針存放於CX寄存器,參數從右到左壓。thiscall不是關鍵詞,因此不能被程序員指定。
naked call採用上述調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。
另外不同的調用約定對於函數內部修飾名處理的方法也不一樣。所謂修飾名是C或者C++函數在內部編譯和鏈接的時候產生的唯一的標識名稱。
對於C語言而言,__stdcall調用約定在輸出函數名前加上一個下劃線前綴,後面加上一個"@"符號和其參數的字節數,格式爲_functionname@number,例如 :function(int a, int b),其修飾名爲:_function@8
__cdecl調用約定僅在輸出函數名前加上一個下劃線前綴,格式爲_functionname。
__fastcall調用約定在輸出函數名前加上一個"@"符號,後面也是一個"@"符號和其參數的字節數,格式爲@functionname@number。
如果是C++,不同調用約定處理要稍微複雜一點。由於Apache是基於C語言開發,因此本處不再描述。
1.5.2.3內存池參數
關於函數的最後一個問題就是它的參數,如果函數內部需要分配空間,那麼你就可以看到參數的參數中肯定包含一個apr_pool_t參數,比如:
APR_DECLARE(apr_status_t) apr_shm_attach(apr_shm_t **m,
                                         const char *filename,
                                         apr_pool_t *pool);
由於Apache服務器所具有的一些特性,APR中並沒有使用普通的malloc/free內存管理策略,而是使用了自行設計的內存池管理策略。APR中所有的需要的內存都不再直接使用malloc分配,然後首先分配一塊足夠大的內存塊,然後每次需要的時候再從中獲取;當內存不再使用的時候也不是直接調用free,而是直接歸還給內存池。只有當內存池本身被釋放的時候,這些內存才真正的被free給操作系統。Apache中使用apr_pool_t描述一個內存池,因此毫無疑問,由於這種特殊的內存分配策略,對於任何一個函數,如果你需要使用內存,那麼你就應該指定內存所源自的內存池。這就是爲什麼大部分函數參數中都具有apr_pool_t的原因。關於內存池的詳細細節,我們在第二章詳細討論。
發佈了4 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章