Скачать 17.46 Mb.
|
Глава 3. Файловый ввод-вывод Запись в таблице процессов ![]() ![]() ![]() Таблица виртуальных узлов Текущий размер файла
Запись в таблице процессов
Таблица файлов Флаги состояния файла Текущая позиция файла Указатель на виртуальный узел Флаги состояния файла Текущая позиция файла Указатель на виртуальный узел Информация виртуального узла Информация индексного узла Рис. 3.2. Два независимых процесса открыли один и тот же файл В таблице файлов после завершения каждой операции записи текущая позиция файла увеличивается на количество записанных байт. В том случае, если текущая позиция файла оказывается больше текущего размера файла, в таблице индексных узлов изменяется размер файла в соответствии с текущей позицией (это происходит, например, при добавлении новых данных в конец файла). Если файл был открыт с флагом 0_APPEND, соответствующий флаг устанавливается в таблице файлов. Каждый раз при выполнении операции записи в качестве текущей позиции принимается значение размера файла из таблицы индексных узлов. В результате запись всегда производится в конец файла. Если текущая позиция переносится в конец файла с помощью функции lseek, выполняется только перезапись значения текущего размера файла из таблицы индексных узлов в поле текущей позиции в таблице файлов. (Обратите внимание: это не то же самое, что открытие файла с флагом 0_APPEND, о чем мы будем говорить в разделе 3.11.) Функция lseek изменяет только значение текущей позиции файла в таблице файлов. Никаких операций ввода-вывода при этом не производится. 3.11. Атомарные операции 113 Существует возможность открыть несколько дескрипторов, которые будут ссылаться на одну и ту же запись в таблице файлов; мы увидим это в разделе 3.12 при обсуждении функции dup. То же самое происходит в результате вызова функции fork, когда родительский и дочерний процессы совместно используют одни и те же записи в таблице файлов для каждого из открытых дескрипторов (раздел 8.3). Обратите внимание на различия, которые существуют между флагами дескриптора и флагами состояния файла. Флаги дескриптора уникальны для каждого отдельно взятого дескриптора, открытого процессом, тогда как флаги состояния файла имеют отношение ко всем дескрипторам в любом процессе, которые ссылаются на одну и ту же запись в таблице файлов. Рассматривая функцию fcntl в разделе 3.14, мы узнаем, как можно получить и изменить значения флагов дескриптора и флагов состояния файла. Все описанное ранее в этом разделе прекрасно работает в том случае, когда несколько процессов читают данные из одного и того же файла. Каждый процесс имеет собственную запись в таблице файлов со своим собственным значением текущей позиции файла. Однако можно получить совершенно неожиданные результаты, если несколько процессов попытаются выполнить запись данных в один и тот же файл. Чтобы избежать в будущем неприятных сюрпризов, мы должны разобраться с понятием атомарности операций. 3.11. Атомарные операции Добавление данных в конец файла Рассмотрим процесс, который дописывает данные в конец файла. Старые версии UNIX не поддерживали флаг 0_APPEND для функции open, в результате приходилось писать нечто вроде: if (lseek(fd, OL, 2) < 0) /* переместить текущую позицию в конец файла */ err_sys("ошибка вызова функции lseek"); if (write(fd, buf, 100) != 100) /* и выполнить запись */ егr_sys("ошибка вызова функции write"); Такой код будет прекрасно работать в случае единственного процесса, но могут возникнуть определенные проблемы, если добавление данных в конец файла производится сразу несколькими процессами. (Подобная ситуация возможна, например, когда несколько процессов добавляют сообщения в файл журнала.) Допустим, существуют два независимых процесса А и В, которые выполняют запись данных в конец одного и того же файла. Каждый из процессов открыл файл, но без флага 0_APPEND. Эта ситуация изображена на рис. 3.2. Каждый процесс имеет собственную запись в таблице файлов, но при этом они ссылаются на одну и ту же запись в таблице виртуальных узлов. Предположим, что процесс А вызывает функцию lseek и устанавливает текущую позицию файла в значение 1500 (текущий размер файла). Затем ядро приостанавливает работу процесса А и передает управление процессу В, который 114 Глава 3. Файловый ввод-вывод ![]() Проблема в том, что операция «перейти в конец файла и записать данные» требует обращения к двум отдельным функциям (как мы только что показали). Решением проблемы было бы атомарное1 выполнение операции позиционирования и записи. Любая операция, которая требует обращения более чем к одной функции, не может быть атомарной, поскольку всегда существует вероятность того, что ядро временно приостановит процесс между двумя последовательными вызовами функций (как это было показано выше). ОС UNIX предоставляет возможность атомарного выполнения этой операции, если мы укажем флаг 0_APPEND при открытии файла. Как мы уже говорили в предыдущем разделе, этот флаг заставит ядро выполнять перенос текущей позиции в конец файла непосредственно перед операцией записи. А кроме того, отпадает необходимость вызывать функцию lseek перед каждым вызовом функции write. Функции pread и pwrite Стандарт Single UNIX Specification включает в себя расширения XSI, которые позволяют процессам атомарно выполнять операции перемещения текущей позиции и ввода-вывода. Эти расширения представлены функциями pread и pwrite. #include ssize_t pread(int filedes, void *buf, size J: nbytes, off_t offset); Возвращает количество прочитанных байт, 0 по достижении конца файла и -1 в случае ошибки ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset); Возвращает количество записанных байт или -1 в случае ошибки Вызов функции pread эквивалентен двум последовательным вызовам функций lseek и read со следующими отличиями: • При использовании pread нет возможности прервать выполнение этих двух операций То есть неделимое. - Примеч. перев. 3.12. Функции dup и dup2 115 • Значение текущей позиции файла не изменяется Вызов функции pwrite эквивалентен двум последовательным вызовам функций lseek и write с аналогичными отличиями. Создание файла Пример еще одной атомарной операции мы видели при описании флагов 0_CREAT и 0_EXCL функции open. При одновременном указании обоих флагов функция open будет завершаться ошибкой, если файл уже существует. Мы также говорили, что проверка существования файла и создание файла будут выполняться атомарно. Если бы не было такой атомарной операции, мы могли бы попробовать написать нечто вроде: if ((fd = open(pathname, 0_WR0NLY)) < 0) { if (errno == ENOENT) { if ((fd = creat(pathname, mode)) < 0) err_sys("ошибка вызова функции creat"); } else { err_sys("ошибка вызова функции open"); > } Эта ситуация чревата проблемами, если файл с тем же именем будет создан другим процессом между обращениями к функциям open и creat. Если другой процесс создаст файл между вызовами этих функций и успеет туда что-либо записать, то эти данные будут утеряны, когда первый процесс вызовет функцию creat. Объединение проверки существования файла и его создания в единую атомарную операцию решает эту проблему. Вообще говоря, термин атомарная операция относится к таким операциям, которые могут состоять из нескольких действий. Если операция атомарна, то либо все необходимые действия будут выполнены до конца, либо не будет выполнено ни одно из них. Атомарность не допускает выполнения лишь некоторой части действий. К теме атомарных операций мы еще вернемся, когда будем рассматривать функцию link (раздел 4.15) и блокировку отдельных записей в файле (раздел 14.3). 3.12. функции dup и dup2 Дубликат дескриптора существующего файла можно создать с помощью одной из следующих функций: #include int dup(int filedes)', int dup2(int filedes, int filedes2)\ Возвращают новый дескриптор файла или -1 в случае ошибки 116 Глава 3. Файловый ввод-вывод функция dup гарантирует, что возвращаемый ею новый файловый дескриптор будет иметь наименьшее возможное значение. При вызове функции dup2 мы указываем значение нового дескриптора в аргументе filedes2. Если дескриптор filedes2 перед вызовом функции уже был открыт, то он предварительно закрывается. Если значения аргументов filedes и filedes2 эквивалентны, то функция dup2 вернет дескриптор f iledes2, не закрывая его. Новый файловый дескриптор, возвращаемый функциями, будет ссылаться на ту же самую запись в таблице файлов, что и дескриптор filedes. Продемонстрируем это на рис .3.3. Запись в таблице процессов
Таблица файлов Флаги состояния файла Текущая позиция файла Указатель на виртуальный узел Таблица виртуальных узлов Информация виртуального узла Информация индексного узла Текущий размер файла Рис. 3.3. Структуры в ядре после вызова функции dup(l) На рисунке предполагается, что процесс при запуске выполняет код newfd = dup(1); Предполагается, что следующий доступный дескриптор - это число 3 (что наиболее вероятно, потому что командная оболочка уже открыла для процесса дескрипторы 0, 1 и 2). Поскольку оба дескриптора указывают на одну и ту же запись в таблице файлов, они совместно будут использовать флаги состояния файла - чтение, запись, добавление в конец файла и прочие, и текущая позиция файла будет для них также одинаковой. Каждый из дескрипторов будет иметь свой собственный набор флагов дескриптора. Как мы увидим в следующем разделе, функция dup всегда сбрасывает флаг close-on-exec («закрыть-при-вызове-ехес») в новом дескрипторе. Дубликат дескриптора можно создать также с помощью функции fcntl, которая будет описана в разделе 3.14. На самом деле вызов dup(filedes); эквивалентен вызову fcntl(filedes, F.DUPFD, 0); Аналогично вызов dup2(filedes, filedes2); 3.13. Функции sync, fsync и fdatasync 117 эквивалентен вызову close(filedes2); fcntl(filedes, F_DUPFD, filedes2); В последнем случае для функции dup2 приведен не совсем точный эквивалент из двух последовательных вызовов функций close и fcntl. Имеются следующие различия:
Системный вызов dup2 впервые появился в Version 7 и затем перекочевал в BSD. Возможность создания дубликатов дескрипторов с помощью fcntl появилась в System III и перешла в System V. В SVR3.2 была включена функция dup2, а в 4.2BSD - функция fcntl и функциональность F_DUPFD. Стандарт P0SIX.1 требует как наличия функции dup2,TaK и поддержки функцией fcntl параметра F_DUPFD. 3.13. Функции sync, fsync и fdatasync Традиционные реализации UNIX имеют в своем распоряжении буферный кэш или кэш страниц, через который выполняется большинство дисковых операций ввода-вывода. Когда мы записываем данные в файл, они, как правило, сначала помещаются ядром в один из буферов, а затем ставятся в очередь для записи на диск в более позднее время. Этот прием называется отложенной записью. (В главе 3 [Bach 1986] детально рассматривается работа буферного кэша.) Ядро обычно записывает отложенные данные на диск, когда возникает необходимость в повторном использовании буфера. Для синхронизации файловой системы на диске и содержимого буферного кэша существуют функции sync, fsync и fdatasync. tfinclude Возвращают значение 0 в случае успеха, -1 в случае ошибки void sync(void); Функция sync просто ставит все измененные блоки буферов в очередь для за-писи и возвращает управление - она не ждет, пока физически будет выполнена запись на диск. 118 |
![]() | Разработка серийного оформления художника В. Щербакова Серия основана в 1999 году | ![]() | Европа: вчера, сегодня, завтра / ран, Ин-т Европы; отв ред., авт предисл. Н. П. Шмелев. – М.: Экономика, 2002. – 823с |
![]() | Ричард Докинз профессор Оксфордского университета, автор таких известных книг, как "Эгоистический ген", "Слепой часовщик", "Расширенный... | ![]() | Он, бесформенный, присвоил чужую форму. Как такое могло случиться, Остин? Нет, как такое может быть? И почему тогда солнце не померкнет,... |
![]() | Ричард Флорида, «Креативный класс: люди, которые меняют будущее». М.: Классика-xxi, 2005. (Richard Florida, “The Rise of The Creative... | ![]() | Роджерсом (одним из этих проектов был получивший академическую премию документальный фильм "Путешествие в себя"). Ричард Фарсон получил... |
![]() | ![]() | ||
![]() | Борисов С. Б. Человек. Текст. Культура. Социогуманитарные исследования. Издание второе, дополненное. – Шадринск, 2007 – 556 с | ![]() | Барр, С. Россыпи головоломок [Текст] / Стивен Барр; пер с англ. Ю. Н. Сударева. М. Мир, 1987. 415 с |