учебники, программирование, основы, введение в,

 

Файловый ввод/вывод

Основные понятия
В стандарте POSIX-2001 предусмотрены две основные группы функций, обслуживающие операции ввода/вывода:

  • функции нижнего уровня, использующие упоминавшиеся ранее целочисленные файловые дескрипторы (эти функции и ассоциирован- ные объекты определены в заголовочном файле <fcntl.h>);
  • функции более высокого уровня, осуществляющие буферизованный ввод/вывод с применением потоков (см. <stdio.h>).

Применение потоков не расширяет базовых возможностей ввода/вывода, поскольку в конечном счете используются функции нижнего уровня, но в отличие от них позволяет приложениям получить дополнительный сервис в виде управляемой буферизации и форматного ввода/вывода.
Согласно стандарту POSIX-2001, поток – это объект, служащий для доступа к файлам как к упорядоченной последовательности символов.
Поток представляется структурой типа FILE, с которой ассоциирован соответствующий дескриптор открытого файла. Несколько дескрипторов и/или потоков могут ссылаться на одно описание открытого файла.
Существенны не только содержимое, но и адрес объекта типа FILE; копия подобного объекта не обязательно является корректным представлением потока.
И файловые дескрипторы, и потоки формируются в результате выполнения функций открытия файлов, которые должны предшествовать операциям ввода/вывода. Имеется, однако, три предопределенных потока: стандартный ввод, стандартный вывод и стандартный протокол, открываемые окружением времени выполнения еще перед началом работы C-программ. Для обращения к ним служат указатели на объекты типа FILE с именами, соответственно, stdin, stdout и stderr.
При открытии файлов указывается вид последующих операций ввода/вывода: чтение, запись, модификация (чтение и запись), добавление (запись в конец). Вид операций должен быть согласован с правами доступа к файлу; в противном случае открытие закончится неудачей.
Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.
В дальнейшем индикатор текущей позиции смещается под воздействием операций чтения, записи и позиционирования, чтобы упростить последовательное продвижение по файлу.
Потоки могут быть полностью буферизованными, буферизованными построчно и небуферизованными. В первом случае передача байт из файла/в файл осуществляется преимущественно блоками, когда буфер оказывается заполненным. При построчной буферизации передача данных также осуществляется блоками, по достижении символа перевода строки или заполнении буфера. При отсутствии буферизации байты передаются по возможности без задержек.
Стандартом C99 [5] предусмотрены байтные и широкие символы. Соответственно, в стандарте POSIX-2001 введено понятие ориентации потока, которая может быть широкой или байтной. Задает ориентацию первая после открытия файла   ввода/вывода операция. Если вначале применяется функция ввода/вывода широких символов, поток получает широкую ориентацию; в противном случае – байтную. Сменить ориентацию можно только повторным открытием файла; применяемые к потоку функции ввода/вывода должны соответствовать его ориентации.
После завершения работы с файлом его необходимо закрыть. При этом не только разрывается связь между файлами с одной стороны и дескрипторами и потоками с другой, но и обеспечивается передача данных, остававшихся буферизованными.
Стандарт POSIX-2001 предусматривает как синхронный, так и асинхронный ввод/вывод.
Операция асинхронного ввода/вывода сама по себе не способна привести к приостановке процесса (потока управления) и лишить его возможности использовать процессор. Это означает, что процесс и асинхронная операция ввода/вывода могут выполняться параллельно.
При синхронном вводе/выводе процесс (поток управления) приостанавливается до завершения запрошенной операции обмена данными.
С одним файлом могут параллельно работать несколько процессов, выполняющихся на разных хостах (узлах сети) и использующих разные буфера. Для корректного обслуживания подобной ситуации в стандарте POSIX-2001 определено (в качестве необязательной возможности) понятие синхронизированного ввода/вывода – так называют механизм, повышающий детерминированность и устойчивость средств обмена данными, поэтому приложение может быть уверено, что данные, которыми оно манипулирует, физически присутствуют на устройствах вторичной (массовой, стабильной) памяти и наряду с файлами находятся в целостном состоянии.
Операция синхронизированного ввода/вывода считается завершенной, когда данные успешно переданы или она диагностирована как неудачная. Различают завершение с целостностью данных и с целостностью файлов.
Синхронизированное чтение с целостностью данных считается завершенным, когда образ данных успешно передан запрашивающему процессу. Если в момент запроса операции чтения оставались незавершенные операции записи, пересекающиеся с чтением по данным, сначала должны быть обслужены запросы на запись.
Синхронизированная запись с целостностью данных завершена, когда данные успешно переданы и, кроме того, модифицирована и передана в стабильную память информация о файловой системе, необходимая для последующей выборки данных (такая, например, как размер файла).
Синхронизированная операция с целостностью файлов отличается только тем, что к моменту ее завершения дополнительно должны быть модифицированы и переданы в стабильную память все атрибуты файлов, относящиеся к вводу/выводу (включая время последнего доступа, изменения файлов и их статуса, не влияющие на выборку данных).

http://localhost:3232/img/empty.gifОткрытие и закрытие файлов

Как уже указывалось, открытие файла должно предшествовать операциям ввода/вывода, поскольку оно возвращает дескриптор файла или поток, которые используют подобные операции. Для открытия файлов и формирования новых описаний открытых файлов, файловых дескрипторов и потоков служат функции нижнего уровня open() и pipe() , а также функции буферизованного ввода/вывода, показанные в.
#include <fcntl.h>
int open (const char *path, int oflag, ...);
#include <unistd.h>
int pipe (int fildes [2]);
Листинг 5.1. Описание функций open() и pipe().
#include <stdio.h>
FILE *fopen (const char *restrict path,
const char *restrict mode);
#include <stdio.h>
FILE *fdopen (int fildes,
const char *mode);
#include <stdio.h>
FILE *freopen (const char *restrict path, const
char *restrict mode, FILE *restrict stream);
Листинг 5.2. Описание функций fopen(), fdopen(), freopen().
Функция open() открывает файл с заданным маршрутным именем (первый аргумент, path), создавая для него описание – новое и, следовательно, не разделяемое с другими процессами. Возвращаемый в качестве результата файловый дескриптор является минимальным из числа не используемых в данный момент текущим процессом (при неудаче возвращается -1).
Второй аргумент, oflag, устанавливает флаги статуса файла и определяет допустимые виды операций ввода/вывода. Его значение формируется как побитное ИЛИ перечисленных ниже флагов. Из первых трех флагов должен быть задан ровно один.
O_RDONLY
Открыть файл только на чтение.
O_WRONLY
Открыть файл только на запись.
O_RDWR
Открыть файл на чтение и запись.
Следующие флаги могут комбинироваться произвольным образом.
O_APPEND
Перед каждой записью устанавливать индикатор текущей позиции на конец файла.
O_CREAT
Если файл существует, данный флаг принимается во внимание только при наличии описываемого далее флага O_EXCL. Если файла нет, он создается от имени текущего пользователя. Обратим внимание, что функция open() имеет переменное число аргументов. При создании файла предполагается, что в качестве третьего аргумента типа mode_t задается режим доступа.
O_EXCL
Если установлены флаги O_CREAT и O_EXCL, а файл существует (хотя бы и в виде символьной ссылки на несуществующий файл), вызов open() завершится неудачей. Проверка существования файла и его создание представляют собой атомарное действие по отношению к попыткам других процессов (потоков управления) выполнить аналогичный запрос.
O_TRUNC
Если файл существует, является обычным и успешно открывается с флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается равным нулю).
Отметим, что рассмотренная ранее функция creat (path, mode) по определению эквивалентна вызову open (path, O_WRONLY | O_CREAT | O_TRUNC, mode).
Следующий флаг относится к асинхронному вводу/выводу.
O_NONBLOCK
Обычно, если канал открывается только на чтение (или только на запись), процесс (поток управления) блокируется, пока этот же канал не откроют на запись (чтение). При установленном флаге O_NONBLOCK открытие на чтение завершается без задержки, а открытие на запись заканчивается неудачей, если канал еще не открыт кем-либо на чтение.
При открытии специального файла вызов open() завершается только после того, как устройство оказывается в состоянии готовности. Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе- ние устройства зависит от реализации.
Следующая группа флагов обслуживает синхронизированный ввод/вывод.
O_DSYNC
Операции записи с возвращаемым файловым дескриптором должны завершаться с обеспечением целостности данных.
O_SYNC
Операции записи должны завершаться с обеспечением целостности файла.
O_RSYNC
Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.
Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.
O_NOCTTY
Если открывается специальный файл, соответствующий терминалу, последний не должен становиться управляющим терминалом процесса. Для создания и открытия канала предназначена функция pipe(). В массиве fildes она возвращает сразу два дескриптора: fildes [0]служит для чтения, fildes [1] – для записи. Данные читаются в том же порядке, в каком были записаны.
При успешном завершении pipe() возвращает 0, при неудаче – -1.
Функция fopen() из группы буферизованного ввода/вывода по сути аналогична open(), только вместо файлового дескриптора в качестве результата возвращается указатель на объект, служащий для управления сформированным потоком (в случае неудачи результат равен пустому указателю NULL).
Второй аргумент, mode, определяющий допустимые виды операций ввода/вывода, задается как цепочка символов. Он может принимать следующие значения.
"r"
Открыть файл на чтение.
"w"
Опустошить или создать файл, открыв его на запись.
"a"
Открыть или создать файл на запись в конец.
"r+"
Открыть файл на изменение (чтение и запись).
"w+"
Опустошить или создать файл, открыв его на изменение.
"a+"
Открыть или создать файл на изменение с записью в конец.
(Стандарт языка C позволяет приписывать к перечисленным цепочкам символ 'b', который, впрочем, ни на что не влияет.)
Если открытый файл не соответствует интерактивному устройству, ассоциированный с ним поток полностью буферизуется.
Функция fdopen() формирует поток, ассоциированный с дескриптором ранее открытого файла.
Второй аргумент, mode, может принимать те же значения, что и для fopen(), но их трактовка по понятным причинам отличается: существующие файлы не опустошаются, а новые не создаются.
Функция freopen() предназначена для ассоциирования существующего потока (третий аргумент, stream) с заданным файлом (первый аргумент, path) и разрешенными видами операций ввода/вывода (второй аргумент, mode).
В первую очередь freopen() пытается вытолкнуть буфера потока stream и закрыть ассоциированный с ним файл. Неудача данного действия ни на что не влияет.
Затем, аналогично fopen(), открывается заданный файл, только без формирования нового потока; результатом служит stream (лишенный, правда, ориентации) или NULL (в случае неудачи).
Если в качестве первого аргумента функции freopen() задан пустой указатель, делается попытка изменить виды операций, разрешенные для потока   stream. (Формально можно считать, что первым аргументом freopen() служит имя файла, ассоциированного с этим потоком.) От реализации зависит, разрешены ли подобные действия вообще и при каких условиях они завершаются успешно.
Для закрытия файлов (точнее, файловых дескрипторов или потоков) предназначены функции close() и fclose().
#include <unistd.h>
int close (int fildes);
#include <stdio.h>
int fclose (FILE *stream);
Листинг 5.3. Описание функций close() и fclose().
Функция close() освобождает файловый дескриптор fildes, который становится доступным для последующего использования при открытии файлов.
Когда закрывается последний дескриптор, ссылающийся на описание открытого файла, оно освобождается.
Если число жестких ссылок на файл равно нулю и закрывается последний ассоциированный с ним дескриптор, файл перестает быть доступным, а занимавшееся им пространство освобождается.
Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.
Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.
Функция fclose() по сути аналогична, только она освобождает поток, выталкивая при этом буфера. Признаком успешного завершения также служит 0, признаком неудачи – константа EOF.
Приведем примеры использования описанных функций. Сочетание флагов O_CREAT и O_EXCL функции open() позволяет организовать проверку и создание файлов-замков, для которых важен факт существования в одном экземпляре, а не содержимое .
Листинг 5.4. Пример программы, использующей функции open() и close().
Читателю предлагается выполнить приведенную программу дважды.
Следующая программа иллюстрирует перенаправление стандартного вывода в файл. Ее тоже полезно выполнить дважды и затем самостоятельно осмыслить результаты.
Листинг 5.5. Перенаправление стандартного вывода с помощью функции freopen().
Весьма полезной с практической точки зрения является функция создания и открытия временных файлов tmpfile().
#include <stdio.h>
FILE *tmpfile (void);
Листинг 5.6. Описание функции tmpfile().
Временный файл открывается на изменение (w+) и автоматически удаляется после закрытия всех ссылок на него.
Использование функции tmpfile() предпочтительнее генерации «временного» имени с помощью функции tmpnam() и последующего создания файла с этим именем, поскольку в промежутке какой-либо другой процесс может создать одноименный файл.

