Чтение онлайн

ЖАНРЫ

Разработка приложений в среде Linux. Второе издание

Троан Эрик В.

Шрифт:

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) {

16: if (write(1, buf, len) != len) {

17: perror("write");

18: return 1;

19: }

20: }

21:

22: /* len
будет <= 0; если len = 0, больше нет

23: доступных данных. Иначе - ошибка. */

24: if (len < 0) {

25: perror("read");

26: return 1;

27: }

28:

29: return 0;

30: }

11.2.6. Сокращение файлов

Хотя обычные файлы автоматически растут при записи данных в их конец, у системы нет способа автоматически усекать файлы, когда данные в конце не нужны. К тому же, как система может узнать, что данные стали излишними? Это находится в компетенции процесса — извещать систему о том, когда файл можно сократить до определенной точки.

#include <unistd.h>

int truncate(const char *pathname, size_t length);

int ftruncate(int fd, size_t length);

Размер файла устанавливается равным

length
, и все данные, находящиеся за новым концом файла, теряются.

Если

length
больше текущего размера файла, то файл увеличивается до заданного размера (по возможности используя "дырки"), хотя такое поведение и не гарантируется POSIX, поэтому на него нельзя полагаться при написании переносимых программ.

11.2.7. Синхронизация файлов

Когда программа пишет данные в файл, обычно они сохраняются в кэше ядра до тех пор, пока оно не выполнит запись на физический носитель (такой как жесткий диск), но ядро возвращает управление программе сразу после того, как данные скопируются в кэш. Это обеспечивает значительный рост производительности, так как позволяет ядру определять порядок записи на диск и объединять несколько записей в одну блочную операцию. Однако в случае системного сбоя у такой технологии есть два существенных недостатка, которые могут оказаться важными. Например, приложение, которое предполагает, что данные сохранены в базе данных прежде, чем был сохранен индекс для этих данных, может не справиться со сбоем, явившимся результатом того, что индекс был просто обновлен.

Есть несколько механизмов, которые может использовать приложение, чтобы дождаться записи данных на физический носитель. Флаг

O_SYNC
, описанный ранее в этой главе, при каждой операции записи в файл вызывает блокирование вызывающего процесса до тех пор, пока носитель не будет действительно обновлен. Хотя это, конечно, работает, все же такой подход не является достаточно аккуратным. Обычно приложения не нуждаются в том, чтобы все операции были синхронизированы, гораздо чаще они нуждаются в том, чтобы гарантировать, что некий набор операций завершился перед тем, как может быть начат другой набор операций. Системные вызовы
fsync
и
fdatasync
обеспечивают такую семантику.

#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);

Оба системных вызова приостанавливают приложение до тех пор, пока в файл

fd
не будут записаны все данные,
fsync
также ожидает обновления информации в inode файла, подобной времени доступа (информация inode для файлов перечислена в табл. 11.3). Однако ни один из этих вызовов не гарантирует записи на неразрушимое устройство хранения. Современные дисковые приводы имеют большие собственные кэши, поэтому сбой питания может привести к тому, что некоторые данные, сохраненные в кэше, будут потеряны.

Поделиться с друзьями: