Unix/Linux编程:大文件IO

  • 通常将存放文件偏移量的数据类型off_t实现为一个有符号的长整型(之所以采用有符号数据类型,是要以-1来表示错误情况)。在32位体系架构中(比如x86-32),这将文件大小置于 2 31 − 1 2^{31}−1 2311个字节(即 2GB)的限制之下。
  • 然而,磁盘驱动器的容量早已超出这一限制,也就是说32位Unix有处理超过2GB大小文件的需求。由于问题比较普遍,UNIX 厂商联盟在大型文件峰会(Large File Summit)上就此进行了协商,并针对必需的大文件访问功能,形成了对 SUSv2 规范的扩展。本节将概述 LFS 的增强特性。(完整的 LFS 规范定稿于 1996 年,可通过 http://opengroup. org/platform/lfs.html 访问。)
  • 始于内核版本2.4,32位Linux系统开始提供对LFS的支持(glibc版本必须为2.2或更高)。
  • 另一个前提是,相应的文件系统也必须支持大文件操作。

大多数“原生”Linux 文件系统提供了 LFS 支持,但一些“非原生”文件系统则未提供该功能(微软的 VFAT 和 NFSv2 系统是其中较为知名的范例,无论系统是否启用了 LFS 扩展功能,2GB 的文件大小限制都是硬杠杠)。

由于 64 位系统架构(例如,Alpha、IA-64)的长整型类型长度为 64 位,故而 LFS 增强特性所要突破的限制对其而言并不是问题。然而,即便在64 位系统中,一些“原生”Linux 文件系统的实现细节还是将文件大小的理论值默认为不会超过 263−1 个字节。在大多数情况下,此限额远远超出了目前的磁盘容量,故而这一对文件大小的限制并无实际意义

应用程序可使用如下两种方式之一以获得 LFS 功能

  • 使用支持大文件操作的备选API。该API由LFS设计,意在作为SUS规范的“过渡型扩展”。因此,尽管大部分系统都支持这一API,但这对于符号SUSv2或SUSv3规范的系统其实并非必须。这一方法现在已经过时
  • 在编译应用程序时,将宏_FILE_OFFSET_BITS的值定义为 64。这一方法更为可取,因为符合 SUS 规范的应用程序无需修改任何源码即可获得 LFS 功能

过渡型LFS API

要使用过渡型LFS API,必须在编译程序时但定义_LARGEFILE64_SOURCE功能测试宏,

  • 该定义可以通过命令行指定,
  • 也可以定义于源文件中包含所有头文件之前的位置。

该 API 所属函数具有处理 64 位文件大小和文件偏移量的能力。这些函数与其 32 位版本命名相同,只是尾部缀以 64 以示区别。其中包括:fopen64()、open64()、lseek64()、truncate64()、stat64()、mmap64()和 setrlimit64()。

要访问大文件,可以使用这些函数的64位版本。比如,打开大文件:

fd = open64(name, O_CREAT | O_RDWR, mode);
if(fd == -1){
	exit(EXIT_FAILURE);
}

调用open64(),相当于在调用open()时指定o-LARGEFILE标志。如果调用open()时未指定此标志,而且欲打开的文件大小大于2GB,那么调用将返回错误。

另外,除去上面提到的函数之外,过渡型LFSAPI还增加了一些新的数据类型,如下所示:

  • struct stat64:类似于 stat 结构,支持大文件尺寸。
  • off64_t:64 位类型,用于表示文件偏移量
// 访问大文件
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <zconf.h>

int main(int argc, char *argv[]){
    int fd;
    off64_t off;

    if (argc != 3 || strcmp(argv[1], "--help") == 0){
        printf("%s pathname offset\n", argv[0]);
        exit(EXIT_FAILURE);
    }


    fd = open64(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1){
        perror("open64");
        exit(EXIT_FAILURE);
    }


    off = atoll(argv[2]);
    if (lseek64(fd, off, SEEK_SET) == -1){
        perror("lseek64");
        exit(EXIT_FAILURE);
    }

    if (write(fd, "test", 4) == -1){
        perror("write");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

在这里插入图片描述

_FILE_OFFSET_BITS

要获取 LFS 功能,推荐的作法是:

  • 在编译程序时,将宏_FILE_OFFSET_BITS 的值定义为64。做法之一是利用 C 语言编译器的命令行选项
cc -D_FILE_OFFSET_BITS =64 prog.c
  • 另外一种方法,是在 C 语言的源文件中,在包含所有头文件之前添加如下宏定义
#define _FILE_OFFSET_BITS  64

所有相关的32位函数和数据类型将自动转换为64位版本。比如会将 open()转换为 open64(),数据类型 off_t 的长度也将转而定义为 64 位。换句话说,无需对源码进行任何修改,只要对已有程序重新编译,就能够实现大文件操作

显然,使用宏_FILE_OFFSET_BITS 要比采用过渡型的 LFS API 更为简单,但这也要求应用程序的代码编写必须规范(例如,声明用于放置文件偏移量的变量,应正确地使用 off_t,而不能使用“原生”的 C 语言整型)

LFS规范对于支持_FILE_OFFSET_BITS 宏未作硬性规定,仅仅提及将该宏作为指定数据类型off_t大小的可选方案。一些 UNIX 实现使用不同的特性测试宏来获取此功能

若试图使用 32 位函数访问大文件(即在编译程序时,未将宏_FILE_OFFSET_BITS 的值设置为 64),调用可能会返回 EOVERFLOW 错误。例如,为获取大小超过 2G 文件的信
息,若使用 stat 的 32 位版本时就会遇到这一错误。

向 printf()调用传递 off_t 值

LFS扩展功能没有解决的问题之一时,如何向printf()调用传递off_t值。

对于预定义的系统类型(比如pid_t、uid_t),展示其值可移植方法是将该值强制转换为long型,并在printf()中使用%ld。然而,一旦使用了LFS扩展功能,%ld将不足以处理off_t数据类型,因为对该数据类型的定义可能会超出long类型的范围,一般为long long类型。

因此,如果要显式off_t类型的值,则先要将其强制转换为long long类型,然后使用printf()函数的%lld限制符显式:

#define _FILE_OFFSET_BITS  64

off_t offset

printf("offset = %lld\n", (long long)offset);

在处理 stat 结构所使用的 blkcnt_t 数据类型时,也应予以类似关注

如需在独立的编译模块之间传递 off_t 或 stat 类型的参数值,则需确保在所有模块中,这些数据类型的大小相同(即编译这些模块时,要么将宏_FILE_OFFSET_BITS 的值都定义为 64,要么都不做定义)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值