В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Файловый ввод/вывод
Блокировка сегмента описывается структурой 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 (см. листинг 5.25).
#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() (см. листинг 5.26).
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 ) устанавливает блокировки нескольких сегментов файла и засыпает на некоторое время (см. листинг 5.27), а вторая ( test_locks, см. листинг 5.28), выполняющаяся, разумеется, параллельно, в рамках другого процесса, получает и выводит информацию о заблокированных сегментах того же файла (см. листинг 5.29).
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <assert.h> #define LOCKFILE "my_lockfile" /* Программа устанавливает несколько блокировок */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT | O_TRUNC, S_IRWXU)) != -1); /* Установим блокировку на запись на весь файл */ lck.l_type = F_WRLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 0; lck.l_len = (off_t) 0; if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-1"); close (fd); return (-1); } /* Сделаем размер файла ненулевым */ if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) { perror ("LSEEK"); close (fd); return (-1); } if (write (fd, &lck, sizeof (lck)) != sizeof (lck)) { perror ("WRITE"); close (fd); return (-1); } /* Снимем блокировку в середине файла */ lck.l_type = F_UNLCK; lck.l_whence = SEEK_SET; lck.l_start = (off_t) 512; lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } /* Установим блокировку на чтение в конце файла */ lck.l_type = F_RDLCK; lck.l_whence = SEEK_END; lck.l_start = (off_t) -sizeof (lck); lck.l_len = (off_t) sizeof (lck); if (fcntl (fd, F_SETLK, &lck) == -1) { perror ("FCNTL-F_SETLK-2"); close (fd); return (-1); } sleep (10); return (close (fd)); }Листинг 5.27. Пример программы set_locks, устанавливающей блокировки файла.
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <assert.h> #define LOCKFILE "my_lockfile" /* Программа выявляет блокировки, установленные */ /* на файл LOCKFILE */ int main (void) { int fd; struct flock lck; assert ((fd = open (LOCKFILE, O_WRONLY)) != -1); (void) printf ("ид-р проц. тип начало длина\n"); /* Начнем с попытки установить блокировку на */ /* весь файл */ lck.l_whence = SEEK_SET; lck.l_start = 0; lck.l_len = 0; do { lck.l_type = F_WRLCK; (void) fcntl (fd, F_GETLK, &lck); if (lck.l_type != F_UNLCK) { (void) printf ("%9d %3c %7ld %5ld\n", lck.l_pid, (lck.l_type == F_WRLCK) ? 'W' : 'R', lck.l_start, lck.l_len); /* Если эта блокировка покрывает остаток файла, */ /* нет нужды выявлять другие блокировки */ if (lck.l_len == 0) break; /* Иначе поищем новую блокировку после найденной */ lck.l_start += lck.l_len; } while (lck.l_type != F_UNLCK); return (close (fd)); }Листинг 5.28. Пример программы test_locks, выявляющей блокировки файла.
ид-р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0Листинг 5.29. Возможный результат выполнения командной строки set_locks & test_locks.
Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.
Читателю предлагается выполнить командную строку set_locks & set_locks и объяснить полученный результат.
Функции setbuf(), setvbuf( ) и fflush() выполняют управляющие операции с буферами потоков (см. листинг 5.30).
#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:
_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() – необходимый элемент завершения транзакций, но он полезен и применительно к стандартному выводу (протоколу), если нужно выдать приглашение для пользовательского ввода на экран, а не в буфер (см. листинг 5.31).
char name [LINE_MAX]; (void) printf ("Введите Ваше имя: "); (void) fflush (stdout); (void) fgets (name, sizeof (name), stdin);Листинг 5.31. Пример использования функции fflush().