http://localhost:3232/img/empty.gifЧтение и запись данных

Чтение данных из файла выполняют функции read() и fread().
#include <unistd.h>
ssize_t read (int fd, void *buf, size_t nbyte);
#include <stdio.h>
size_t fread (void *restrict buf, size_t size,
size_t nitems, FILE *restrict stream);
Листинг 5.7. Описание функций read() и fread().
Функция read() пытается прочитать nbyte байт из файла, ассоциированного с дескриптором fd, и поместить их в буфер buf.
Для файлов, допускающих позиционирование, read() выполняет чтение, начиная со значения индикатора текущей позиции, ассоциированного с дескриптором fd. После завершения операции этот индикатор увеличивается на количество прочитанных байт. Для устройств, не поддерживающих позиционирования (таких, например, как терминал), значение упомянутого индикатора не определено, а чтение выполняется с текущей позиции устройства.
При успешном завершении read() возвращает количество байт, реально прочитанных и помещенных в буфер; это значение может оказаться меньше значения аргумента nbyte, если до конца файла оставалось меньше, чем nbyte байт. Например, если текущая позиция совпадала с концом файла, результат будет равен 0. В случае ошибки возвращается -1.
Функция буферизованного ввода/вывода   fread() во многом аналогична read(), но число читаемых байт задается как произведение размера одного элемента (аргумент size) на число элементов (аргумент nitems), а результатом служит количество успешно прочитанных элементов. В стандарте оговаривается, что элементы читаются побайтно.
Число элементов, успешно прочитанных функцией fread(), может быть меньше затребованного, только если достигнут конец файла или произошла ошибка чтения. В таком случае fread() устанавливает для потока индикатор ошибки или конца файла, проверить которые позволяют функции feof() и ferror(), соответственно, возвращая при установленном индикаторе ненулевой результат.
#include <stdio.h>
int feof (FILE *stream);
#include <stdio.h>
int ferror (FILE *stream);
Листинг 5.8. Описание функций feof() и ferror().
Отметим, что использование функции бинарного ввода   fread() ограничивает мобильность приложений, так как результат зависит от размера элементов и порядка байт, поддерживаемого процессором.
Обратим также внимание на некоторые нюансы синхронного и асинхронного ввода с помощью функции read(). При попытке чтения из пустого канала, не открытого кем-либо на запись, результат равен 0 (как признак конца файла). Если пустой канал открыт кем-либо на запись, при установленном флаге O_NONBLOCK возвращается -1 (как признак ошибки EAGAIN); при отсутствии флага O_NONBLOCK процесс (поток управления) блокируется до появления данных в канале. Аналогичным образом устроен ввод из файлов других типов, поддерживающих чтение в асинхронном режиме.
Содержимое символьных ссылок приходится читать особым образом (хотя бы потому, что обычно функция open() раскрывает их, т. е. открывает указуемый файл). Для этого служит функция readlink(). Она помещает содержимое ссылки с именем link_name в буфер buf длины buf_size (если буфер мал, остаток содержимого отбрасывается). Результат равен числу помещенных в буфер байт или -1 в случае неудачи.
#include <unistd.h>
ssize_t readlink (const char *restrict
link_name, char *restrict buf,
size_t buf_size);
Листинг 5.9. Описание функции readlink().
Следующая программа переправляет недлинные сообщения с управляющего терминала процесса (ему соответствует специальный файл /dev/tty) на стандартный вывод до тех пор, пока не будет введен символ конца файла.
Листинг 5.10. Пример чтения из файла.
В качестве примера мобильного использования функции fread(), а также функций feof() и ferror(), рассмотрим программу, подсчитывающую число символов, слов и строк в файле – аргументе командной строки.
Листинг 5.11. Программа, подсчитывающая число строк, слов и символов в файле.
Читателю предлагается убрать из цикла проверку feof (fp) и оценить, как изменится обработка интерактивного стандартного ввода.
Для иллюстрации использования функции readlink() напишем программу, выдающую на стандартный вывод содержимое символьных ссылок, имена которых заданы в командной строке.
Листинг 5.12. Пример программы, читающей содержимое символьных ссылок.
Запись данных в файл выполняют функции write() и fwrite().
#include <unistd.h>
ssize_t write (int fildes, const void *buf,
size_t nbyte);
#include <stdio.h>
size_t fwrite (const void *restrict buf,
size_t size,       size_t nitems,
FILE *restrict stream);
Листинг 5.13. Описание функций write() и fwrite().
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла может увеличиться. Если файл открыт на добавление, запись производится в его конец.
http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifПри записи в канал, если флаг O_NONBLOCK не установлен, процесс (поток управления) может быть отложен, но после нормального завершения функция write() вернет nbyte. При установленном флаге O_NONBLOCK поведение зависит от значения nbyte и наличия свободного места в канале. Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего (в последнем случае результат будет равен -1). При попытке записать порцию данных большего размера запишется сколько можно или ничего.
Приведем несколько примеров. Следующая программа выводит приветствие на управляющий терминал.
Листинг 5.14. Пример программы, использующей функцию write().
Программа prnmyself выводит свой собственный исходный текст. При этом применяется следующий прием: данные фиксированными порциями читаются из файла и выводятся на терминал; процесс повторяется до тех пор, пока число реально прочитанных байт совпадает с указанным (до обнаружения конца файла).
Листинг 5.15. Пример программы, использующей функции read() и write().
Для буферизованного ввода/вывода байт служат функции fgetc() и fputc(), строки рекомендуется вводить, вызывая функцию fgets(), а выводить с помощью функций fputs() и puts().
#include <stdio.h>
int fgetc (FILE *stream);
#include <stdio.h>
int fputc (int c, FILE *stream);
#include <stdio.h>
char *fgets (char *restrict s, int n,
FILE *restrict stream);
#include <stdio.h>
int fputs (const char *restrict s,
FILE *restrict stream);
#include <stdio.h>
int puts (const char *s);
Листинг 5.16. Описание функций fgetc(), fputc(), fgets(), fputs(), puts().
Описание аналогичных функций для широких символов приведено в.
#include <stdio.h>
#include <wchar.h>
wint_t fgetwc (FILE *stream);
#include <stdio.h>
#include <wchar.h>
wint_t fputwc (wchar_t wc, FILE *stream);
#include <stdio.h>
#include <wchar.h>
wchar_t *fgetws (wchar_t *restrict ws, int n,
FILE *restrict stream);
#include <stdio.h>
#include <wchar.h>
int fputws (const wchar_t *restrict ws,
FILE *restrict stream);
Листинг 5.17. Описание функций fgetwc(), fputwc(), fgetws(), fputws().
Функция fgetc() пытается прочитать из заданного потока один байт, преобразовать его из типа unsigned char в int и вернуть в качестве результата. В случае ошибки или при достижении конца файла возвращается константа EOF.
Функция fgets() читает из заданного потока и помещает в буфер с адресом s (n - 1) байт или строку, включая символ перевода строки, или байты, оставшиеся до конца файла, если длина строки или число байт до конца меньше (n - 1). После прочитанных добавляется нулевой байт.
При нормальном завершении fgets() возвращает s. В случае ошибки или при достижении конца файла возвращается пустой указатель.
Функция fputc() помещает в поток значение c, предварительно преобразовав его к типу unsigned char. Результатом служит c или EOF.
Функция fputs() выводит в поток цепочку символов (без завершающего нулевого байта) и возвращает неотрицательное целое число или EOF. Функция puts() делает то же для потока stdout, завершая вывод переводом строки.
Функции для работы с потоками широкой ориентации устроены аналогично с точностью до замены int на wint_t, char на wchar_t и EOF на WEOF.
Программа, показанная в, иллюстрирует использование функций fgets() и fputs(). Читателю предлагается сравнить ее с.
Листинг 5.18. Пример использования функций fgets() и fputs().
Использование функций fgetc() и fputc() иллюстрируется программой, написанной С.В. Самборским. Она выполняет раскодировку файлов формата base64, применяемого, например, в электронной почте.
Листинг 5.19. Пример использования функций fgetc() и fputc().
Приведенный пример показывает, что написание мобильных программ даже для сравнительно простых задач требует заметных усилий. В данном случае пришлось воспользоваться нестандартной возможностью – включаемым файлом <endian.h>, содержащим препроцессорные константы, которые описывают порядок байт в машинном слове.
Отметим также стиль обработки ошибочных ситуаций, основанный на применении макроса assert. Для авторов программ такой стиль, безусловно, упрощает жизнь, исходный текст получается более компактным, однако для пользователей сообщение вида
decode64: decode64.c:39: main: Assertion
`((void *)0) != (input = fopen (argv [1], "r"))'
failed.
может оказаться менее информативным, чем выдача функции perror().
Управляющие операции с файлами и ассоциированными данными
К числу управляющих операций с файлами мы отнесем прежде всего позиционирование. Индикатор текущей позиции может быть опрошен или передвинут при помощи функции нижнего уровня lseek(), а также функций буферизованного ввода/вывода   fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind().
#include <unistd.h>
off_t lseek (int fildes, off_t offset,
int whence);
#include <stdio.h>
int fseek (FILE *stream, long offset,
int whence);
long ftell (FILE *stream);
off_t ftello (FILE *stream);
int fgetpos (FILE *restrict stream,
fpos_t *restrict pos);
int fsetpos (FILE *stream, const fpos_t *pos);
void rewind (FILE *stream);
Листинг 5.20. Описание функций lseek(), fseek(), ftell(), ftello(), fgetpos(), fsetpos(), rewind().
Функция lseek() устанавливает индикатор текущей позиции следующим образом. Сначала, в зависимости от значения третьего аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).
Индикатор текущей позиции можно сместить за конец файла (причем его размер не изменится). Если с этой позиции будет произведена запись, в файле образуется дыра, чтение из которой выдает нулевые байты.
Результатом функции lseek() служит новое значение индикатора текущей позиции, отсчитанное в байтах от начала файла. В случае ошибки возвращается (off_t) (-1), а текущая позиция остается прежней.
Функция fseek() по сути аналогична, только в случае нормального завершения возвращается 0. Кроме того, если поток имеет широкую ориентацию, значение аргумента whence должно равняться SEEK_SET, а значение offset – нулю или результату вызова функции ftell() для того же потока.
Функция ftell() возвращает значение индикатора текущей позиции для заданного потока (в случае ошибки – (long) (-1)). Функция ftello() эквивалентна ftell() с точностью до типа результата; в новых приложениях рекомендуется использовать ftello(), так как эта функция применима к большим файлам.
Функции fgetpos() и fsetpos() являются парными. Первая из них заполняет структуру, на которую указывает аргумент pos, а вторая применяет ее для установки индикатора текущей позиции. Нормальным результатом служит 0.

Функция rewind(), если пренебречь некоторыми тонкостями, сводится к вызову
(void) fseek (stream, 0L, SEEK_SET).
Приведем несколько примеров. В показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.
(void) lseek (fildes, (off_t) 0, SEEK_SET);
(void) lseek (fildes, (off_t) 0, SEEK_END);
(void) lseek (fildes, inc, SEEK_CUR);
Листинг 5.21. Примеры вызова функции lseek().
Применение функций буферизованного ввода/вывода иллюстрируется программой, показанной в. Отметим, что она не является мобильной относительно смены порядка байт, поддерживаемого процессором.
Листинг 5.22. Пример использования функций буферизованного ввода/вывода.
Функция fcntl() предназначена для выполнения разнообразных управляющих операций над открытым файлом .
#include <fcntl.h>
int fcntl (int fildes, int cmd, ...);
Листинг 5.23. Описание функции fcntl().
Аргумент fildes задает дескриптор открытого файла, cmd – управляющую команду, дополнительные данные для которой могут быть переданы в качестве третьего (необязательного) аргумента arg (обычно имеющего тип int). В случае успешного завершения возвращаемое значение естественным образом зависит от команды; при неудаче всегда возвращается -1.
Допустимые команды (значения аргумента cmd) определены в файле <fcntl.h>. Перечислим сначала наиболее употребительные из них.
F_DUPFD
Дублирование дескриптора открытого файла: вернуть минимальный среди бывших незанятыми файловый дескриптор (не меньший, чем arg) и ассоциировать его с тем же описанием открытого файла, что и fildes.
F_GETFL
Вернуть флаги статуса и режим доступа к файлу (см. выше описание функции open()). Для выделения режима доступа из возвращаемого значения предоставляется маска O_ACCMODE.
F_SETFL
Установить флаги статуса файла в соответствии со значением третьего аргумента arg.
Приведем пример использования описанных команд функции fcntl(). Перенаправить стандартный вывод в файл, а затем стандартный протокол на стандартный вывод позволяет программа, показанная в (читателю рекомендуется сопоставить ее с программой перенаправления стандартного вывода с помощью функции freopen(), ). Кроме того, в ней демонстрируется изменение набора флагов статуса файла, ассоциированного с дескриптором.
Листинг 5.24. Пример перенаправления стандартного вывода в файл, а стандартного протокола – на стандартный вывод.
Команды F_GETFD и F_SETFD функции fcntl() предназначены, соответственно, для опроса и изменения флагов, ассоциированных с заданным файловым дескриптором (а не с файлом). В стандарте упомянут один такой флаг – FD_CLOEXEC, предписывающий закрывать дескриптор при смене программы процесса.
Особый класс управляющих операций с файлами, имеющих свою систему понятий, составляют блокировки, хотя и они оформляются как команды функции fcntl().
Назначение механизма блокировок – дать возможность процессам (потокам управления), одновременно обрабатывающим одни и те же данные, синхронизировать свою работу. В стандарте POSIX-2001 представлены только так называемые рекомендательные блокировки, которые взаимодействуют исключительно с другими блокировками и не препятствуют операциям ввода/вывода. Это значит, что для достижения синхронизации процессы должны окружать операции ввода/вывода разделяемых данных операциями установки и снятия соответствующей блокировки.
Блокировка связывается с сегментом (произвольным последовательным участком) файла. Различают блокировку на запись и на чтение. Блокировка на запись носит монопольный характер. Когда сегмент блокирован на запись, никакие другие процессы не имеют возможности заблокировать на чтение или запись этот же или пересекающийся с ним сегмент.
Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.
Для установки блокировки на чтение требуется, чтобы файл был открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись.
http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifБлокировка сегмента описывается структурой flock, которая, согласно стандарту, должна содержать по крайней мере следующие поля:
short l_type /* Тип блокировки: */
   /* F_RDLCK (на чтение) */
   /* F_WRLCK (на запись), */
   /* F_UNLCK (снятие блокировки) */
short l_whence/* Точка отсчета для смещения */
   /* l_start: SEEK_SET, SEEK_CUR */
   /* или SEEK_END */
off_t l_start /* Смещение в байтах начала */
   /* блокируемого сегмента */
   /* относительно точки отсчета */
off_t l_len /* Размер блокируемого сегмента. */
   /* 0 означает блокировку до конца файла. */
pid_t l_pid /* Идентификатор процесса,*/
   /* установившего блокировку; */
   /* (возвращается по команде F_GETLK, */
   /* см. далее) */
Отметим, что блокируемый сегмент может выходить за конец, но не за начало файла. Длину сегмента разрешается задавать отрицательным числом, если тип off_t допускает такую возможность; в таком случае смещение начала блокируемого сегмента относительно точки отсчета равно l_start+l_len, а смещение конца – l_start-1.
Приведем несколько примеров заполнения структуры flock.
#include <fcntl.h>
struct flock lck;
. . .
lck.l_type = F_RDLCK;
    /* Блокировка на чтение */
    /* всего файла */
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
Листинг 5.25. Примеры заполнения структуры flock.
Установка блокировки осуществляется управляющими командами F_SETLK и F_SETLKW функции fcntl().
if (fcntl (fd, F_SETLK, &lck) != -1) ...
if (fcntl (fd, F_SETLKW, &lck) != -1) ...
Листинг 5.26. Примеры вызова функции 2 для установки блокировок.
Если блокировка не может быть установлена, выполнение команды F_SETLK немедленно завершается, и возвращается -1. Команда F_SETLKW отличается только тем, что в аналогичной ситуации процесс переходит в состояние ожидания до тех пор, пока нужный сегмент файла не будет разблокирован.
Для снятия блокировки можно воспользоваться командами F_SETLK или F_SETLKW. Для этого значение поля l_type должно быть установлено равным F_UNLCK.
Получить характеристики блокировки, мешающей установить новую, позволяет управляющая команда F_GETLK (новая блокировка задается структурой, на которую при обращении к fcntl() указывает третий аргумент arg). Если запрашиваемую блокировку установить нельзя, информация о первой помещается в ту же структуру; в частности, будет задано значение поля l_pid – оно идентифицирует процесс, установивший блокировку. (Естественно, значение поля l_whence будет установлено равным SEEK_SET.) Если нет помех для создания нужной блокировки, полю l_type присваивается значение F_UNLCK, а остальные поля в структуре не изменяются.
При закрытии файлового дескриптора все блокировки, установленные в файле текущим процессом, снимаются.
Приведем пример двух программ, первая из которых (назовем ее set_locks) устанавливает блокировки нескольких сегментов файла и засыпает на некоторое время , а вторая (test_locks, выполняющаяся, разумеется, параллельно, в рамках другого процесса, получает и выводит информацию о заблокированных сегментах того же файла.
Листинг 5.27. Пример программы set_locks, устанавливающей блокировки файла.
Листинг 5.28. Пример программы test_locks, выявляющей блокировки файла.
ид-р проц. тип начало длина
31174      W    0     512
31174      W    528   496
31174      R    1024  16
31174      W    1040  0
Листинг 5.29. Возможный результат выполнения командной строки set_locks &amp; test_locks.
Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.
Читателю предлагается выполнить командную строку set_locks & set_locks и объяснить полученный результат.
Функции setbuf(), setvbuf() и fflush() выполняют управляющие операции с буферами потоков.
#include <stdio.h>
void setbuf (FILE *restrict stream,
   char *restrict buf);
#include <stdio.h>
int setvbuf (FILE *restrict stream,
   char *restrict buf, int type, size_t size);
#include <stdio.h>
int fflush (FILE *stream);
Листинг 5.30. Описание функций setbuf(), setvbuf() и fflush().
Функция setvbuf(), которую можно использовать после открытия файла, но до первой операции ввода/вывода, устанавливает режим буферизации в соответствии со значением своего третьего аргумента, type:
_IOFBF– полная буферизация;
_IOLBF– построчная буферизация;
_IONBF– отсутствие буферизации.
Функция setvbuf() сама резервирует буфера заданного размера, но если аргумент buf не является пустым указателем, может использоваться и пользовательский буфер. В случае нормального завершения функция возвращает 0.
Функцию setbuf() можно считать частным случаем setvbuf(). С точностью до возвращаемого значения вызов setbuf (stream, NULL) эквивалентен setvbuf (stream, NULL, _IONBF, BUFSIZ) (отмена буферизации); если же значение buf не равно NULL, вызов setbuf (stream, buf) сводится к setvbuf (stream, buf, _IOFBF, BUFSIZ) (полная буферизация).
Функцию setbuf() чаще всего применяют для отмены буферизации стандартного вывода и/или стандартного протокола, выполняя вызовы setbuf (stdout, NULL) и/или setbuf (stderr, NULL).
Использование функций setbuf() и setvbuf() требует известной аккуратности. Типичная ошибка – указание в качестве аргумента buf автоматического массива, определенного внутри блока, и продолжение работы с потоком после выхода из этого блока.
Функция fflush() выталкивает буфера, помещая в файл предназначенные для записи, но еще не записанные данные. Если в качестве аргумента stream задан пустой указатель, выталкиваются все буфера всех потоков.
Вызов fflush() – необходимый элемент завершения транзакций, но он полезен и применительно к стандартному выводу (протоколу), если нужно выдать приглашение для пользовательского ввода на экран, а не в буфер.
char name [LINE_MAX];
(void) printf ("Введите Ваше имя: ");
(void) fflush (stdout); (void) fgets (name,
   sizeof (name), stdin);
Листинг 5.31. Пример использования функции fflush().

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика