编程接口指南

第 1 章 内存和 CPU 管理

本章从应用程序开发者的角度介绍了 Solaris 操作系统中的虚拟内存以及 CPU 管理。

内存管理接口

应用程序通过多组接口使用虚拟内存功能。本节概述了这些接口,另外还提供了接口用法的示例。

创建和使用映射

mmap(2) 可用于建立指定的文件系统对象到进程地址空间的映射。指定的文件系统对象也可部分映射到进程地址空间中。此基本内存管理接口非常简单。请使用 open(2) 打开文件,接着使用 mmap(2) 创建具有适当访问权限和共享选项的映射,随后再继续执行应用程序。

mmap(2) 所建立的映射可针对指定的地址范围替换先前所有的映射。

标志 MAP_SHAREDMAP_PRIVATE 可指定映射的类型。必须指定一种映射类型。如果设置了 MAP_SHARED 标志,则写入操作会修改映射对象。无需对对象进一步执行任何操作即可进行更改。如果设置了 MAP_PRIVATE 标志,则首次对映射区域进行写入操作将创建一个页面副本。所有进一步的写入操作都会引用该副本。只有修改了的页面会被复制。

映射类型可以跨 fork(2) 保留。

通过 mmap(2) 建立映射之后,将不再使用调用中所用的文件描述符。如果关闭文件,则映射在 munmap(2) 将其撤消之前会一直保留。创建新映射可以替换现有映射。

通过 truncate 调用可以截短映射文件。尝试访问不再存在的文件区域会导致产生 SIGBUS 信号。

映射 /dev/zero 可为调用程序提供零填充的虚拟内存块。块的大小在调用 mmap(2) 时指定。以下代码段说明了如何使用此技术在程序中创建零填充的存储块。块的地址由系统选择。

removed to fr.ch4/pl1.create.mapping.c

某些设备或文件仅当通过映射对其进行访问时才有用。用于支持位图显示的帧缓冲区设备便是此现象的一个示例。显示管理算法直接对显示地址执行操作时,实现这些算法会简单得多。

删除映射

munmap(2) 可用于删除调用进程的指定地址范围内的所有页面映射。munmap(2) 对已映射的对象没有任何影响。

高速缓存控制

SunOS 中的虚拟内存系统是一个高速缓存系统,处理器内存可在其中缓冲文件系统对象中的数据。系统提供了一些接口,用于控制或询问高速缓存的状态。

使用 mincore

mincore(2) 接口可确定内存页是否驻留在指定范围内的映射所涵盖的地址空间中。由于页面状态可能会在 mincore 检查页面之后和 mincore 返回数据之前发生更改,因此返回的信息可能会过时。只能保证将锁定的页面保留在内存中。

使用 mlockmunlock

mlock(3C) 会导致将指定地址范围内的页面锁定在物理内存中。在此进程或其他进程中引用锁定的页面不会导致需要 I/O 操作的页面错误。由于此 I/O 操作会干扰虚拟内存的正常操作,并且会降低其他进程的速度,因此仅有超级用户才能使用 mlock。内存中可锁定页数的限制取决于系统配置。如果超过此限制,则调用 mlock 将失败。

munlock 可用于释放对物理页的锁定。如果对单个映射的地址范围进行多次 mlock 调用,则一次 munlock 调用即可释放锁定。不过,如果 mlock 锁定了对相同页面的不同映射,则在释放对所有映射的锁定之前,不会解除对这些页面的锁定。

删除映射也会释放锁定,方法有两种:使用 mmap(2) 操作替换或者通过 munmap(2) 删除。

MAP_PRIVATE 映射关联的写复制事件会将对源页面的锁定传递到目标页面。这样,对包括 MAP_PRIVATE 映射的地址范围的锁定便会以透明方式与写复制重定向操作一起保留。有关此重定向操作的介绍,请参见创建和使用映射

使用 mlockallmunlockall

mlockall(3C)munlockall(3C)mlockmunlock 类似,但是 mlockallmunlockall 针对整个地址空间执行操作。mlockall 用于设置对地址空间中所有页面的锁定,munlockall 用于删除对该地址空间中所有页面的锁定,无论是通过 mlock 还是 mlockall 建立操作均如此。

