13.3.4. Аренда файла

13.3.4. Аренда файла

И рекомендательное, и обязательное блокирование предназначены для предотвращения доступа процесса к файлу или его части, которая используется другим процессом. Когда блокировка установлена, процесс, которому необходим доступ к файлу, должен подождать завершения процесса, владеющего блокировкой. Эта структура подходит для большинства применений, но иногда программа использует файл до тех пор, пока он не понадобится другой программе, и желает получить при необходимости эксклюзивный доступ к файлу. Для этого Linux предлагает механизм аренды файлов (в других системах это называется периодическими блокировками (oplocks))[93].

Взятие файла в аренду позволяет процессу получать уведомления (через сигнал) о доступе к файлу со стороны другого процесса. Существуют два типа аренды: аренда чтения и аренда записи. Аренда чтения вызывает передачу сигнала при открытии файла для записи, открытии с указанием O_TRUNC или вызове truncate(). Аренда записи также посылает сигнал при открытии файла для чтения[94]22. Аренды файлов работают только для модификаций, внесенных в файл той же системой, которая владеет арендой. Если файл локальный (не файл, доступ к которому возможен через сеть), любой подходящий доступ к файлу инициирует сигнал. Если доступ к файлу возможен через сеть, передачу сигнала вызывают только процессы на одной машине с процессо-арендатором; доступ с любой другой машины удается в случае отсутствия аренды.

Системный вызов fcntl() используется для создания, реализации и запроса аренды файлов. Аренды можно размещать только на обычных файлах (для каналов и каталогов это невозможно), кроме того, аренды записи предоставляются только владельцу файла. Первый аргумент fcntl() — это интересующий для слежения файловый дескриптор, а второй аргумент, command, определяет, какую операцию следует выполнить.

F_SETLEASE

Аренда создается или освобождается в зависимости от значения последнего параметра, передаваемого в fcntl(); F_RDLCK создает аренду чтения, F_WRLCK — аренду записи, a F_UNLCK освобождает любую аренду, которая может существовать. Если запрашивается новая аренда, она заменяет любую существующую аренду. В случае ошибки возвращается отрицательное число; ноль или положительное число свидетельствуют об успехе операции[95].

F_GETLEASE

Возвращается тип аренды, существующей в настоящий момент для файла (F_RDLCK, F_WRLCK или F_UNLCK).

Когда в арендованном файле происходит одно из контролируемых событий, ядро передает сигнал удерживающему аренду процессу. По умолчанию передается SIGIO, но процесс может выбрать, какой сигнал передавать этому файлу, с помощью вызова fcntl(), в котором второй параметр установлен в F_SETSIG, а последний — в сигнал, который должен использоваться вместо SIGIO.

Использование F_SETSIG дает один значительный эффект. По умолчанию siginfo_t не передается обработчику при доставке SIGIO. Если используется F_SETSIG, даже когда сигналом, передаваемым в ядро, является SIGIO, a SA_SIGINFO был установлен при регистрации обработчика сигнала, файловый дескриптор, аренда которого инициировала событие, передается в обработчик сигналов одновременно с элементом siginfo_t по имени si_fd. Это позволяет применять отдельный сигнал к аренде множества файлов, в то время как si_fd сообщает сигналу, какому файлу необходимо уделить внимание[96].

Единственные два системных вызова, которые могут инициировать передачу сигнала для арендуемого файла — это open() и truncate(). Когда они вызываются процессом для арендуемого файла, они блокируются[97], и процессу-владельцу передается сигнал, open() или truncate() завершаются после удаления аренды с файла (или его закрытия процессом-владельцем, что вызывает удаление аренды). Если процесс, удерживающий аренду, не отменяет снятие в течение времени, указанного в файле /proc/sys/fs/lease-break-time, ядро прерывает аренду и позволяет завершиться запускающему системному вызову.

Ниже приведен пример применения владений файлами для уведомления о намерении другого процесса получить доступ к файлу. Список файлов берется командной строки, и на каждый файл помещается аренда записи. Когда другой процесс намеревается получить доступ к файлу (даже для чтения, поскольку использовалась блокировка записи), программа освобождает блокировку файла, позволяя другому процессу продолжать работу. Она также выводит сообщение о том, какой именно файл был освобожден.

 1: /* leases.с */

 2:

 3: #define GNU_SOURCE

 4:

 5: #include <fcntl.h>

 6: #include <signal.h>

 7: #include <stdio.h>

 8: #include <string.h>

 9: #include <unistd.h>

10:

11: const char ** fileNames;

12: int numFiles;

13:

14: void handler (int sig, siginfo_t * siginfo, void * context) {

15:  /* Когда аренда истекает, вывести сообщение и закрыть файл.

16:     Предполагается, что первый открываемый файл получит файловый

17:     дескриптор 3, следующий - 4 и так далее. */

18:

19:  write(1, "освобождение", 10);

20:  write(1, fileNames[siginfo->si_fd - 3],

21:  strlen(fileNames[siginfo->si_fd - 3]));

22:  write(1, " ", 1);

23:  fcntl(siginfo->si_fd, F_SETLEASE, F_UNLCK);

24:  close(siginfo->si_fd);

25:  numFiles--;

26: }

27:

28: int main(int argc, const char ** argv) {

29:  int fd;

30:  const char ** file;

31:  struct sigaction act;

32:

33:  if (argc < 2) {

34:   fprintf(stderr, "использование: %s <filename>+ ", argv[0]);

35:   return 1;

36:  }

37:

38:  /* Зарегистрировать обработчик сигналов. Указав SA_SIGINFO, предоставить

39:     обработчику возможность узнать, какой файловый дескриптор имеет

40:     истекшую аренду. */

41:  act.sa_sigaction = handler;

42:  act.sa_flags = SA_SIGINFO;

43:  sigemptyset(&act.sa_mask);

44:  sigaction(SIGRTMIN, &act, NULL);

45:

46:  /* Сохранить список имен файлов в глобальной переменной, чтобы

47:     обработчик сигналов мог иметь доступ к нему. */

48:  fileNames = argv + 1;

49:  numFiles = argc - 1;

50:

51:  /* Открыть файлы, установить используемые сигнал

52:     и создать аренду */

53:  for (file = fileNames; *file; file++) {

54:   if ((fd = open(* file, O_RDONLY)) < 0) {

55:    perror("open");

56:    return 1;

57:   }

58:

59:   /* Для правильного заполнения необходимо использовать F_SETSIG

60:      для структуры siginfo */

61:   if (fcntl(fd, F_SETSIG, SIGRTMIN) < 0) {

62:    perror("F_SETSIG");

63:    return 1;

64:   }

65:

66:   if (fcntl(fd, F_SETLEASE, F_WRLCK) < 0) {

67:    perror("F_SETLEASE");

68:    return 1;

69:   }

70:  }

71:

72:  /* Пока файлы остаются открытыми, ожидать поступления сигналов. */

73:  while (numFiles)

74:   pause();

75:

76:  return 0;

77: }