read() большого файла размером 6 ГБ не работает на x86_64

Вот описание моей проблемы:

Я хочу прочитать большой файл, около 6,3 ГБ, весь в память, используя системный вызов read в C, но возникает ошибка. Вот код:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>

int main(int argc, char* argv[]) {
    int _fd = open(argv[1], O_RDONLY, (mode_t) 0400);
    if (_fd == -1)
        return 1;
    off_t size = lseek(_fd, 0, SEEK_END);
    printf("total size: %lld\n", size);
    lseek(_fd, 0, SEEK_SET);
    char *buffer = malloc(size);
    assert(buffer);
    off_t total = 0;
    ssize_t ret = read(_fd, buffer, size);
    if (ret != size) {
        printf("read fail, %lld, reason:%s\n", ret, strerror(errno));
        printf("int max: %d\n", INT_MAX);
    }
}

И скомпилируйте его с помощью:

gcc read_test.c

затем запустите с:

./a.out bigfile

выход:

total size: 6685526352
read fail, 2147479552, reason:Success
int max: 2147483647

Системная среда

 3.10.0_1-0-0-8 #1 SMP Thu Oct 29 13:04:32 CST 2015 x86_64 x86_64 x86_64 GNU/Linux

Есть два места, которые я не понимаю:

  1. Чтение не удается для большого файла, но не для маленького файла.
  2. Даже если есть ошибка, кажется, что errno установлен неправильно.

person zhanglistar    schedule 16.07.2016    source источник
comment
хорошо, вызов успешно прочитал 2147479552 байт. вам нужно зацикливаться, пока вы не израсходуете все data.btw? сколько оперативки у тебя всего?   -  person UmNyobe    schedule 16.07.2016
comment
Почему? Есть несколько ситуаций, когда вам действительно нужен весь файл в памяти.   -  person user207421    schedule 16.07.2016
comment
ваша система ограничивает доступную память? ты пробовал ulimit -s unlimited ?   -  person amn41    schedule 16.07.2016
comment
Связано: stackoverflow.com/q/10178660/694576   -  person alk    schedule 16.07.2016
comment
Если вы используете функции POSIX, такие как open() и read(), вы также можете использовать POSIX stat() и/или fstat(), чтобы получить размер файл напрямую.   -  person Andrew Henle    schedule 16.07.2016
comment
В зависимости от того, что вы собираетесь делать с этим огромным файлом, когда он будет у вас в памяти, mmap может быть более подходящим, чем read.   -  person zwol    schedule 16.07.2016


Ответы (4)


Системный вызов read может возвращать меньшее число, чем запрошенный размер по нескольким причинам, положительное ненулевое возвращаемое значение не является ошибкой, errno в этом случае не устанавливается, его значение неопределенно. Вы должны продолжать чтение в цикле, пока read не вернет 0 в случае конца файла или -1 в случае ошибки. Очень распространенной ошибкой является использование read для чтения всего блока за один вызов, даже из обычных файлов. Используйте fread для более простой семантики.

Вы печатаете значение INT_MAX, которое не имеет отношения к вашей проблеме. Интересны размеры off_t и size_t. На вашей платформе, 64-битной GNU/Linux, вам повезло, что и off_t, и size_t имеют длину 64 бита. ssize_t по определению имеет тот же размер, что и size_t. На других 64-битных платформах off_t может быть меньше size_t, что препятствует правильной оценке размера файла, или size_t может быть меньше off_t, позволяя malloc выделять блок меньше размера файла. Обратите внимание, что в этом случае read будет передан тот же самый меньший размер, потому что size будет молча усечен в обоих вызовах.

person chqrlie    schedule 16.07.2016
comment
Большое спасибо! Когда я продолжаю читать в цикле, пока не будет достигнуто 0 или -1, все работает нормально. - person zhanglistar; 16.07.2016
comment
@zhanglistar: на этот вопрос нет однозначного ответа: для небольших фрагментов fread может быть быстрее из-за буферизации, выполняемой по умолчанию в стандартном пакете ввода-вывода; для больших кусков это зависит от фактической реализации. Обратите внимание, что fread — переносное решение. read() — это системный вызов, стандартизированный в Posix, он доступен не во всех системах. - person chqrlie; 16.07.2016

Вы должны отказаться от чтения только в том случае, если оно возвращает -1. Со страницы руководства:

В случае успеха возвращается число прочитанных байтов (ноль указывает на конец файла), и позиция в файле увеличивается на это число. Если это число меньше запрошенного числа байтов, это не является ошибкой;

Я предполагаю, что на границе 2G в вашей файловой системе read() может прочитать короткий буфер.

person evaitl    schedule 16.07.2016

Попробуйте #define _FILE_OFFSET_BITS 64 для открытия и #define _LARGEFILE64_SOURCE для lseek64. тогда вы можете прочитать файл записи размером более 2 ГБ

person mc.robin    schedule 18.07.2016

Системный вызов read() не сможет прочитать огромные данные за один раз. Это зависит от многих факторов, таких как внутренний буфер ядра, реализация носителя в драйвере устройства. В вашем примере вы пытаетесь проверить, прочитал ли read() данные размера длины, если нет, то ошибка печати. Вам нужно продолжать читать данные до тех пор, пока прочитанные байты не будут равны 0, также вам нужно проверить код возврата, возвращаемый read(), если он равен -1, то это означает, что при чтении произошла ошибка, и в этом случае вам нужно проверьте установленную ошибку.

Также я бы посоветовал не выделять огромную память за один раз, даже если система способна выделять огромную память, поскольку это не очень хорошая реализация. Рассмотрите возможность разбить размер на несколько патронов, если это возможно.

person rj99999    schedule 22.07.2016