使用 msync

msync(3C) 会导致指定地址范围内的所有已修改的页面都刷新到这些地址所映射的对象。此命令与 fsync(3C) 类似,后者对文件执行操作。

库级别动态内存

库级别动态内存分配为动态内存分配提供了一个易于使用的接口。

动态内存分配

最常用的接口包括:

其他动态内存分配接口包括 memalign(3C)valloc(3C)realloc(3C)

动态内存调试

Sun WorkShop 工具包有助于查找和消除在使用动态内存时出现的错误。Sun WorkShop 的运行时检查 (Run Time Checking, RTC) 工具使用本节中介绍的函数来查找使用动态内存时的错误。

RTC 不需要使用 -g 对程序进行编译以查找所有错误。不过,有时需要符号 (-g) 信息,以保证某些错误(尤其是从未初始化内存中读取的错误)的正确性。因此,如果没有可用的符号信息,则某些错误会受到抑制。这些错误包括 rui(针对 a.out)和 rui + aib + air(针对共享库)。可以使用 suppressunsuppress 来更改此行为。

check -access

-access 选项可用于启动访问检查。 RTC 将报告以下错误:

baf

错误释放

duf

重复释放

maf

未对齐的释放

mar

未对齐的读取

maw

未对齐的写入

oom

内存不足

rua

从未分配的内存读取

rui

从未初始化的内存读取

rwo

写入只读内存

wua

写入未分配的内存

缺省行为是在检测到每个访问错误之后停止进程。可以使用 rtc_auto_continue dbxenv 变量更改此行为。如果设置为 on,则 RTC 会将访问错误记录到文件中。文件名由 rtc_error_log_file_name dbxenv 变量的值确定。缺省情况下,仅在每个唯一的访问错误首次发生时报告该错误。可使用 rtc_auto_suppress dbxenv 变量更改此行为。此变量的缺省设置为 on

check -leaks [-frames n] [-match m]

-leaks 选项可用于启动泄漏检查。 RTC 将报告以下错误:

aib

可能的内存泄漏-仅有的指针指向块的中间位置

air

可能的内存泄漏-指向块的指针仅存在于寄存器中

mel

内存泄漏-没有指向块的指针

启动泄漏检查之后,在程序退出时会获取自动生成的泄漏报告。此时会报告包括潜在泄漏在内的所有泄漏。缺省情况下,将生成非详细报告。此缺省行为由 dbxenv rtc_mel_at_exit 控制。不过,可以随时要求提供泄漏报告。

报告泄漏时,-frames n 最多可显示 n 个不同的栈帧。-match m 变量用于合并泄漏。如果进行分配时两个或多个泄漏的调用栈与 m 帧匹配,则会在单个合并的泄漏报告中报告这些泄漏。n 的缺省值是 8 或 m 值之间的较大者。n 的最大值为 16。m 的缺省值为 2。

check -memuse [-frames n] [-match m]

-memuse 选项可用于启动内存使用 (memuse) 检查。如果使用 check -memuse,则表示同时启用了 check -leaks。除程序退出时获取泄漏报告之外,您还会获得一个列出使用中的块 (biu) 的报告。缺省情况下,将生成有关使用中的块的非详细报告。此缺省行为由 dbxenv rtc_biu_at_exit 控制。在程序执行过程中,可随时查看程序中的内存所分配到的位置。

-frames n-match m 变量的作用如以下部分所述。

check -all [-frames n] [-match m]

check -access; check -memuse [-frames n] [-match m] 等效。rtc_biu_at_exit dbxenv 变量的值不会随 check -all 进行更改。因此,缺省情况下,退出时不会生成任何内存使用报告。

check [funcs] [files] [loadobjects]

funcs files loadobjects 中的 check -all; suppress all; unsuppress all 等效。使用此选项可以将 RTC 重点用于所需的位置。

其他内存控制接口

本节介绍其他内存控制接口。

使用 sysconf

sysconf(3C) 可用于返回与系统相关的内存页的大小。为便于移植,应用程序不应嵌入用于指定页面大小的任何常量。请注意,即使在相同指令集的执行中,变化的页面大小也很常见。

使用 mprotect

mprotect(2) 可用于为指定地址范围内的所有页面提供指定的保护。保护不能超出底层对象所允许的权限。

使用 brksbrk

中断点 (break) 是进程映像中栈外部的最大的有效数据地址。程序开始执行时,execve(2) 通常会将中断点 (break) 值设置为程序及其数据存储所定义的最大地址。

使用 brk(2) 可将中断点 (break) 设置为更大的地址。您还可以使用 sbrk(2) 向进程的数据段中添加一个存储增量。通过调用 getrlimit(2) 可以获取数据段的最大可能大小。

caddr_t

 brk(caddr_t addr);



 caddr_t

 sbrk(intptr_t incr); 

brk 可用于将调用方未使用的最低数据段位置标识为 addr。此位置会向上舍入为系统页面大小的下一个倍数。

备用接口 sbrk 可用于向调用方数据空间中添加 incr 个字节,并返回指向新数据区域开头的指针。

CPU 性能计数器

本节介绍了使用 CPU 性能计数器 (CPU Performance counters, CPC) 时用到的开发者接口。Solaris 应用程序可以使用与底层计数器体系结构无关的 CPC。

libcpc 的 API 附加功能

本节介绍 libcpc(3LIB) 库的最新附加功能。有关旧接口的信息,请参见 libcpc 手册页。

初始化接口

准备使用 CPC 工具的应用程序会通过调用 cpc_open() 函数来初始化该库。此函数会返回一个供其他接口使用的 cpc_t * 参数。cpc_open() 函数的语法如下:

cpc_t*cpc_open(intver);

ver 参数的值可标识应用程序所使用的接口的版本。如果底层计数器无法访问或不可用,则 cpc_open() 函数将失败。

硬件查询接口

uint_t cpc_npic(cpc_t *cpc);

uint_t cpc_caps(cpc_t *cpc);

void cpc_walk_events_all(cpc_t *cpc, void *arg,

          void (*action)(void *arg, const char *event));

void cpc_walk_events_pic(cpc_t *cpc, uint_t picno, void *arg, 

          void(*action)(void *arg, uint_t picno, const char *event));

void cpc_walk-attrs(cpc_t *cpc, void *arg,

          void (*action)(void *arg, const char *attr));

cpc_npic() 函数可返回底层处理器上的物理计数器的数量。

cpc_caps() 函数可返回 uint_t 参数,此参数的值是基于底层处理器支持的功能执行按位或运算的结果。共有两项功能。CPC_CAP_OVERFLOW_INTERRUPT 功能,允许处理器在计数器溢出时产生中断;CPC_CAP_OVERFLOW_PRECISE 功能,允许处理器确定哪一个计数器产生溢出中断。

内核可维护底层处理器支持的事件的列表。单个芯片上的不同物理计数器不必使用相同的事件列表。cpc_walk_events_all() 函数可针对每个处理器支持的事件调用 action() 例程,而不用考虑物理计数器。cpc_walk_events_pic() 函数可针对特定物理计数器上每个处理器支持的事件调用 action() 例程。这两个函数都会将未解释的 arg 参数从调用方传递到每个 action() 函数调用。

平台可维护底层处理器支持的属性的列表。利用这些属性,可以访问特定于处理器的高级性能计数器功能。cpc_walk_attrs() 函数可针对每个属性名称调用操作例程。

配置接口

cpc_set_t *cpc_set_create(cpc_t *cpc);

int cpc_set_destroy(cpc_t *cpc, cpc_set_t *set);

int cpc_set_add_request(cpc_t *cpc, cpc_set_t *set, const char *event,

          uint64_t preset, uint_t flags, uint_t nattrs,

          const cpc_attr_t *attrs);

int cpc_set_request_preset(cpc_t *cpc, cpc_set_t *set, int index,

          uint64_t preset);

不透明数据类型 cpc_set_t 表示请求的集合。这些集合称为集。cpc_set_create() 函数可用于创建空集。cpc_set_destroy() 函数可销毁一个集,并释放该集使用的所有内存。销毁集将释放该集使用的硬件资源。

cpc_set_add_request() 函数可用于向集中添加请求。以下列出了请求的参数。

