Книга: Основы программирования в Linux

Применение вызовов read и write при наличии блокировки

Применение вызовов read и write при наличии блокировки

Когда вы применяете блокировку участков файла, очень важно использовать для доступа к данным низкоуровневые вызовы read и write вместо высокоуровневых функций freadи fwrite. Это необходимо, поскольку функции fread и fwrite выполняют внутри библиотеки буферизацию читаемых или записываемых данных, так что при выполнений вызова fread для считывания 100 байтов из файла может быть (и на самом деле почти наверняка будет), считано более 100 байтов, и дополнительные данные помещаются во внутрибиблиотечный буфер. Если программа применит функцию fread для считывания следующих 100 байтов, она на самом деле считает данные из буфера и не разрешит низкоуровневому вызову read извлечь больше данных из файла.

Для того чтобы понять, в чем тут проблема, рассмотрим две программы, которые хотят обновить один и тот же файл. Предположим, что файл содержит 200 байтов данных, все нули. Первая программа начинает работу и устанавливает блокировку на запись для первых 100 байтов файла. Затем она применяет функцию fread для считывания этих 100 байтов. Однако, как было показано в одной из предшествующих глав, fread будет каждый раз считывать больше, до BUFSIZ байтов, поэтому она на самом деле считает в память целиком весь файл, но программе вернет только первые 100 байтов.

Затем стартует вторая программа. Она устанавливает блокировку write на вторые 100 байтов файла. Это действие завершится успешно, поскольку первая программа заблокировала только первые 100 байтов. Вторая программа записывает двойки в байты с 100-го по 199-й, закрывает файл, снимает блокировку и завершается. В это время первая программа блокирует вторые 100 байтов файла и вызывает функцию fread для их считывания. Поскольку эти данные были уже занесены библиотекой в буфер, программа увидит 100 байтов нулей, а не 100 двоек, которые на самом деле хранятся в файле на жестком диске. Подобной проблемы не возникает, если вы применяете вызовы read и write.

Приведенное описание блокировки файла может показаться сложноватым, но ее труднее описать, чем применить. Поэтому выполните упражнение 7.9.

Упражнение 7.9. Блокировка файла с помощью вызова fcntl

Давайте рассмотрим пример работы блокировки файла в программе lock3.с. Для опробования блокировки вам понадобятся две программы: одна для установки блокировки и другая для ее тестирования. Первая программа выполняет блокировку.

