Работа с большими файлами

В AIX поддерживаются файлы размером более 2 Гб. В этом разделе описаны особенности больших файлов, которые программист должен учитывать при разработке приложений. С помощью ряда программных интерфейсов существующие приложения можно изменить таким образом, чтобы они поддерживали большие файлы. Интерфейсы файловой системы обычно основаны на типе данных off_t.

Особенности работы старых программ

32-разрядная среда, применявшаяся всеми приложениями в версиях младше AIX 4.2, осталась без изменений. Однако старые приложения не поддерживают обработку больших файлов.

Например, поле st_size структуры stat, в котором программе возвращается размер файла, представляет собой 32-разрядное целое число со знаком. В связи с этим, структура stat непригодна для работы с файлами, размер которых превышает LONG_MAX. Если приложение попытается вызвать функцию stat для файла, размер которого больше LONG_MAX, то в функции stat произойдет ошибка EOVERFLOW, означающая, что размер файла не умещается в соответствующем поле структуры.

Это может приводит к ошибкам в программах, не рассчитанных на работу с большими файлами, даже если эти программы не будут пытаться выполнять какие-либо операции с такими файлами.

Ошибка EOVERFLOW может возникать и при выполнении функций lseek и fcntl, если возвращаемые ими значения превосходят размер типа данных или структуры, применяемой программой. В функции lseek ошибка EOVERFLOW будет возникать в тех случаях, когда смещение превышает LONG_MAX. В функции fcntl ошибка EOVERFLOW может возникать при попытке выполнения операции F_GETLK, если смещение или длина блокируемой области превышает значение LONG_MAX.

Защита открытых файлов

Использование существующих программ для работы с большими файлами может повлечь за собой непредсказуемые результаты, включая потерю данных. Для защиты приложений от таких сбоев в AIX предусмотрены средства защиты, запрещающие старым программам выполнять операции, которые могут повредить большие файлы.

В операционной системе предусмотрены и другие механизмы защиты больших файлов. Суть этих механизмов сводится к тому, что старые программы работают в той же среде, что и ранее, не имея возможности повредить большие файлы. Например, если приложение попытается записать в файл более 2 ГБ данных с помощью функций семейства write, то будет записано только 2 ГБ - 1 байт. Если приложение попытается превысить это ограничение, то функция write выдаст ошибку EFBIG. Функции mmap, ftruncate и fclear работают аналогично.

Функции семейства read также снабжены механизмами защиты. Если приложение попытается считать более 2 Гб данных, то будет прочитано только 2 Гб минус 1 байт. Если приложение явно запросит данные, расположенные за этой границей, то будет выдана ошибка EOVERFLOW.

Для защиты открытых файлов в описание открытого файла теперь добавлен специальный флаг. Текущее состояние этого флага можно определить с помощью команды F_GETFL процедуры fcntl. Изменить его состояние можно с помощью команды F_SETFL процедуры fcntl.

Поскольку описания открытых файлов наследуются в семействе функций exec, то программы, передающие описания больших файлов другим программам, должны проверять, могут ли принимающие программы правильно работать с такими файлами.

Модификация программ для работы с большими файлами

В AIX предусмотрено два способа добавления поддержки больших файлов в существующие приложения. Программист может выбрать любой из них, по своему усмотрению:
  • Первый из них заключается в добавлении определения _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-разрядной файловой системы

Перекомпиляция программы в режиме _LARGE_FILES может быть нежелательной из-за большого числа неявных изменений, вносимых в программу при такой перекомпиляции. Если в программе очень мало функций, работающих с файловой системой, то целесообразно просто заменить имена этих функций. Ниже перечислены типы данных, структуры и процедуры 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 - это 64-разрядное смещение в файле.

Неправильный выбор типов данных

Самая распространенная ошибка в программах - выбор неправильного типа данных. Если программа хранит размеры файлов и положения указателей в переменных типа int, то значения этих величин могут быть усечены. Размеры файлов и смещение указателя в файле следует хранить в переменных типа 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-разрядные целые числа.

Для того чтобы избежать подобных ошибок, следует правильно задавать прототипы функций. В следующих примерах fexample() - это функция, ожидающая в качестве параметра 64-разрядное смещение указателя в файле. В первом примере компилятор создаст вызов функции, в которую передается 32-разрядное целое число. Во втором примере явно указан модификатор типа "LL", и поэтому компилятор создаст правильный вызов функции. В последнем примере перед вызовом функции в программу помещен ее прототип, в котором указан тип аргумента. Это оптимальное решение, поскольку в этом случае сохраняется возможность переноса программы в 32-разрядную среду.

Неправильно:

fexample(0);

Лучше:

fexample(0LL);  

Лучше всего:

\est: