Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 5:

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

Блокировка сегмента описывается структурой 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 &amp; 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:

_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() – необходимый элемент завершения транзакций, но он полезен и применительно к стандартному выводу (протоколу), если нужно выдать приглашение для пользовательского ввода на экран, а не в буфер (см. листинг 5.31).

char name [LINE_MAX];
(void) printf ("Введите Ваше имя: "); 
(void) fflush (stdout); (void) fgets (name, 
   sizeof (name), stdin);
Листинг 5.31. Пример использования функции fflush().
Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?