1. Начните с файлов include и объявлений переменных:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char *test_file = "/tmp/test_lock";
int main() {
 int file desc;
 int byte_count;
 char *byte_to_write = "A";
 struct flock region_1;
 struct flock region_2;
 int res;

2. Откройте файловый дескриптор:

 file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
 if (!file_desc) {
  fprintf(stderr, "Unable to open %s for read/writen", test_file);
  exit(EXIT_FAILURE);
 }

3. Поместите данные в файл:

 for (byte_count = 0; byte_count < 100; byte_count++) {
  (void)write(file_desc, byte_to_write, 1);
 }

4. Задайте разделяемую блокировку для участка region 1 с 10-го байта по 30-й:

 region_1.l_type = F_RDLCK;
 region_1.l_whence = SEEK_SET;
 region_1.l_start = 10;
 region_1.l_len = 20;

5. Задайте исключительную блокировку для участка region_2 с 40-го байта по 50-й:

 region_2.l_type = F_WRLCK;
 region_2.l_whence = SEEK_SET;
 region_2.l_start = 40;
 region_2.l_len = 10;

6. Теперь заблокируйте файл:

 printf("Process %d locking filen", getpid());
 res = fcntl(file_desc, F_SETLK, &region_1);
 if (res == -1) fprintf(stderr, "Failed to lock region 1n");
 res = fcntl(file_desc, F_SETLK, &region_2);
 if (res = fprintf(stderr, "Failed to lock region 2n");

7. Подождите какое-то время:

 sleep(60);
 printf ("Process %d closing filen", getpid());
 close(file_desc);
 exit(EXIT_SUCCESS);
}

Как это работает

Сначала программа создает файл, открывает его для чтения и записи и затем заполняет файл данными. Далее задаются два участка: первый с 10-го по 30-й байт для разделяемой блокировки и второй с 40-го по 50-й байт для исключительной блокировки. Затем программа выполняет вызов fcntl для установки блокировок на два участка файла и ждет в течение минуты, прежде чем закрыть файл и завершить работу.

На рис. 7.1 показан этот сценарий с блокировками в тот момент, когда программа переходит к ожиданию.


Рис. 7.1

Сама по себе эта программа не очень полезна. Вам нужна вторая программа lock4.c для тестирования блокировок (упражнение 7.10).

Упражнение 7.10. Тестирование блокировок файла

В этом примере вы напишете программу, проверяющую блокировки разных типов, установленные для различных участков файла.

1. Как обычно, начнем с заголовочных файлов и объявлений:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
const char *test_file = "/tmp/test_lock";
#define SIZE_TO_TRY 5
void show_lock_info(struct flock *to_show);
int main() {
 int file_desc;
 int res;
 struct flock region_to_test;
 int start_byte;

2. Откройте дескриптор файла:

 file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
 if (!file_desc) {
  fprintf(stderr, "Unable to open %s for read/write", test_file);
  exit(EXIT_FAILURE);
 }
 for (start_byte = 0; start_byte < 99; start_byte += SIZE_TO_TRY) {

3. Задайте участок файла, который хотите проверить:

  region_to_test.l_type = F_WRLCK;
  region_to_test.l_whence = SEEK_SET;
  region_to_test.lstart = start_byte;
  region_to_test.l_len = SIZE_TO_TRY;
  region_to_test.l_pid = -1;
  printf("Testing F_WRLCK on region from %d to %dn", start_byte, start_byte + SIZE_TO_TRY);

4. Теперь проверьте блокировку файла:

  res = fcntl(file_desc, F_GETLK, &region_to_test);
  if (res == -1) {
   fprintf(stderr, "F_GETLK failedn");
   exit(EXIT_FAILURE);
  }
  if (region_to_test.l_pid != -1) {
   printf("Lock would fail. F_GETLK returned:n");
   showlockinfo(&region_to_test);
  } else {
   printf("F_WRLCK - Lock would succeedn");
  }

5. Далее повторите тест с разделяемой блокировкой (на чтение). Снова задайте участок файла, который хотите проверить:

  region_to_test.l_type = F_RDLCK;
  region_to_test.l_whence = SEEK_SET;
  region_to_test.l_start = start_byte;
  region_to_test.l_len = SIZE_TO_TRY;
  region_to_test.l_pid = -1;
  printf("Testing F_RDLCK on region from %d to %dn", start_byte, start_byte + SIZE_TO_TRY);

6. Еще раз проверьте блокировку файла:

  res = fcntl(file_desc, F_GETLK, &region_to_test);
  if (res == -1) {
   fprintf(stderr, "F_GETLK failedn");
   exit(EXIT_FAILURE);
  }
  if (region_to_test.l_pid != -1) {
   printf("Lock would fail. F_GETLK returned:n");
   show_lock_info(&region_to_test);
  } else {
   printf("F_RDLCK — Lock would succeedn");
  }
 }
 close(file_desc);
 exit(EXIT_SUCCESS);
}
void show_lock_info(struct flock *to_show) {
 printf("tl_type %d, ", to_show->l_type);
 printf("l_whence %d, ", to_show->l_whence);
 printf("l_start %d, (int)to_show->l_start);
 printf("l_len %d, ", (int)to_show->l_len);
 printf("l_pid %dn", to_show->l_pid);
}

Для проверки блокировки сначала запустите программу lock3, затем выполните программу lock4, чтобы протестировать заблокированный файл. Сделайте это, запустив программу lock3 в фоновом режиме с помощью следующей команды:

$ ./lock3 &
$ process 1534 locking file

На экране появится приглашение для ввода команд, поскольку lock3 выполняется в фоновом режиме. Далее сразу же запустите программу lock4 с помощью следующей команды:

$ ./lock4

Вы получите вывод, приведенный далее с некоторыми пропусками для краткости:

Testing F_WRLCK on region from 0 to 5
F_WRLCK — Lock would succeed
Testing F_RDLCK on region from 0 to 5
F_RDLCK - Lock would succeed
...
Testing F_WRLCK on region from 10 to 15
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLCK on region from 10 to 15
F_RDLCK — Lock would succeed
Testing F_WRLCK on region from 15 to 20
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLCK on region from 15 to 20
F_RDLCK — Lock would succeed
...
Testing F_WRLCK on region from 25 to 30
Lock would fail. F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLCK on region from 25 to 30
F_RDLCK — Lock would succeed
...
Testing F_WRLCK on region from 40 to 45
Lock would fail. F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
Testing F_RDLCK on region from 40 to 45
Lock would fail. F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
...
Testing F_RDLCK on region from 95 to 100
F_RDLCK - Lock would succeed

Как это работает

Для каждой группы из пяти байтов в файле программа lock4 задает структуру участка файла для тестирования блокировок, которую она потом применяет для определения того, может ли этот участок быть заблокирован для чтения или записи. Возвращаемая информация показывает байты, относящиеся к участку файла, смещение от нулевого байта, которое могло бы вызвать аварийное завершение запроса на блокировку. Поскольку поле l_pid возвращаемой структуры содержит идентификатор программы, владеющей в данный момент заблокированным файлом, программа задает ему значение -1 (некорректное значение) и затем проверяет, изменилось ли оно после завершения вызова fcntl. Если участок в данный момент не заблокирован, поле l_pid не изменится.

Для того чтобы понять вывод, следует заглянуть в заголовочный файл fcntl.h (обычно /usr/include/fcntl.h) и увидеть, что поле l_type, равное 1, вытекает из определения F_WRLCK как 1, а равное 0 из определения F_RDLCK как 0. Таким образом, поле l_type, равное 1, говорит о том, что блокировка не будет установлена, поскольку существует блокировка на запись, а поле l_type, равное 0, свидетельствует о существовании блокировки на чтение. Для тех участков файла, которые не заблокировала программа lock3, могут быть установлены и разделяемая, и исключительная блокировки.

Для байтов с 10-го по 30-й возможна установка разделяемой блокировки, поскольку блокировка, установленная программой lock3, не исключительная, а разделяемая. Для участка с 40-го по 50-й байт нельзя установить оба типа блокировки, поскольку lock3 задала исключительную (F_WRLCK) блокировку для этого участка.

После завершения программы lock4 необходимо немного подождать, чтобы программа lock3 завершила вызов sleep и закончила выполнение.

Оглавление книги


Генерация: 1.160. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз