通用编程概念:编写并调试程序
用户定义的 malloc 替换
用户可用他们自己的设计来替换内存子系统(malloc、calloc、realloc、free、mallopt 和 mallinfo 子系统。
现存的内存子系统对有线程和没有线程的应用程序都有作用。用户定义的内存子系统必须是线程安全的,以便它在线程和非线程进程中都能起作用。因为没有用来验证的检查,如果在线程应用程序中装入一个非线程安全的内存模块,可能会毁坏内存和数据。
用户定义的内存子系统的 32 和 64 位对象必须置于归档中,32 位共享对象名为 mem32.o 而 64 位共享对象名为 mem64.o。
用户共享的对象必须导出以下符号:
- __malloc__
- __free__
- __realloc__
- __calloc__
- __mallinfo__
- __mallopt__
- __malloc_init__
- __malloc_prefork_lock__
- __malloc_postfork_unlock__
用户共享的对象可选导出以下符号:
- __malloc_once__
如果不存在入口点,执行不会停止。
函数定义如下:
-
void *__malloc__(size_t) :
- 对于用户该函数为 malloc 子例程的等价物。 void __free__(void *) :
- 对于用户该函数与 free 子例程等价。 void *__realloc__(void *, size_t) :
- 对于用户该函数与 realloc 子例程等价。 void *__calloc__(size_t, size_t) :
- 对于用户该函数与 calloc 子例程等价。 int __mallopt__(int, int) :
- 对于用户该函数与 mallopt 子例程等价。 struct mallinfo __mallinfo__() :
- 对于用户该函数与 mallinfo 子例程等价。 void __malloc_once__()
- 该函数在调用任何用户定义的 molloc 入口点前被调用一次。
以下函数被线程子系统用于管理多线程环境中的用户定义的内存子系统。它们仅在应用程序和/或用户定义的模块与 libpthreads.a 绑定时才被调用。即使用户定义的子系统不是线程安全的且没有与 libpthreads.a 绑定,还是必须定义并导出这些函数。否则将不会装入该对象。
-
void __malloc_init__(void)
- 由 pthread 初始化例程调用。该函数用于初始化线程用户内存子系统。大多数情况下,这包括创建并初始化某些锁定数据的格式。即使用户定义的内存子系统与 libpthreads.a 绑定,用户定义的内存子系统 必须在调用 __malloc_init__() 之前运行。 void __malloc_prefork_lock__(void)
- 在调用 fork 子例程时由 pthread 调用。该函数用于确保内存子系统在 fork() 前处于已知的状态,并保持该状态直到 fork() 返回。大多数情况下这包括获取内存子系统的锁定。 void __malloc_postfork_unlock__(void)
- 在调用 fork 子例程时由 pthread 调用。该函数用于建立可在 fork 后的父代与子代中使用的内存子系统。这应该撤销由 __malloc_prefork_lock__ 所做的工作。大多数情况下,这包括释放内存子系统的锁定。
所以这些函数必须被从共享模块导出。对于位于归档中的 32 和 64 位实现必须存在独立的模块。例如:
- mem.exp 模块:
__malloc__ __free__ __realloc__ __calloc__ __mallopt__ __mallinfo__ __malloc_init__ __malloc_prefork_lock__ __malloc_postfork_unlock__ __malloc_once__
- mem_functions32.o 模块:
包含全部所需的 32 位函数
- mem_functions64.o 模块:
包含全部所需的 64 位函数
以下是创建共享对象的示例:-lpthreads 参数只有在对象使用 pthread 函数时才需要。
- 创建 32 位共享对象:
ld -b32 -m -o mem32.o mem_functions32.o / -bE:mem.exp / -bM:SRE -lpthreads -lc
- 创建 64 位共享对象:
ld -b64 -m -o mem64.o mem_functions64.o / -bE:mem.exp / -bM:SRE -lpthreads -lc
- 创建归档(对于 32 位对象,共享对象名称必须为 mem32.o,而对于 64 位对象为 mem64.o):
ar -X32_64 -r archive_name mem32.o mem64.o
启用用户定义的内存子系统
可通过使用以下之一来启用用户定义的内存子系统:
- MALLOCTYPE 环境变量
- 用户应用程序中的 _malloc_user_defined_name 全局变量
要使用 MALLOCTYPE 环境变量,包含用户定义的内存子系统的归档通过将 MALLOCTYPE 设为 user:archive_name 来指定,其中 archive_name 在应用程序的库路径或 LIBPATH 环境变量内所指定的路径中。
要使用 _malloc_user_defined_name 全局变量,用户应用程序必须如下声明该全局变量:
char *_malloc_user_defined_name="archive_name"
其中 archive_name 必须在应用程序的库路径或 LIBPATH 环境变量中所指定的路径中。
- 运行 setuid 应用程序时忽略 LIBPATH 环境变量,这样归档肯定在应用程序的库路径中。
- archive_name 不可能包含路径信息。
- MALLOCTYPE 环境变量和 _malloc_user_defined_name 全局变量都被用于指定 archive_name 时,由 MALLOCTYPE 指定的归档将覆盖由 _malloc_user_defined_name 指定的那个。
32 和 64 位注意事项
如果归档没有包含 32 位和 64 位两种共享对象,且使用 MALLOCTYPE 环境变量启用了用户定义的内存子系统,在执行 32 位应用程序的 64 位进程以及 64 位应用程序的 32 位进程时将出现问题。当使用 exec 子例程创建新进程时,该进程将继承调用应用程序的环境。这意味着 MALLOCTYPE 环境变量将被继承,且新的进程将试图装入用户定义的内存子系统。如果该程序类型的归档成员不存在,则装入失败,且新进程将退出。
线程注意事项
所提供的全部函数必须在多线程环境中运行。即使模块与 libpthreads.a 相链接,至少__malloc__() 必须在调用 __malloc_init__() 和初始化 pthread 之前运行。需要这样做的原因是因为 pthread 的初始化在调用 __malloc_init__() 之前需要使用 malloc()。
全部所提供的内存函数必须在线程和非线程两个环境中运行。__malloc__() 函数应该能够顺利运行,而没有任何对 __malloc_init__() 的相关性(即,__malloc__() 一开始就应该假设 __malloc_init__() 还没有运行。)在 __malloc_init__() 完成后,__malloc__() 可信赖由 __malloc_init__() 完成的任何工作。需要这样做的原因是因为 pthread 的初始化在调用 __malloc_init__() 之前要使用 malloc()。
提供了以下变量以防止调用不需要的线程相关例程:
- __multi_threaded 变量为零,直到一个线程被创建,此时它变为非零且对与那个进程不再被重新置零。
- __n_pthreads 变量为 -1,直到 pthread 被初始化,此时它被设为 1。从这一点开始它对活动的线程数量计数。
示例:
如果 __malloc__() 使用 pthread_mutex_lock(),代码可能看起来相似于如下:
if (__multi_threaded) pthread_mutex_lock(mutexptr); /* ..... work ....... */ if (__multi_threaded) pthread_mutex_unlock(mutexptr);
在此例中防止 __malloc__() 在完全初始化 pthread 之前执行 pthread 函数。因为要到另一个线程启动后才完成锁定,单线程应用程序也可得到加速。
局限性
因为初始化以及 libC.a 和 libc.a 内存子系统的相关性,不支持以 C++ 编写的内存子系统。
因为 setlocale 子例程使用 malloc() 来初始化语言环境,所以错误消息不会被翻译。如果 malloc() 失败,则 setlocale 子例程无法完成,且应用程序仍然位于 POSIX 语言环境中。因此,只显示缺省的英语消息。
现存的静态构建的程序不经重新编译不能使用用户定义的内存子系统。
错误报表
第一次调用 malloc 子例程时,将装入由 MALLOCTYPE 环境变量定义的归档中 32 或 64 位对象。如果装入失败则显示消息,且应用程序退出。如果装入成功,则将试图验证全部所需的符号都存在。如果缺少任何符号,则将终止应用程序并显示所缺符号的列表。