我們首先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 ?Cladm
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
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類型
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的原因。關于內存池的具體細節,我們在第二章具體討論。
關于作者
張中慶,目前主要的研究方向是嵌入式瀏覽器,移動中間件以及大規模服務器設計。目前正在進行Apache的源代碼分析,計劃出版《Apache源代碼全景分析》上下冊。Apache系列文章為本書的草案部分,對Apache感愛好的朋友可以通過flydish1234 at sina.com.cn與之聯系!
假如你覺得本文不錯,請點擊文后的“推薦本文”鏈接!!