Поведение read()
также зависит от того, был ли файл открыт с флагом O_NONBLOCK
. Для файлов многих типов O_NONBLOCK
не влияет ни на что. Файлы, для которых система может гарантировать завершенность операции в пределах разумного периода времени, всегда блокирует чтение и запись; такие файлы часто называют быстрыми файлами. Это множество файлов включает локальные блочные устройства и обычные файлы. Для других типов файлов, таких как каналы, и символьных устройств вроде терминалов процесс может ожидать другого процесса (или человека), чтобы тот либо выполнил чтение, либо освободил ресурсы системы при обработке запроса на write()
. В обоих случаях система не имеет способа знать — будет ли вообще возможно дождаться завершения системного вызова. Когда такие файлы открываются с флагом O_NONBLOCK
, то для каждой операции с файлом система просто делает максимум того, что удается сделать немедленно, а затем возвращает управление вызывающему процессу.
Неблокирующий ввод-вывод — это важная тема, и больше примеров вы найдете в главе 13. После стандартизации системного вызова poll()
, однако, необходимость в нем (особенно при чтении) минимизирована. Если вам нужно интенсивно использовать неблокирующий ввод-вывод, попробуйте пересмотреть свою программу в терминах poll()
, чтобы увидеть, нельзя ли ее сделать более эффективной.
Чтобы показать конкретный пример чтения и записи файлов, приведем простую реализацию cat
. Она копирует стандартный поток ввода (stdin) на стандартный вывод (stdout) до тех пор, пока есть что копировать.
1: /* cat.с */
2:
3: #include
4: #include
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
int truncate(const char *pathname, size_t length);
int ftruncate(int fd, size_t length);
Размер файла устанавливается равным length
, и все данные, находящиеся за новым концом файла, теряются.
Если length
больше текущего размера файла, то файл увеличивается до заданного размера (по возможности используя "дырки"), хотя такое поведение и не гарантируется POSIX, поэтому на него нельзя полагаться при написании переносимых программ.
11.2.7. Синхронизация файлов
Когда программа пишет данные в файл, обычно они сохраняются в кэше ядра до тех пор, пока оно не выполнит запись на физический носитель (такой как жесткий диск), но ядро возвращает управление программе сразу после того, как данные скопируются в кэш. Это обеспечивает значительный рост производительности, так как позволяет ядру определять порядок записи на диск и объединять несколько записей в одну блочную операцию. Однако в случае системного сбоя у такой технологии есть два существенных недостатка, которые могут оказаться важными. Например, приложение, которое предполагает, что данные сохранены в базе данных прежде, чем был сохранен индекс для этих данных, может не справиться со сбоем, явившимся результатом того, что индекс был просто обновлен.