APR(Apache portable Run-time libraries,Apache可移植运行库)的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多
操作系统平台使用的底层支持接口库。在早期的Apache版本中,应用程序本身必须能够处理各种具体操作系统平台的细节,并针对不同的平台调用不同的处理函数。随着Apache的进一步开发,Apache组织决定将这些通用的函数独立出来并发展成为一个新的项目。这样,APR的开发就从Apache中独立出来,Apache仅仅是使用APR而已。目前APR主要还是由Apache使用,不过由于APR的较好的移植性,因此一些需要进行移植的C程序也开始使用APR,开源项目比如Flood loader tester(http://httpd.apache.org/test/flood/,该项目用于服务器压力测试,不仅仅适用于Apache)、FreeSwitch(www.freeswitch.org),JXTA-C(http://jxta-c.jxta.org,C版本的JXTA点对点平台实现);商业的项目则包括Blogline(http://www.bloglines.com/,covalent(http://www.covalent.net)等等。
APR使得平台细节的处理进行下移。对于应用程序而言,它们根本就不需要考虑具体的平台,不管是Unix、
linux还是Window,应用程序执行的接口基本都是统一一致的。因此对于APR而言,可移植性和统一的上层接口是其考虑的一个重点。而APR最早的目的并不是如此,它最早只是希望将Apache中用到的所有代码合并为一个通用的代码库,然而这不是一个正确的策略,因此后来APR改变了其目标。有的时候使用公共代码并不是一件好事,比如如何将一个请求映射到线程或者进程是平台相关的,因此仅仅一个公共的代码库并不能完成这种区分。APR的目标则是希望安全合并所有的能够合并的代码而不需要牺牲性能。
APR的最早的一个目标就是为所有的平台(不是部分)提供一个公共的统一操作函数接口,这是一个非常了不起的目的,当然也是不现实的一个目标。我们不可能支持所有平台的所有特征,因此APR目前只能为大多数平台提供所有的APR特性支持,包括Win32、OS/2、BeOS、Darwin、Linux等等。为了能够实现这个目标,APR开发者必须为那些不能运行于所有平台的特性创建了一系列的特征宏(FEATURE MACROS)以在各个平台之间区分这些特征。这些特征宏定义非常简单,通常如下:
APR_HAS_FEATURE
假如某个平台具有这个特性,则该宏必须设置为true,比如Linux和window都具有内存映射文件,同时APR提供了内存映射文件的操作接口,因此在这两个平台上,APR_HAS_MMAP宏必须设置,同时ap_mmap_*函数应该将磁盘文件映射为内存并返回适当的状态码。假如你的操作系统并不支持内存映射,那么APR_HAS_MMAP必须设置为0,而且所有的ap_mmap_*函数也可以不需要定义。第二步就是对于那些在程序中使用了不支持的函数必须提出警告。
目前APR中支持的基本类型包括下面几种:
表3-1 APR中支持的基本类型
类型名称
文件夹名称
描述
atomic
/srclib/apr/atomic
原子操作
dso
/srclib/apr/dso
动态加载共享库
file io
/srclib/apr/file_io
文件IO处理
mmap
/srclib/apr/mmap
内存映射文件
locks
/srclib/apr/locks
进程和线程互斥锁
memory
/srclib/apr/memory
内存池操作
network_io
/srclib/apr/network_io
网络IO处理
poll
/srclib/apr/poll
轮询IO
table
/srclib/apr/tables
Apache数组(堆栈)和表格以及哈希表
process
/srclib/apr/threadproc
进程和线程操作
user
/srclib/apr/user
用户和用户组操作
time
/srclib/apr/time
时间操作
string
/srclib/apr/strings
字符串操作
/srclib/apr/passwd
终端密码处理
misc
/srclib/apr/misc
大杂烩,不属于其余类的任何apr类型都可以放在里面
shmem
/srclib/apr/shmem
共享内存
random
/srclib/apr/random
随机数生成库
每一个APR的实现我们都在后面会具体描述。
1.2 APR版本规则
由于Apache组织的目标是将APR独立出来形成单独的第三方库,因此对其而言稳定的API接口就成为一个非常重要的必须考虑的方面。不过由于APR需要不断的往前方展,因此API接口的变化又是必然的趋势,因此如何平衡稳定性和变化性是APR开发者面临的一个极需解决的问题。为此APR采用了严格的版本规则来实现这一点。用户只需要简单的判定APR版本号,就可以很轻易确定当前版本的兼容性:向前兼容、向后兼容还是前后同时兼容。
1.2.1版本概述
APR中使用三个整数来记录APR版本号:MAJOR.MINOR.PATCH。MAJOR表示当前APR的主版本号,它的变化通常意味着APR的巨大的变化,比如体系结构的重新设计,API的重新设计等等,而且这种变化通常会导致APR版本的向前不兼容。MINOR称之为APR的次版本号,它通常只反映了一些较大的更改,比如APR的API的增加等等,但是这些更改并不影响与旧版本源代码和二进制代码之间的兼容性。PATCH通常称之为补丁版本,通常情况下假如只是对APR函数的修改而不影响API接口的话都会导致PATCH的变化。
目前为止APR的最高版本是1.2.2,最早遵循这种规则的版本号是0.9.0,不过在0.9.0之前,APR还推出了两个版本a8和a9。不过有一点需要注重的是,我们后面描述的版本规则并不适合1.0.0以前的版本。对于1.0.0以前的版本(0.x.y),APR提供的API是可以任意的改变而没有任何的限制,因此这些版本的变化不遵循后面描述的版本规则。从1.0.0以后的所有版本都遵循。切记。
除非主版本号发生变化,否则假如某个应用程序使用了低版本的APR,那么假如将该版本用高版本的APR替代,应用程序必须能够无错误的编译通过,通常我们称之为前向兼容行;反之很明显,假如应用程序中使用了高版本的APR,那么假如将该版本用低版本的APR替代,则未必能够编译通过,通常我们称之为后向不兼容。
APR的发展中力图总是保持与旧版本的源代码和二进制版本之间的兼容性。通过源代码兼容,应用程序就可以在使用新版本的APR进行编译的时候不会报错,这样应用程序就不需要为了适应新的APR而做出调整,从而保持应用开发的一致性和持续性。除非APR的主版本号发生变更。这种兼容性反之则不成立。假如一个应用程序使用较高的MINOR版本开发,那么很明显,假如将该版本替换为MINOR相对较低的版本进行编译,则成功的可能性应该不是很大。
除了源代码方面的兼容性,APR还希望能够保持二进制之间的兼容性。通过保持二进制兼容,应用程序可以直接使用高版本的APR库(或者是DLL,或者使so文件)替换低版本的库文件,而不需要做任何修改,就可以链接成功。与源代码兼容一样,二进制的兼容也是向前兼容,而不保证向后兼容。
下面的表格演示了APR的版本规则策略:
原始版本
新版本
兼容性
原因
2.2.3
2.2.4
前后兼容
PATCH版本号的变化保持前向和后向兼容
2.2.3
2.2.1
前后兼容
PATCH版本号的变化保持前向和后向兼容
2.2.3
2.3.1
向前兼容
次版本号的变化保持向前兼容,但并不保持向后兼容
2.2.3
2.1.7
不兼容
次版本号的降低不能保证向后兼容
2.2.3
3.0.0
不兼容
主版本号的变化不保证兼容性
2.2.3
1.4.7
不兼容
主版本号的变化不保证兼容性
1.2.2版本策略
为了控制APR接口的稳定,APR制定了严格的版本变化策略。
1.2.2.1PATCH版本策略
在前表中我们看到,PATCH的变化并不影响版本的源代码和二进制级别的兼容性,包括向前和向后兼容,因此,我们很轻易看出,PATCH版本变化通常意味着对版本的修修补补,即BUG的修复。这些工作通常被局限于函数内部的修改,或者是API函数内部,或者是APR内部static函数的变化。任何对API的增加、修改、删除都是不答应的。
1.2.2.1次版本号策略
任何新函数,新变量以及新常量的引入以及任何现有函数的废除都将可能导致次版本号的变化:
1)、新函数的引入
An
application coded against an older minor release will still have all of its functions available with their original signatures. Once an application begins to use a new function, however, they will be unable to work against older minor versions.
It is tempting to say that introdUCing new functions might create incompatibility across minor releases. If an application takes advantage of an API that was introduced in version 2.3 of a library, then it is not going to work against version 2.2. However, we have stated that an any application built against version 2.2 will continue to work for all 2.x releases. Thus, an application that states "requires 2.3 or later" is perfectly acceptable -- the user or administrator simply upgrades the installed library to 2.3. This is a safe
Operation and will not break any other application that was using the 2.2 library.
In other words, yes an incompatibility arises by mandating that a specific version needs to be installed. But in practice, this will not be a problem since upgrading to newer versions is always safe.
2)、新常量的引入
Similar to functions, all of the original (old) constants will be available to an application. An application can then choose to use new constants to pick up new semantics and features.
3)、函数替换
This gets a bit trickier. The original function must remain available at the link-level so that an application compiled against a minor version will continue to work with later minor versions. Further, if an application is designed to work with an earlier minor version, then we don't want to suddenly change the requirements for that application. This means that the headers cannot silently map an old function into a newer function, as that would turn an application, say, based on 1.2 into an application requiring the 1.4 or later release.
This means that functions cannot truly be replaced. The new, alternate function can be made available in the header and applications can choose to use it (and become dependent upon the minor release where the function appears).
It is possible to design a set of headers where a macro will always refer to the "latest" function available. Of course, if an application chooses to use this macro, then the resulting compiled-binary will be dependent upon whatever version it was compiled against. This strategy adds the new functionality for applications, yet retains the necessary source and binary compatibility for applications designed or built against previous minor releases.
Constants (enumerated values and preprocessor macros) are not allowed to change since an older application will still be using them. Similarly, function signatures at the link-level may not change, so that support for older, compiled applications is maintained.
4)、函数作废
随着APR的升级,APR中的一些API可能将作废,不再使用,但是这些API并不能从APR库中移除。因为一旦API被移除,向后兼容性将被破坏。因此我们能够做的仅仅是公布其作废。
If you deprecate a function in APR, please mark it as such in the function documentation, using the doxygen "/deprecated" tag. Deprecated functions can only be removed in major releases.
A deprecated function should remain available through the original header. The function prototype should remain in the same header, or if moved to a "deprecated functions" header, then the alternate header should be included by the original header. This requirement is to ensure that source compatibility is retained.
Finally, if you are deprecating a function so that you can change the name of the function, please use the method described above under "Replacing functions", so that projects which use APR can retain binary compatibility.
Note that all deprecated functions will be removed at the next major version bump.
1.2.2.3主版本号策略
下面的任何一种变化都将可能导致主版本号的变化:
1)、常量的移除或者更改
2)、函数移除或者作为
3)、fold together macro-ized function replacements
1.2.3版本检查
由于APR严格的版本控制策略,使得应用程序在使用APR库之前必须能够检测使用的APR库的版本号。APR答应在编译以及使用APR的时候检测它的版本号。
1.2.3.1 编译时版本检查
Libraries should make their version number available as compile-time constants. For example:
#define FOO_MAJOR_VERSION 1
#define FOO_MINOR_VERSION 4
#define FOO_PATCH_VERSION 0
The above symbols are the minimum required for this specification.
An application that desires, at compile-time, to decide on whether and how to use a particular library feature needs to only check two values: the major and the minor version. Since, by definition, there are no API changes across patch versions, that symbol can be safely ignored. Note that any kind of a check for a minimum version will then pin that application to at least that version. The application's installation mechanism should then ensure that that minimal version has been installed (for example, using RPM dependency checks).
If the feature changes across minor versions are source compatible, but are (say) simply different choices of values to pass into the library, then an application can support a wider variety of installed libraries if it avoids compile-time checks.
1.2.3.2 执行时版本检查
A library meeting this specification should support a way for an application to determine the library's version at
run-time. This will usually be emboded as a simple function which returns the MAJOR, MINOR, and PATCH tr
iplet in some form.
Run-time checks are preferable in all cases. This type of check enables an application to run against a wider variety of minor releases of a library (the application is "less coupled" to a particular library release). Of course, if an application requires a function that was introduced in a later, minor release, then the application will require that, at least, that release is installed on the target system.
Run-time checks are particurly important if the application is trying to determine if the library has a particular bug that may need to be worked around, but has been fixed in a later release. If the bug is fixed in a patch release, then the only avenue for an application is to perform a runtime check. This is because an application cannot require a specific patch level of the library to be installed -- those libraries are perfectly forward and backwards compatible, and the administrator is free to choose any patch release, knowing that all applications will continue to function properly. If the bug was fixed in a minor release, then it is possible to use a compile-time check, but that would create a tighter coupling to the library.
1.2.3.3 版本API
与前面的版本规则定义一致,APR中定义了数据结构apr_version_t来描述版本规则:
typedef struct {
int major; /**< major number */
int minor; /**< minor number */
int patch; /**< patch number */
int is_dev; /**< is development (1 or 0) */
} apr_version_t;
major是当前APR版本的主版本号,minor则是次版本号,patch对应的则是APR的补丁号。es_dev则描述了当前APR库的状态:开发版还是发行版,分别对应1和0。
一旦定义了apr_version_t结构,APR就将使用它作为基本的版本控制单位。APR中提供了函数apr_version和apr_version_string分别设置和返回APR的版本。
APR_DECLARE(void) apr_version(apr_version_t *pvsn)
{
pvsn->major = APR_MAJOR_VERSION;
pvsn->minor = APR_MINOR_VERSION;
pvsn->patch = APR_PATCH_VERSION;
#ifdef APR_IS_DEV_VERSION
pvsn->is_dev = 1;
#else
pvsn->is_dev = 0;
#endif
}
apr_version函数仅仅就是设置apr_version_t结构中的各个成员。对于每一个APR版本,APR都会将当前的版本号分别用三个常量定义在version.h中,比如,假如版本号是2.2.0,则常量定义应该如下:
#define APR_MAJOR_VERSION 2
#define APR_MINOR_VERSION 2
#define APR_PATCH_VERSION 0
apr_version_string函数仅仅是返回APR_VERSION_STRING宏,该宏定义为:
#define APR_VERSION_STRING /
APR_STRINGIFY(APR_MAJOR_VERSION) "." /
APR_STRINGIFY(APR_MINOR_VERSION) "." /
APR_STRINGIFY(APR_PATCH_VERSION) /
APR_IS_DEV_STRING
APR_STRINGIFY用于将给定的数值转换为字符串,因此APR_VERSION_STRING宏就是无非将APR_MAJOR_VERSION,APR_MINOR_VERSION,APR_PATCH_VERSION分别转换为字符串,再用"."连接起来,最后的形式应该为“2.2.0”。
尽管一般情况下,APR的版本号是“x.x.x”的格式,不过在Window的资源文件.rc中通常是“x,x,x”格式,因此APR中也提供了APR_VERSION_STRING_CSV来提供这种格式的版本号:
#define APR_VERSION_STRING_CSV APR_MAJOR_VERSION ##, /
##APR_MINOR_VERSION ##, /
##APR_PATCH_VERSION
##宏用于将变量与特定字符进行连接形成新的字符串。在后面的部分,我们会不断看到它的用法。
在一些情况下,应用程序需要使用的APR版本达到一定的版本号,为此,APR中提供了APR_VERSION_AT_LEAST宏用以检测给定的APR库是否达到给定的版本要求:
#define APR_VERSION_AT_LEAST(major,minor,patch) /
(((major) < APR_MAJOR_VERSION) /
((major) == APR_MAJOR_VERSION && (minor) < APR_MINOR_VERSION) /
((major) == APR_MAJOR_VERSION && (minor) == APR_MINOR_VERSION && (patch) <= APR_PATCH_VERSION))
假如我们希望当前使用的APR库的版本不的低于”1.2.0”,那么我们就使用使用APR_VERSION_AT_LEAST(1,2,0)对当前的APR库版本进行检查。
关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感爱好的朋友可以通过flydish1234 at sina.com.cn与之联系!
假如你觉得本文不错,请点击文后的“推荐本文”链接!!