Рассмотрим некоторые другие примеры вызовов lseek(), а также комментарии, объясняющие, куда передвигается файловое смещение:
lseek(fd, 0, SEEK_SET); /* Начало файла */
lseek(fd, 0, SEEK_END); /* Следующий байт после конца файла */
lseek(fd, — 1, SEEK_END); /* Последний байт файла */
lseek(fd, — 10, SEEK_CUR); /* Десять байтов до текущего размещения */
lseek(fd, 10000, SEEK_END); /* 10 000 и 1 байт после
последнего байта файла */
Вызов lseek() просто устанавливает значение для записи ядра, содержащей файловое смещение и связанной с дескриптором файла. Никакого физического доступа к устройству при этом не происходит.
Некоторые дополнительные подробности взаимоотношений между файловыми смещениями, дескрипторами файлов и открытыми файлами рассматриваются в разделе 5.4.
Не ко всем типам файлов можно применять системный вызов lseek(). Запрещено применение lseek() к конвейеру, FIFO-устройству, сокету или терминалу — вызов аварийно завершится с установленным для errno значением ESPIPE. С другой стороны, lseek() можно применять к тем устройствам, в отношении которых есть смысл это делать, например, при наличии возможности установки на конкретное место на дисковом или ленточном устройстве.
Буква l в названии lseek() появилась из-за того, что как для аргумента offset, так и для возвращаемого значения первоначально определялся тип long. В ранних реализациях UNIX предоставлялся системный вызов seek(), в котором для этих значений определялся тип int.
Файловые дыры
Что происходит, когда программа перемещает указатель, переходя при этом за конец файла, а затем выполняет ввод-вывод? При вызове read() возвращается 0, показывающий, что достигнут конец файла. А вот записывать байты можно в произвольное место после окончания файла.
Пространство между предыдущим концом файла и только что записанными байтами называется файловой дырой. С точки зрения программиста, байты в дыре имеются, и чтение из дыры возвращает буфер данных, содержащий 0 (нулевые байты).
Файловые дыры не занимают места на диске. Файловая система не выделяет для дыры дисковые блоки до тех пор, пока в нее не будут записаны данные. Основное преимущество файловых дыр заключается в том, что для слабозаполненного файла потребляется меньше дискового пространства, чем понадобилось бы, если бы для нулевых байтов действительно нужно было выделять дисковые блоки. Файлы дампов ядра (см. раздел 22.1) — яркие примеры файлов с большими дырами.
Утверждение о том, что файловые дыры не потребляют дисковое пространство, требует уточнения. На большинстве файловых систем файловое пространство выделяется поблочно (см. раздел 14.3). Размер блока зависит от типа файловой системы, но обычно составляет 1024, 2048 или 4096 байт. Если край дыры попадает в блок, а не на границу блока, тогда для хранения байтов в другой части блока выделяется весь блок, и та часть, которая относится к дыре, заполняется нулевыми байтами.
Большинство нативных для UNIX файловых систем поддерживают концепцию файловых дыр, в отличие от многих «неродных» файловых систем (например, VFAT от Microsoft). В файловой системе, не поддерживающей дыры, в файл записывается явно указанное количество нулевых байтов.
Наличие дыр означает, что номинальный размер файла может быть больше, чем занимаемый им объем дискового пространства (иногда существенно больше). Запись байтов в середину дыры сократит объем свободного дискового пространства, поскольку для заполнения дыры ядро выделит блоки, даже притом, что размер файла не изменится. Подобное случается редко, но это все равно следует иметь в виду.
В SUSv3 определена функция posix_fallocate(fd, offset, len). Она гарантирует выделение дискового пространства для байтового диапазона, указанного аргументами offset и len для дискового файла, ссылка на который дается в дескрипторе fd. Это позволяет приложению получить гарантию, что при последующем вызове write() в отношении данного файла не будет сбоя, связанного с исчерпанием дискового пространства (который в противном случае может произойти при заполнении дыры в файле или потреблении дискового пространства каким-нибудь другим приложением). Исторически, реализация этой функции в glibc достигает нужного результата, записывая в каждый блок указанного диапазона нули. Начиная с версии 2.6.23, в Linux предоставляется системный вызов fallocate(). Он предлагает более эффективный способ обеспечения выделения необходимого пространства, и реализация posix_fallocate() в glibc использует этот системный вызов при его доступности.
В разделе 14.4 описывается способ представления дыр в файле, а в разделе 15.1 рассматривается системный вызов stat(), который способен сообщить о текущем размере файла, а также о количестве блоков, фактически выделенных файлу.