Файлы, открытые с флагом O_APPEND
ведут себя несколько иначе. Для таких файлов текущая позиция перемещается в конец файла перед тем, как ядро осуществит в него запись. После записи текущая позиция перемещается в конец записанных данных, как обычно. Для файлов, работающих в режиме только для добавления, это гарантирует, что текущая позиция файла всегда будет находиться в его конце немедленно после вызова write()
.
Приложения, которые хотят читать и писать данные с произвольного места файла, должны установить текущую позицию перед выполнением операции чтения или записи данных, используя lseek()
.
#include
int lseek(int fd, off_t offset, int whence);
Текущая позиция для файла fd
перемещается на offset
байт относительно whence
, где whence принимает одно из следующих значений:
SEEK_SET
[45] Поскольку в большинстве систем SEEK_SET определена как 0, часто можно увидеть использование lseek(fd, offset, 0) вместо lseek(fd, offset, SEEK_SET) . Это делает код непереносимым (или плохо читабельным), чем SEEK_SET , но подобное часто встречается в старом коде.
Начало файла.
SEEK_CUR
Текущая позиция в файле.
SEEK_END
Конец файла.
Для SEEK_CUR
и SEEK_END
значение offset
может быть отрицательным. В этом случае текущая позиция перемещается в сторону начала файла (от whence
), а не в сторону конца. Например, приведенный ниже код перемещает текущую позицию на 5 байт назад от конца файла.
lseek(fd, -5, SEEK_END);
Системный вызов lseek()
возвращает новую текущую позицию в файле относительно его начала, либо -1 в случае ошибки. То есть lseek(fd, 0, SEEK_END)
— это просто способ определения размера файла, но следует убедиться, что вы сбросили текущую позицию, прежде чем читать из fd
.
Хотя текущая позиция не разделяется другими процессами, которые одновременно имеют доступ к файлу [46] Все же не всегда. Если процессы разделяют файловые дескрипторы (имеются в виду дескрипторы, полученные от одного вызова open() ),эти процессы разделяют одни и те же файловые структуры и одно и тоже текущее положение. Наиболее часто такое случается после вызова fork() ,как обсуждается в конце этой главы. Другая ситуация, когда такое может случиться — это если файловый дескриптор передается другому процессу через доменный сокет Unix, описанный в главе 17.
, это не значит, что множество процессов могут безопасно осуществлять совместную запись в файл. Пусть имеется следующая последовательность.
Процесс A Процесс B
lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_END);
write (fd, buf, 10);
write(fd, buf, 5);
В этом случае процесс А перепишет первые 5 байт данных, которые запишет процесс В, а это наверняка не то, чего вы хотели. Если множеству процессов нужно совместно писать данные в конец файла, должен быть использован флаг O_APPEND
, который делает эту операцию атомарной.
В большинстве систем POSIX процессам разрешается перемещать текущую позицию за конец файла. При этом файл увеличивается до соответствующего размера и его текущая позиция устанавливается в новый конец файла. Единственной ловушкой может быть то, что большинство систем при этом не выделяют никакого дискового пространства для той части файла, которая не была записана — они просто изменяют логический размер файла.
Части файлов, которые "создаются" подобным образом, называют " дырками" (holes). Чтение из такой "дырки" в файле возвращает буфер, полный двоичных нулей, а запись в них может завершиться ошибкой по причине отсутствия свободного пространства на диске. Все это значит, что вызов lseek()
не должен применяться для резервирования дискового пространства для позднейшего использования, поскольку такое пространство может быть и не выделено. Если ваше приложение нуждается в выделении некоторого дискового пространства для последующего использования, вы должны применять write()
. Файлы с "дырками" часто используют для хранения редко расположенных в них данных, таких как файлы, представляющие хеш-таблицы.
Для простой демонстрации "дырок" в файлах, основанной на командной оболочке, рассмотрим следующий пример ( /dev/zero
— это символьное устройство, которое возвращает столько двоичных нулей, сколько процесс пытается прочитать из него).
$ dd if=/dev/zero of=foo bs=1k count=10
10+0 records in
10+0 records out
$ ls -l foo
-rw-rw-r-- 1 ewt ewt 10240 Feb 6 21:50 foo
$ du foo
10 foo
$ dd if=/dev/zero of=bar bs=1k count=1 seek=9
Читать дальше