возвращает новую текущую позицию в файле относительно его начала, либо -1 в случае ошибки. То есть
lseek(fd, 0, SEEK_END)
— это просто способ определения размера файла, но следует убедиться, что вы сбросили текущую позицию, прежде чем читать из
fd
.
Хотя текущая позиция не разделяется другими процессами, которые одновременно имеют доступ к файлу [46] , это не значит, что множество процессов могут безопасно осуществлять совместную запись в файл. Пусть имеется следующая последовательность.
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
1+0 records in
1+0 records out
$ ls -l bar
– rw-rw-r-- 1 ewt ewt 10240 Feb 6 21:50 foo
$ du bar
1 bar
$
Хотя оба файла — и
foo
, и
bar
— имеют длину в 10 Кбайт, файл
bar
занимает только 1 Кбайт дискового пространства, потому что
остальные 9 Кбайт были пропущены
seek
, когда файл был создан или записан.
11.2.5. Частичное чтение и запись
Хотя обе функции — и
read
, и
write
— принимают параметр, указывающий, сколько байт нужно прочитать или записать, ни одна из них не гарантирует, что обработает указанное количество байт, даже если не случается никаких ошибок. Простейший пример этого — попытки чтения из обычного файла, который уже позиционирован в конце. Система не может прочитать ни одного байта, но это в то же время не является ошибкой. Вместо этого
read
возвращает 0 байт. Точно так же, если текущая позиция находилась в 10 байт от конца файла, и была выполнена попытка прочитать из файла более 10 байт, то прочитано будет ровно 10 байт и вызов
read
вернет число 10. Опять-таки это не рассматривается как ошибочная ситуация.
Поведение
read
также зависит от того, был ли файл открыт с флагом
O_NONBLOCK
. Для файлов многих типов
O_NONBLOCK
не влияет ни на что. Файлы, для которых система может гарантировать завершенность операции в пределах разумного периода времени, всегда блокирует чтение и запись; такие файлы часто называют быстрыми файлами. Это множество файлов включает локальные блочные устройства и обычные файлы. Для других типов файлов, таких как каналы, и символьных устройств вроде терминалов процесс может ожидать другого процесса (или человека), чтобы тот либо выполнил чтение, либо освободил ресурсы системы при обработке запроса на
write
. В обоих случаях система не имеет способа знать — будет ли вообще возможно дождаться завершения системного вызова. Когда такие файлы открываются с флагом
O_NONBLOCK
, то для каждой операции с файлом система просто делает максимум того, что удается сделать немедленно, а затем возвращает управление вызывающему процессу.
Неблокирующий ввод-вывод — это важная тема, и больше примеров вы найдете в главе 13. После стандартизации системного вызова
poll
, однако, необходимость в нем (особенно при чтении) минимизирована. Если вам нужно интенсивно использовать неблокирующий ввод-вывод, попробуйте пересмотреть свою программу в терминах
poll
, чтобы увидеть, нельзя ли ее сделать более эффективной.
Чтобы показать конкретный пример чтения и записи файлов, приведем простую реализацию
cat
. Она копирует стандартный поток ввода (stdin) на стандартный вывод (stdout) до тех пор, пока есть что копировать.
1: /* cat.с */
2:
3: #include <stdio.h>
4: #include <unistd.h>
5:
6: /* Пока есть данные на стандартном входе (fd0), копировать их в
7: стандартный выход (fd1). Выйти, когда не будет доступных данных. */
8:
9: int main(void) {
10: char buf[1024];
11: int len;
12:
13: /* len будет >= 0, пока доступны данные
14: и read успешен */
15: while ((len = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {