编写访问大文件的程序
AIX支持大于 2 GB 的文件。 本节辅助控件员理解大文件应用的含义,并帮助他们修改这些文件的应用。 通过编程接口,可以对应用程序进行修改以使其识别大文件。 文件系统编程接口通常基于 off_t 数据类型。
现有程序的影响
所有应用程序在 AIX 4.2 之前使用的 32 位应用程序环境保持不变。 但是,现有的应用程序无法处理大文件。
例如,stat 结构中用于返回文件大小的 st_size 字段为有符号、32 位长整型。 因此,该 stat 结构无法用来返回大小超过 LONG_MAX 文件。 如果应用程序试图使用 stat 子例程处理大于 LONG_MAX 的文件,那么 stat 子例程将会失败,错误号将设为 EOVERFLOW,指示文件大小溢出程序正在使用的结构的大小字段。
此行为很重要,因为即使与文件大小不相关,那些看起来可能对大文件不起作用的现有程序将发生失败。
如果需要返回的值大于程序正在使用的数据类型和结构,错误号 EOVERFLOW 也可能由 lseek 指针和 fcntl 子例程返回。 对于 lseek,如果结果偏移量大于 LONG_MAX,那么 lseek 会失败,错误号将设为 EOVERFLOW。 对于 fcntl 子例程,如果调用程序使用 F_GETLK,且块锁定的起始偏移量和长度大于 LONG_MAX,那么 fcntl 调用将失败,错误号将设为 EOVERFLOW。
开放式保护
如果允许应用程序处理大文件,那么许多现有的应用程序可能会发生意外行为(包括数据损坏)。 AIX 使用开放式保护方案来保护应用程序免受此类故障的影响。
除开放式保护外,许多其他的子例程通过提供执行环境来提供保护,该环境与开发这些程序所处于的环境相同。 如果应用程序使用 write 系列子例程,并且 write 请求超出 2 GB 边界,那么 write 子例程将仅传输最多 2 GB 减去 1 的数据。 如果应用程序试图写入2GB -1边界或超出该边界,写入子程序将失败并将 errno 设为 EFBIG。 mmap、ftruncate 和 fclear 子例程的行为相似。
read 系列子例程也参与开放式保护方案。 如果应用程序尝试读取超过 2 GB 阈值的文件,那么最多将只读取 2 GB -1 数据。 对 2 GB -1 或超过 2 GB -1 界限数据的读取将失败,错误号将设为 EOVERFLOW。
开放式保护由与开放式文件描述相关联的标记实现。 通过 fcntl 子例程,使用 F_GETFL 命令可以查询标记的当前状态。 通过 fcntl 子例程,使用 F_SETFL 命令可以修改标记。
因为开放式文件描述继承自 exec 系列的子例程,所以,将大文件访问所启用的文件描述符传递给其他程序的应用程序,应该考虑接收程序是否能安全地访问大文件。
将应用程序移植到大文件环境
- 定义 _LARGE_FILES,这会仔细地将所有相关的数据类型、结构和子例程名称重新定义到其大文件启用的对应项。 定义 _LARGE_FILES 的优点是使应用程序到其他平台的可移植性最大化,这是因为仍将应用程序编写到普通的 POSIX 和 XPG 接口。 它的缺点是在代码中造成模糊性,这是因为无法根据代码确定不同数据项的大小。
- 重新对应用程序进行编码,以明确地调用大文件启用的子例程。 重新对应用程序进行编码的缺点是较为费事,而且降低了应用程序的可移植性。 当对 _LARGE_FILES 的作用进行的重新定义对程序产生相当大的负面影响或仅需要对程序的很小部分进行转换时,可以使用这种方法。
在每周情况下,都必须仔细地核查应用程序以确保在新环境中其行为正确。
使用 _LARGE_FILES
在缺省的编译环境中, off_t 数据类型被定义为有符号、32 位长整型。 如果应用程序在包含所有头文件之前定义 _LARGE_FILES,那么启用大文件编程环境,并且定义 off_t 为有符号、64 位长整型。 另外,将所有处理文件大小或文件偏移量的子例程重新定义为其大文件启用的相应项。 同样地,重新定义带有嵌入式文件大小或偏移量的所有数据结构。
下表显示发生在 _LARGE_FILES 环境中的重新定义:
实体 | 重新定义为 | 头文件 |
---|---|---|
off_t 对象 | long long | <sys/types.h> |
fpos_t 对象 | long long | <sys/types.h> |
struct stat 结构 | struct stat64 | <sys/stat.h> |
stat 子例程 | stat64() | <sys/stat.h> |
fstat 子例程 | fstat64() | <sys/stat.h> |
lstat 子例程 | lstat64() | <sys/stat.h> |
mmap 子例程 | mmap64() | <sys/mman.h> |
lockf 子例程 | lockf64() | <sys/lockf.h> |
struct flock 结构 | struct flock64 | <sys/flock.h> |
open 子例程 | open64() | <fcntl.h> |
creat 子例程 | creat64() | <fcntl.h> |
F_GETLK 命令参数 | F_GETLK64 | <fcntl.h> |
F_SETLK 命令参数 | F_SETLK64 | <fcntl.h> |
F_SETLKW 命令参数 | F_SETLKW64 | <fcntl.h> |
ftw 子例程 | ftw64() | <ftw.h> |
nftw 子例程 | nftw64() | <ftw.h> |
fseeko 子例程 | fseeko64() | <stdio.h> |
ftello 子例程 | ftello64() | <stdio.h> |
fgetpos 子例程 | fgetpos64() | <stdio.h> |
fsetpos 子例程 | fsetpos64() | <stdio.h> |
fopen 子例程 | fopen64() | <stdio.h> |
freopen 子例程 | freopen64() | <stdio.h> |
lseek 子例程 | lseek64() | <unistd.h> |
ftruncate 子例程 | ftruncate64() | <unistd.h> |
truncate 子例程 | truncate64() | <unistd.h> |
fclear 子例程 | fclear64() | <unistd.h> |
pwrite 子例程 | pwrite64() | <unistd.h> |
pread 子例程 | pread64() | <unistd.h> |
struct aiocb 结构 | struct aiocb64 | <sys/aio.h> |
aio_read 子例程 | aio_read64() | <sys/aio.h> |
aio_write 子例程 | aio_write64() | <sys/aio.h> |
aio_cancel 子例程 | aio_cancel64() | <sys/aio.h> |
aio_suspend 子例程 | aio_suspend64() | <sys/aio.h> |
aio_return 子例程 | aio_return64() | <sys/aio.h> |
aio_error 子例程 | aio_error64() | <sys/aio.h> |
liocb 结构 | liocb64 | <sys/aio.h> |
lio_listio 子例程 | lio_listio64() | <sys/aio.h> |
使用 64 位文件系统子例程
<sys/types.h>
typedef long long off64_t;
typedef long long fpos64_t;
<fcntl.h>
extern int open64(const char *, int, ...);
extern int creat64(const char *, mode_t);
#define F_GETLK64
#define F_SETLK64
#define F_SETLKW64
<ftw.h>
extern int ftw64(const char *, int (*)(const char *,const struct stat64 *, int), int);
extern int nftw64(const char *, int (*)(const char *, const struct stat64 *, int,struct FTW *),int, int);
<stdio.h>
extern int fgetpos64(FILE *, fpos64_t *);
extern FILE *fopen64(const char *, const char *);
extern FILE *freopen64(const char *, const char *, FILE *);
extern int fseeko64(FILE *, off64_t, int);
extern int fsetpos64(FILE *, fpos64_t *);
extern off64_t ftello64(FILE *);
<unistd.h>
extern off64_t lseek64(int, off64_t, int);
extern int ftruncate64(int, off64_t);
extern int truncate64(const char *, off64_t);
extern off64_t fclear64(int, off64_t);
extern ssize_t pread64(int, void *, size_t, off64_t);
extern ssize_t pwrite64(int, const void *, size_t, off64_t);
extern int fsync_range64(int, int, off64_t, off64_t);
<sys/flock.h>
struct flock64;
<sys/lockf.h>
extern int lockf64 (int, int, off64_t);
<sys/mman.h>
extern void *mmap64(void *, size_t, int, int, int, off64_t);
<sys/stat.h>
struct stat64;
extern int stat64(const char *, struct stat64 *);
extern int fstat64(int, struct stat64 *);
extern int lstat64(const char *, struct stat64 *);
<sys/aio.h>
struct aiocb64
int aio_read64(int, struct aiocb64 *):
int aio_write64(int, struct aiocb64 *);
int aio_listio64(int, struct aiocb64 *[],
int, struct sigevent *);
int aio_cancel64(int, struct aiocb64 *);
int aio_suspend64(int, struct aiocb64 *[]);
struct liocb64
int lio_listio64(int, struct liocb64 *[], int, void *);
使用大文件环境的常见缺陷
应用程序到大文件环境的移植可能会暴露应用程序中许多不同的问题。 这些问题通常是由于缺乏编程经验所造成的,这在 32 位 off_t 环境中是无害的,但在 64 位 off_t 环境中进行编译时这些问题就会出现。 本节中讨论一些较为常见的问题和解决方案。
数据类型的错误用法
应用程序问题的常见原因是没有使用正确的数据类型。 如果应用程序试图在整数变量中存储文件大小和文件偏移量,那么结果值将被截断且无意义。 要避免差问题,请使用 off_t 数据类型存储文件大小和偏移量。
不正确:
int file_size;
struct stat s;
file_size = s.st_size;
更好:
off_t file_size;
struct stat s;
file_size = s.st_size;
在将 64 位整数作为自变量传递给函数或将 64 位整数从函数返回时,调用和被调用函数的自变量和返回值的类型必须一致。
将 32 位整数传递给期望 64 位整数的函数,这会使得被调用函数误解调用函数的自变量,从而导致意外行为。 如果程序将标量值传递给期望接收 64 位整数的函数,那么此类问题尤为突出。
小心地使用函数原型,可以避免问题发生。 在下面的代码代码中,fexample() 是一个函数,它将 64 位文件偏移量作为参数。 在第一个示例中,编译器生成普通的 32 位整数函数链接,这可能是错误的,因为接收函数希望的是 64 位整数链接。 在第二个示例中,添加了 LL 说明符,强制编译器使用正确的链接。 在最后一个示例中,函数原型使编译器将标量值转储为 64 位整数。 这是首选方法,因为源代码仍保留了在 32 位和 64 位环境之间的可移植性。
不正确:
fexample(0);
更好:
fexample(0LL);
最佳:
\est: