Начиная с версии 4.2, в AIX можно создавать файлы размером более 2 гигабайт (2 Гб). В этом разделе описаны особенности больших файлов, которые программист должен учитывать при разработке новых и модификации старых программ. В AIX встроен новый набор интерфейсов программирования, предназначенных для работы с большими файлами.
Ограничение на размер файла напрямую зависит от типа данных off_t. В AIX версии 4.1 тип off_t был определен как 32-разрядное целое число со знаком. Как следствие, максимальный размер файла был равен 2 Гб без одного байта.
Старая среда, в которой размер файла задавался 32-разрядными значениями, осталась без изменений. Программы, не рассчитанные на большие файлы, будут работать точно так же, как и раньше. Соответственно, они не смогут работать с большими файлами.
Например, поле st_size в структуре stat, в которой программе передается размер файла, представляет собой 32-разрядное целое число со знаком. В связи с этим, данная структура непригодна для работы с файлами, размер которых превышает LONG_MAX. Если программа попытается определить размер большого файла с помощью структуры stat, в процедуре stat произойдет ошибка EOVERFLOW, указывающая на то, что размер файла не умещается в предоставленном поле.
Эта особенность может служить причиной ошибок в программах, не рассчитанных на работу с большими файлами, даже если эти программы не будут пытаться выполнять какие-либо операции с такими файлами.
Ошибка EOVERFLOW может также возникать в процедурах lseek и fcntl, если значения, которые должны будут вернуть эти процедуры, превысят емкость типа данных, используемого программой. В процедуре lseek ошибка будет возникать в тех случаях, когда смещение превышает LONG_MAX. В процедуре fcntl ошибка будет возникать при попытке выполнения операции F_GETLK, если смещение превышает LONG_MAX.
Разработчики многих применяемых в настоящее время программ не предполагали, что 32-разрядного поля со знаком может оказаться недостаточно для хранения информации о размере файла. Использование таких программ для работы с большими файлами могло бы повлечь за собой непредсказуемые результаты, включая потерю данных. В связи с этим, начиная с AIX версии 4.2, в операционной системе предусмотрены средства защиты, запрещающие старым программам выполнять операции, которые могут повредить большие файлы.
Если программа, не рассчитанная на работу с большими файлами, попытается открыть файл размером более LONG_MAX, процедура open выдаст ошибку EOVERFLOW. Благодаря этому старые программы не могут открывать большие файлы, что снижает вероятность потери данных из-за неправильной работы таких программ. Для того чтобы старые программы смогли работать с большими файлами, в них нужно внести ряд изменений. Эти изменения описаны в разделе "Модификация программ для работы с большими файлами".
В операционной системе предусмотрены и другие механизмы защиты больших файлов. Суть этих механизмов сводится к тому, что старые программы работают в той же среде, что и ранее, не имея возможности повредить большие файлы. Например, если приложение попытается записать в файл более 2 Гб данных с помощью процедур семейства write, то будет записано только 2 Гб - 1 байт. Если приложение попытается пересечь эту границу, то процедура write выдаст ошибку EFBIG. Процедуры mmap, ftruncate и fclear работают аналогично.
Процедуры семейства read также снабжены механизмами защиты. Если приложение попытается прочитать более 2 Гб данных из файла с помощью этих процедур, то будет прочитано только 2 Гб - 1 байт. Если приложение явно запросит данные, расположенные за этой границей, то будет выдана ошибка EOVERFLOW.
Для защиты открытых файлов в описание открытого файла теперь добавлен специальный флаг. Текущее состояние этого флага можно определить с помощью команды F_GETFL процедуры fcntl. Изменить его состояние можно с помощью команды F_SETFL процедуры fcntl.
Поскольку описания открытых файлов наследуются в семействе процедур exec, то программы, передающие описания больших файлов другим программам, должны проверять, могут ли принимающие программы правильно работать с такими файлами.
Начиная с версии 4.2, в AIX предусмотрены два способа модификации программ для работы с большими файлами. Программист может выбрать любой из них, по своему усмотрению. Первый способ заключается в определении символа _LARGE_FILES - при этом во время компиляции программы правильно переопределяются все зависимые типы данных и структуры, а вместо процедур старого семейства подставляются новые процедуры, поддерживающие большие файлы. Второй способ заключается в изменении программы таким образом, чтобы в ней явно вызывались процедуры, предназначенные для работы с большими файлами.
Определение символа _LARGE_FILES более предпочтительно, поскольку в этом случае программа остается переносимой на другие платформы, так как она по-прежнему соответствует стандартам POSIX и XPG. Недостаток этого способа заключается в том, что за счет автоматического переопределения типов данных и подстановки новых процедур текст программы перестает отражать фактические типы данных.
Изменение текста программ требует больше усилий и снижает возможность переноса программ на другие платформы. Этот подход целесообразен только в тех случаях, когда определение символа _LARGE_FILES нежелательно по каким-либо причинам, и изменения затрагивают только малую часть текста программы.
Очень важно понимать, что в любом случае измененная программа обязательно должна быть тщательно проверена - вы должны убедиться, что она правильно работает в среде с большими файлами. В разделе "Типичные ошибки при работе с большими файлами" приведены некоторые часто встречающиеся ошибки программистов.
В стандартной среде компиляции тип данных off_t определен как 32-разрядное целое число со знаком. Начиная с AIX версии 4.2, если перед включением файлов заголовков в программе будет определен символ _LARGE_FILES, то программа будет компилироваться с поддержкой больших файлов и тип данных off_t будет определен как 64-разрядное целое со знаком. Кроме того, вызовы всех процедур, в которых используются указатели смещения в файлах и размер файлов, будут заменены на вызовы аналогичных процедур, поддерживающих большие файлы. Соответственно, будут переопределены все структуры данных, содержащие поля размера файла и смещения в файле.
Если первоначально программа была написана таким образом, что ее работа никак не зависела от размера типа данных off_t, то после перекомпиляции она должна правильно работать в новой среде. На практике очень часто оказывается, что в программы требуется внести дополнительные изменения.
В следующей таблице перечислены символы, переопределяемые при компиляции программ в режиме _LARGE_FILES для AIX версии 4.2.
Символ | Заменяется на | Файл заголовка |
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> |
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_listio() | aio_listio64() | <sys/aio.h> |
aio_return() | aio_return64() | <sys/aio.h> |
aio_error | aio_error64() | <sys/aio.h> |
Перекомпиляция программы в режиме _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); <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 *[]);
При переносе программы в среду с поддержкой больших файлов может оказаться, что для работы программы требуется внести в нее определенные изменения. Большинство ошибок бывает вызвано неаккуратным составлением программы, за счет чего она может правильно работать в среде с 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);
void fexample(off_t); fexample(0);
Даже если в программе применяются правильные типы данных, она может быть подвержена арифметическим переполнениям. Обычно ошибки такого рода возникают в ситуациях, когда арифметическое переполнение наступает до преобразования значения в 64-разрядное целое. В следующем примере blkno - это 32-разрядный номер блока. Умножение номера блока на размер блока выполняется до преобразования значения в 64-разрядное целое, и поэтому результат умножения может вызвать переполнение несмотря на то, что длина принимающей переменной достаточна. Эта ошибка особенно опасна, поскольку программа правильно работает с малыми величинами, но начинает неправильно работать с большими. Во избежание этой ошибки преобразование типов должно выполняться до арифметических вычислений.
int blkno; off_t offset; offset = blkno * BLKSIZE;
int blkno; off_t offset; offset = (off_t) blkno * BLKSIZE;
Аналогичная ошибка может возникнуть при передаче констант в функции, рассчитанные на 64-разрядные параметры. В следующем примере выражение LONG_MAX+1 дает отрицательный результат, который и передается в вызываемую функцию.
void fexample(off_t); fexample(LONG_MAX+1);
void fexample(off_t); fexample((off_t)LONG_MAX+1);
Процедуры fseek и ftell рассчитаны на тип данных long и их нельзя переопределить для работы в 64-разрядной среде при компиляции программ в режиме _LARGE_FILES. Программы, использующие fseek и ftell для работы с большими файлами, должны быть изменены. Существует много способов исправления. Функционально процедуры fseeko и ftello равносильны fseek и ftell за исключением того, что для указателя смещения используется тип данных off_t, а не long. Обязательно исправьте типы всех переменных, в которых могут храниться значения смещения.
long cur_offset, new_offset; cur_offset = ftell(fp); fseek(fp, new_offset, SEEK_SET);
off_t cur_offset, new_offset; cur_offset = ftello(fp); fseeko(fp, new_offset, SEEK_SET);
Для того чтобы в программе действовали переопределения функций и типов данных, в ней должны применяться файлы заголовков. Кроме того, в файлах заголовков указаны прототипы функций, вызываемых программой, что дает компилятору возможность дополнительной проверки типов данных.
Во многих программах, вызывающих процедуры open и creat, не используется файл <fcntl.h> , в котором определены константы режимов открытия файлов. Как правило, в таких программах явно заданы значения режимов. Это может привести к ошибкам при работе программ, откомпилированных в режиме _LARGE_FILES, поскольку компилятор не сможет откорректировать вызов процедуры open для работы с большими файлами. В программы обязательно должны включаться файлы заголовков, особенно в среде _LARGE_FILES.
fd = open("afile",2);
#include <fcntl.h> fd = open("afile",O_RDWR);
Преобразование размеров файлов и положения указателей в строковые значения и обратно также может послужить причиной ошибок при работе с большими файлами. Команда printf форматирует 64-разрядные целые числа не так, как 32-разрядные. Если вам необходимы подобные преобразования в программе, в них обязательно должны указываться модификаторы формата. Эта задача может оказаться непростой, особенно если программа должна быть переносима между 32- и 64-разрядной средой, поскольку переносимого модификатора формата не существует. Один из способов обхода этой проблемы заключается в том, чтобы функции учитывали размер типа данных off_t при преобразовании размера файлов и положения указателей.
off_t atooff(const char *s) { off_t o; if (sizeof(off_t) == 4) sscanf(s,"%d",&o); else if (sizeof(off_t) == 8) sscanf(s,"%lld",&o); else error(); return o; } main(int argc, char **argv) { off_t offset; offset = atooff(argv[1]); fexample(offset); }
Программы, в которых жестко заданы смещения и размеры файлов, могут неправильно работать после перекомпиляции с измененным размером типа данных off_t. Эти ошибки могут быть особенно серьезны, когда одна структура данных используется несколькими программами или когда эта структура данных записывается в файл. В таких случаях программист должен решить, не стоит ли перейти от 32-разрядных значений смещения к 64-разрядным. Если программе требуется 32-разрядное смещение даже в случаях, когда длина off_t равна 64 битам, в ней следует применять новый тип данных soff_t - короткий off_t. Длина этого типа данных равна 32 даже в среде с большими файлами. Если упоминавшаяся выше структура данных будет преобразована таким образом, что в ней будут использоваться 64-разрядные значения смещения, программисту потребуется соответствующим образом откорректировать все программы, работающие с этой структурой.
При внесении изменений в программы для работы с большими файлами следует учитывать, что в операционной системе установлены ограничения на максимальный размер создаваемых файлов. Для каждого процесса действует ограничение на максимальное смещение, по которому процесс может записывать информацию в обычные файлы. Фактически это максимальный размер файлов, которые может создавать процесс. Данное ограничение представляет собой 32-разрядное целое число. Для программ, которые должны работать с большими файлами, этому ограничению должно быть присвоено значение RLIM_INFINITY.
struct rlimit r; r.rlim_cur = r.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_FSIZE,&r);
Это ограничение можно также изменять с помощью команды ulimit оболочки Korn:
ulimit -f unlimited
Жесткое ограничение на размер файлов для конкретного пользователя можно изменить с помощью команды chuser:
Пример:chuser fsize_hard = -1 root
Максимальный размер файлов ограничен не только средой, в которой работают процессы, но и параметрами конкретной файловой системы. Для файловых систем JFS максимальный размер файлов выбирается в момент их создания. Для файловых систем JFS, поддерживающих большие файлы, максимальный размер файлов немногим меньше 64 Гб (0xff8400000). Для остальных файловых систем JFS максимальный размер файлов равен 2 Гб - 1 (0x7fffffff). Попытка создать слишком большой файл в такой файловой системе приведет к ошибке с кодом EFBIG.
В JSF2 размер файлов ограничен размером файловой системы.
Поддержка файлов размером более 2 Гб
Глава 5, Файловые системы и каталоги