event

用于指定要进行计数的事件的名称的字符串。

preset

用作计数器初始值的 64 位无符号整数。

flags

应用于一组请求标志的逻辑或运算的结果。

nattrs

attrs 指向的数组中的属性个数。

attrs

指向 cpc_attr_t 结构数组的指针。

以下列出了有效的请求标志。

CPC_COUNT_USER

利用此标志,当 CPU 在用户模式下执行时可对出现的事件进行计数。

CPC_COUNT_SYSTEM

利用此标志,当 CPU 在特权模式下执行时可对出现的事件进行计数。

CPC_OVF_NOTIFY_EMT

此标志可请求在硬件计数器溢出时发出通知。

CPC 接口可将属性作为 cpc_attr_t 结构数组来进行传递。

cpc_set_add_request() 函数成功返回时,它将返回一个索引。此索引会引用通过调用 cpc_set_add_request() 函数时添加的请求所生成的数据。

cpc_set_request_preset() 函数可更改请求的预设值。通过更改可将新的预设值与溢出集重新绑定在一起。

cpc_walk_requests() 函数可针对 cpc_set_t 中的每个请求调用用户提供的 action() 例程。arg 参数的值会在未进行解释的情况下传递给用户例程。应用程序使用 cpc_walk_requests() 函数可列显集中每个请求的配置。cpc_walk_requests() 函数的语法如下:

void cpc_walk_requests(cpc_t *cpc, cpc_set_t *set, void *arg,

void (*action)(void *arg, int index, const char *event,

uint64_t preset, uint_t flags, int nattrs,

            const cpc_attr_t *attrs));

绑定

本节中的接口可将集中的请求绑定到物理硬件,并将计数器设置到起始位置。

int cpc_bind_curlwp(cpc_t *cpc, cpc_set_t *set, uint_t flags);

int cpc_bind_pctx(cpc_t *cpc, pctx_t *pctx, id_t id, cpc_set_t *set,

          uint_t flags);

int cpc_bind_cpu(cpc_t *cpc, processorid_t id, cpc_set_t *set, 

          uint_t flags);

int cpc_unbind(cpc_t *cpc, cpc_set_t *set);

cpc_bind_curlwp() 函数可将集绑定到调用者 LWP。该集的计数器将虚拟化为到此 LWP 上,并计算调用者 LWP 运行时 CPU 上发生的事件的数量。cpc_bind_curlwp() 例程唯一的有效标志为 CPC_BIND_LWP_INHERIT

cpc_bind_pctx() 函数可将集绑定到使用 libpctx(3LIB) 捕获的进程中的 LWP。此函数没有任何有效标志。

cpc_bind_cpu() 函数可将集绑定到在 id 参数中指定的处理器。将集绑定到 CPU 会使系统中的现有性能计数器上下文无效。此函数没有任何有效标志。

cpc_unbind() 函数可停止性能计数器并释放与绑定集关联的硬件。如果将集绑定到 CPU,则 cpc_unbind() 函数将从该 CPU 解除绑定 LWP 并释放 CPC 伪设备。

抽样

利用本节中介绍的接口,可以将数据从计数器返回到应用程序。计数器数据驻留在名为 cpc_buf_t 的不透明数据结构中。此数据结构可用于捕获绑定集正在使用的计数器的状态快照,并且包括以下信息:

cpc_buf_t *cpc_buf_create(cpc_t *cpc, cpc_set_t *set);

int cpc_buf_destroy(cpc_t *cpc, cpc_buf_t *buf);

int cpc_set_sample(cpc_t *cpc, cpc_set_t *set, cpc_buf_t *buf);

cpc_buf_create() 函数可创建一个缓冲区,用于存储在 cpc_set_t 中指定的集中的数据。cpc_buf_destroy() 函数可释放与给定的 cpc_buf_t 关联的内存。cpc_buf_sample() 函数可捕获正在代表指定集进行计数的计数器的快照。指定集必须已绑定并且创建了缓冲区,然后才能调用 cpc_buf_sample() 函数。

在缓冲区中抽样不会更新与该集关联的请求的预设值。使用 cpc_buf_sample() 函数对某个缓冲区进行抽样,然后解除绑定并再次绑定时,计数将从请求的预设值开始,该预设值与初始 cpc_set_add_request() 函数调用中的值相同。

缓冲区操作

利用以下例程,可以访问 cpc_buf_t 结构中的数据。

int cpc_buf_get(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val);

int cpc_buf_set(cpc_t *cpc, cpc_buf_t *buf, int index, uint64_t *val);

hrtime_t cpc_buf_hrtime(cpc_t *cpc, cpc_buf_t *buf);

uint64_t cpc_buf_tick(cpc_t *cpc, cpc_buf_t *buf);

int cpc_buf_sub(cpc_t *cpc, cpc_buf_t *result, cpc_buf_t *left

      cpc_buf_t *right);

int cpc_buf_add(cpc_t *cpc, cpc_buf_t *result, cpc_buf_t *left,

      cpc_buf_t *right);

int cpc_buf_copy(cpc_t *cpc, cpc_buf_t *dest, cpc_buf_t *src);

void cpc_buf_zero(cpc_t *cpc, cpc_buf_t *buf);

cpc_buf_get() 函数可检索 index 参数所标识的计数器的值。index 参数是指在绑定集之前由 cpc_set_add_request() 函数返回的值。cpc_buf_get() 函数可将计数器的值存储在 val 参数所指示的位置。

cpc_buf_set() 函数可设置 index 参数所标识的计数器的值。index 参数是指在绑定集之前由 cpc_set_add_request() 函数返回的值。cpc_buf_set() 函数可将计数器的值设置为位于 val 参数所指示的位置的值。cpc_buf_get() 函数和 cpc_buf_set() 函数均不会更改对应的 CPC 请求的预设值。

cpc_buf_hrtime() 函数可返回高精度时间标记,用于指示对硬件进行抽样的时间。cpc_buf_tick() 函数可返回 LWP 运行时已经过的 CPU 时钟周期的数量。

cpc_buf_sub() 函数可计算在 leftright 参数中指定的计数器与周期值之间的差值。cpc_buf_sub() 函数可将结果存储在 result 中。cpc_buf_sub() 函数的给定调用必须确保所有 cpc_buf_t 值都源自同一 cpc_set_t 结构。result 索引包含缓冲区中每个请求索引的 left - right 计算的结果。结果索引还包含 tick 的差。cpc_buf_sub() 函数可用于将目标缓冲区的高精度时间标记设置为 leftright 缓冲区的最新时间。

cpc_buf_add() 函数可计算在 leftright 参数中指定的计数器和周期值的总和。cpc_buf_add() 函数可将结果存储在 result 中。cpc_buf_add() 函数的给定调用必须确保所有 cpc_buf_t 值都源自同一 cpc_set_t 结构。result 索引包含缓冲区中每个请求索引的 left + right 计算的结果。结果索引还包含 tick 的总和。cpc_buf_add() 函数可用于将目标缓冲区的高精度时间标记设置为 leftright 缓冲区的最新时间。

使用 cpc_buf_copy() 函数时 destsrc 相同。

cpc_buf_zero() 函数可将 buf 中的所有内容都设置为零。

激活接口

本节介绍 CPC 的激活接口。

int cpc_enable(cpc_t *cpc);

int cpc_disable(cpc_t *cpc);

这两个接口可分别启用和禁用绑定到正在执行的 LWP 的任意集的计数器。应用程序使用这些接口可以指定所需的代码,同时使用 libpctx 将计数器配置延迟到某个控制进程。

错误处理接口

本节介绍 CPC 的错误处理接口。

typedef void (cpc_errhndlr_t)(const char *fn, int subcode, const char *fmt,

          va_list ap);

void cpc_seterrhndlr(cpc_t *cpc, cpc_errhndlr_t *errhndlr);

这两个接口允许传递 cpc_t 句柄。除了一个字符串之外,cpc_errhndlr_t 句柄还采用一个整型子代码。整型 subcode 用于描述 fn 参数所引用的函数所遇到的特定错误。通过整型 subcode,应用程序可轻易识别各种错误情况。fmt 参数的字符串值包含对错误子代码的国际化说明,并且适用于列显。