Блокирование файлов
В этом разделе мы рассмотрим методы синхронизации работы программ, основанные на блокировании файлов.
Принцип блокирования файлов достаточно прост. Например, в MS-DOS, если запущена программа SHARE.EXE, программа, открывая файл, может указать, что этот файл будет использоваться ей монопольно. При попытке открыть этот файл еще раз другая программа получит от соответствующей функции MS-DOS код ошибки.
Для открывания файлов из программы, составленной на языке программирования C (или C++), удобно использовать функцию open():
int open(const char *path, int access [, unsigned mode]);
Для использования этой функции ваша программа должна содержать следующие две строки:
#include <fcntl.h> #include <sys\stat.h>
Функция возвращает индекс (handle) открытого файла или -1 в случае ошибки.
Параметр path указывает путь к открываемому файлу.
Параметр access определяет режим доступа к открываемому файлу. Вы можете использовать символические константы (их можно объединять при помощи логической операции ИЛИ):
Константа | Значение |
O_RDONLY | Открыть файл только для чтения |
O_WRONLY | Открыть файл только для записи |
O_RDWR | Открыть файл для чтения и записи |
O_APPEND | Добавлять записываемые данные в конец файла |
O_CREAT | Создать файл и открыть его. Если файл уже существует, ничего не происходит. Если файл не существует, он создается и открывается |
O_EXCL | Этот режим используется только вместе с режимом O_CREAT. Если файл уже существует, возвращается признак ошибки |
O_TRUNC | Открыть файл и установить для него нулевую длину |
O_BINARY | Файл открывается в двоичном режиме |
O_TEXT | Файл открывается в текстовом режиме. Для него выполняется преобразование байтов CR-LF в '\n' |
O_DENYNONE | К файлу разрешен множественный доступ со стороны нескольких программ, т. е. этот файл может быть открыт несколько раз |
O_DENYALL | Этот файл может быть открыт только один раз. Если другая программа попытается открыть файл для чтения или для записи, она получит признак ошибки |
O_DENYWRITE | Другая программа не может открыть этот файл еще раз для записи, но она может открыть его для чтения |
O_DENYREAD | Другая программа не может открыть этот файл еще раз для чтения, но она может открыть его для записи |
Необязательный параметр mode указывается только для вновь создаваемых файлов (в режиме O_CREAT). Он может принимать следующие значения:
Константа | Значение |
S_IWRITE | Разрешена запись в файл |
S_IREAD | Разрешено чтение файла |
Существует еще одна функция для открытия файлов, аналогичная функции open(), - функция sopen():
int sopen(path, access, shflag, mode);
Для использования этой функции в программу необходимо включить следующие строки:
#include <fcntl.h> #include <sys\stat.h> #include <share.h> #include <IO.H>
Параметры этой функции аналогичны параметрам функции open(). Дополнительный параметр shflag может принимать следующие значения:
Константа | Значение |
SH_COMPAT | Режим совместимости. Другие программы могут открывать файл, открытый в режиме совместимости, однако они также должны открывать его именно в режиме совместимости |
SH_DENYNONE | Другим программам разрешается открывать этот файл для записи и чтения, но не в режиме совместимости |
SH_DENYRD | Другие программы могут открывать этот файл, но только на запись |
SH_DENYRW | Другие программы могут открывать этот файл, но только на чтение |
Сетевая оболочка Novell NetWare предоставляет более удобные средства для блокирования файлов. Эти средства позволяют блокировать сразу несколько файлов, что может быть необходимо для обновления содержимого сложной базы данных.
Для того чтобы заблокировать один или несколько файлов средствами сетевой оболочки, вначале надо образовать группу, состоящую из блокируемых файлов. Для добавления файлов в группу служит функция LogFile(). Удалить файл из группы можно при помощи функции ClearFile().
После того как группа файлов создана, все эти файлы можно одновременно заблокировать при помощи функции LockFileSet(). Файлы можно блокировать и во время добавления их в группу.
Функция LogFile() имеет следующий прототип:
int LogFile(char *FileName, BYTE LockDirective,WORD Timeout);
Параметр FileName задает путь к файлу, который необходимо добавить в группу.
Параметр LockDirective определяет, надо ли блокировать файл сразу после его добавления в группу:
0x00 | Файл добавляется в группу, но не блокируется |
0x01 | Добавляемый файл блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемый файл блокируется для совместного использования |
Функция возвращает ноль при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но файл так и не удалось заблокировать |
0xFF | Сбой при блокировании файла |
int ClearFile(char *FileName);
Единственный параметр функции указывает путь к файлу, удаляемому из общего списка файлов, предназначенных для блокирования. Функция возвращает нулевое значение или значение 0xFF, если файла с указанным путем нет в списке.
Функция ClearFileSet() позволяет разблокировать все файлы группы и удалить группу:
void ClearFileSet(void);
Прототип функции LockFileSet(), используемой для блокирования группы файлов:
int LockFileSet(WORD Timeout);
Параметр Timeout используется так же, как и при вызове функции LogFile().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но файл так и не удалось заблокировать |
0xFF | Сбой при блокировании файла |
Для разблокирования отдельных файлов используйте функцию ReleaseFile():
int ReleaseFile(char *FileName);
Параметр FileName указывает путь к разблокируемому файлу. Функция возвращает нулевое значение или значение 0xFF, если файла с указанным путем нет в списке.
Если вам надо разблокировать сразу все файлы, добавленные в группу, используйте функцию ReleaseFileSet():
void ReleaseFileSet(void);
Для добавления файлов в группу вместо функции LogFile() можно использовать функцию EBh прерывания INT21h:
На входе: | AH | = | EBh; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout.; | |
DS:DX | = | Адрес буфера, в котором находится путь к добавляе-мому файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | EDh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | CFh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | CBh; |
AL | = | Регистр должен содержать нулевое значение; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | ECh; |
DS:DX | = | Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | CDh. |
На выходе: | Регистры не используются. |
Блокирование физических записей
Если вы разрабатываете СУБД с коллективным доступом к файлам базы данных, расположенным на сервере, метод блокирования файлов может оказаться не слишком удобным. Так как разные пользователи в разные моменты времени работают с различными участками (записями) базы данных, едва ли стоит блокировать весь файл, если один из пользователей решил изменить содержимое только одной записи в базе данных. Было бы лучше заблокировать только эту запись.
Средства сетевой оболочки позволяют вам блокировать отдельные записи в разных файлах, образуя группу блокируемых записей (по аналогии с группой файлов, рассмотренной нами в предыдущем разделе). В группу могут входить записи из одного или нескольких файлов. Каждая запись идентифицируется индексом файла (файл необходимо открыть функцией open() или аналогичными средствами), смещением начала записи в байтах от начала файла и размером записи в байтах.
Для создания группы физических записей используется функция LogPhysicalRecord(), аналогичная по назначению функции LogFile(), но работающая с записями. Удалить запись из группы можно функцией ClearPhysicalRecord(). Вся группа записей удаляется функцией ClearPhysicalRecordSet().
Записи можно блокировать сразу при их добавлении в группу либо позже. Вы можете заблокировать сразу все записи, относящиеся к группе, вызвав функцию LockPhysicalRecordSet().
Для разблокирования записи используется функция ReleasePhysicalRecord(). Если надо разблокировать сразу все записи, вызывайте функцию ReleasePhysicalRecordSet().
Функция LogPhysicalRecord() имеет следующий прототип:
int LogPhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength, BYTE LockDirective,WORD Timeout);
Параметр FileHandle задает индекс файла, которому принадлежит блокируемая запись.
Параметры RecordStartOffset и RecordLength задают смещение от начала файла и размер блокируемой записи в байтах.
Параметр LockDirective определяет, надо ли блокировать запись сразу после его добавления в группу:
0x00 | Запись добавляется в группу, но не блокируется |
0x01 | Добавляемая запись блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемая запись блокируется для совместного использования |
Параметр Timeout определяет период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если запись нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
int ClearPhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength);
Параметры этой функции аналогичны параметрам функции LogPhysicalRecord. Функция возвращает нулевое значение или значение 0xFF, если в списке нет указанной записи.
Функция ClearPhysicalRecordSet() позволяет разблокировать все записи группы и удалить группу
void ClearPhysicalRecordSet(void);
Прототип функции LockPhysicalRecordSet(), используемой для блокирования группы записей:
int LockPhysicalRecordSet(BYTE LockDirective, WORD Timeout);
Параметр LockDirective задает режим блокирования. Если он равен 0, записи блокируются для монопольного использования программой, заблокировавшей записи. Если параметр имеет значение 1, записи блокируются для совместного использования в режиме чтения.
Параметр Timeout используется так же, как и при вызове функции LogPhysicalRecord().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
Для разблокирования отдельных записей используйте функцию ReleasePhysicalRecord():
int ReleasePhysicalRecord(int FileHandle, long RecordStartOffset, long RecordLength);
Параметры задают индекс файла, смещение записи и ее длину.
Функция возвращает нулевое значение или значение 0xFF, если указанной записи нет в списке.
Если надо разблокировать сразу все записи, добавленные в группу, используйте функцию ReleasePhysicalRecordSet():
void ReleasePhysicalRecordSet(void);
Для добавления записей в группу вместо функции LogPhysicalRecord() можно использовать функцию BCh прерывания INT21h:
На входе: | AH | = | BCh; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout; | |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения; | |
SI | = | Длина записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | BEh; |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C4h. |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C2h; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | BDh; |
BX | = | Индекс файла; | |
CX | = | Старшее слово смещения записи относительно начала файла; | |
DX | = | Младшее слово смещения. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C3h. |
На выходе: | = | Регистры не используются. |
Блокирование логических записей
Для синхронизации процессов можно использовать не только физическое блокирование записей файлов, когда программа не сможет изменить их содержимое, как бы она ни старалась, но и так называемое блокирование логических записей. Логическая запись реально не существует в виде записи на диске, у нее есть только имя. Пользуясь этим именем, программа может блокировать и разблокировать логическую запись. Если запись уже была заблокирована одним процессом, второй процесс не сможет ее заблокировать до тех пор, пока первый процесс не разблокирует данную запись.
Средства сетевой оболочки позволяют создавать группы логических записей и блокировать их все вместе, по аналогии с группами блокируемых файлов и физических записей.
Логика использования логических записей проста. С каждой критичной частью, например базы данных, связывается группа имен, т. е. логических записей. Когда программа желает изменить содержимое этой критической части базы данных, она пытается заблокировать соответствующие логические записи. Если никакой другой процесс в сети не изменяет те же самые данные и уже не заблокировал данную группу логических записей, наша программа сможет заблокировать группу для себя.
Выполнив блокировку логических записей, программа выполняет все необходимые действия с файлами и затем разблокирует логические записи, предоставляя доступ к данным другим процессам.
Необходимо отметить, что при синхронизации процессов с помощью логических записей (а также семафоров, которые мы рассмотрим ниже) программы сами должны проверять состояние записей и правильно выполнять доступ к файлам, так как физически данные в файлах не блокируются.
Набор функций, используемый для работы с логическими записями, аналогичен набору функций для работы с физическими записями. Однако в отличие от физических записей, которые связаны с файлами и идентифицируются индексом файла, смещением и размером, логические записи идентифицируются по имени.
Для создания группы логических записей используется функция LogLogicalRecord().
Удалить запись из группы можно функцией ClearLogicalRecord(). Вся группа записей удаляется функцией ClearLogicalRecordSet().
Записи можно блокировать сразу при их добавлении в группу либо можно заблокировать сразу все записи, относящиеся к группе, вызвав функцию LockLogicalRecordSet().
Для разблокирования логической записи используется функция ReleaseLogicalRecord(). Если надо разблокировать сразу все логические записи, вызывайте функцию ReleaseLogicalRecordSet().
Функция LogLogicalRecord() имеет следующий прототип:
int LogLogicalRecord(char LogicalRecordName, BYTE LockDirective,WORD Timeout);
Параметр LogicalRecordName задает имя логической записи, добавляемой в группу блокируемых записей. Имя может иметь длину до 100 байт и должно быть в формате текстовой строки, закрытой двоичным нулем.
Параметр LockDirective определяет, надо ли блокировать запись сразу после ее добавления в группу:
0x00 | Запись добавляется в группу, но не блокируется |
0x01 | Добавляемая запись блокируется для использования заблокировавшей его программой в монопольном режиме |
0x03 | Добавляемая запись блокируется для совместного использования |
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x96 | Мало памяти на файл-сервере |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
int ClearLogicalRecord(char LogicalRecordName);
Параметр этой функции задает имя логической записи, удаляемой из группы. Функция возвращает нулевое значение или значение 0xFF, если в группе нет указанной записи.
Функция ClearLogicalRecordSet() позволяет разблокировать все записи группы и удалить группу:
void ClearLogicalRecordSet(void);
Прототип функции LockLogicalRecordSet(), используемой для блокирования группы записей:
int LockLogicalRecordSet(WORD Timeout);
Параметр Timeout используется так же, как и при вызове функции LogLogicalRecord().
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать |
0xFF | Сбой при блокировании записи |
Для разблокирования отдельных записей используйте функцию ReleaseLogicalRecord():
int ReleaseLogicalRecord(char LogicalRecordName);
Параметр задает имя записи. Функция возвращает нулевое значение или значение 0xFF, если указанной записи нет в группе.
Если надо разблокировать сразу все записи, добавленные в группу, используйте функцию ReleaseLogicalRecordSet():
void ReleaseLogicalRecordSet(void);
Для добавления записей в группу вместо функции LogLogicalRecord() можно использовать функцию D0h прерывания INT21h:
На входе: | AH | = | D0h; |
AL | = | Параметр LockDirective; | |
BP | = | Параметр Timeout; | |
DS:DX | = | Адрес имени логической записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | D4h; |
DS:DX | = | Адрес имени логической записи. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | D5h. |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | D1h; |
AL | = | Регистр должен содержать значение 0; | |
BP | = | Параметр Timeout. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
Для разблокирования записи вместо функции ReleaseLogicalRecord() можно использовать функцию D2h прерывания INT 21h:
На входе: | AH | = | D2h; |
DS:DX | = | Адрес имени логической записи; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | D3h. |
На выходе: | Регистры не используются. |
Программа FLOCK
Приведем пример программы FLOCK (листинг 21), выполняющей блокирование файлов средствами сетевой оболочки.
Вначале программа в цикле запрашивает пути к блокируемым файлам до тех пор, пока оператор вместо имени файла не введет символ "-". Каждый введенный файл добавляется в группу при помощи функции LogFile().
Затем вся группа блокируется функцией LockFileSet().
Далее программа ожидает нажатия на любую клавишу. Попробуйте просмотреть содержимое заблокированных файлов с другой рабочей станции - это у вас не получится.
После того как вы нажмете на любую клавишу, программа разблокирует группу файлов при помощи функции ReleaseFileSet() и удалит саму группу, вызывая функцию ClearFileSet(). Файлы, разумеется, не удаляются с диска, удаляется только группа путей к файлам, которая использовалась для блокирования.
// =================================================== // Листинг 21. Блокирование файлов // Файл flock\flock.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <conio.h>
#define BYTE unsigned char #define WORD unsigned int
extern "C" int GetNetWareShellVersion(char *,char *, char *); extern "C" int LogFile(char *, BYTE, WORD); extern "C" int LockFileSet(WORD); extern "C" void ReleaseFileSet(void); extern "C" void ClearFileSet(void);
void main(void) {
char MajorVersion=0; char MinorVersion=0; char Revision=0;
char FilePath[255];
int ccode;
printf("\n*FLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si
if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; }
// Создаем набор файлов, которые будут заблокированы
for(;;) { printf("\nВведите путь к файлу или '-':"); gets(FilePath);
strupr(FilePath); if(FilePath[0] == '-') break;
// Добавляем файл в набор
ccode = LogFile(FilePath, 0, 0);
if(!ccode) printf("Файл %s добавлен к списку\n", FilePath); else printf("Ошибка при добавлении %02.2X\n", ccode); }
// Блокируем набор файлов
ccode = LockFileSet(0);
if(!ccode) printf("Файлы заблокированы\n"); else printf("Ошибка при блокировании " "файлов %02.2X\n", ccode);
printf("Для разблокирования файлов нажмите любую клавишу\n"); getch();
// Разблокируем набор файлов
ReleaseFileSet();
// Удаляем набор файлов
ClearFileSet(); }
Программа LOGLOCK
Программа LOGLOCK (листинг 24) демонстрирует использование логических записей для синхронизации процессов.
Вначале программа запрашивает в цикле у оператора имена логических записей и добавляет их в набор. Цикл добавления записей завершается, когда оператор вводит символ "-". Затем программа выполняет попытку заблокировать группу записей.
Вы можете запустить эту программу одновременно на двух рабочих станциях. При попытке заблокировать уже заблокированную ранее логическую запись программа перейдет в состояние ожидания.
Перед завершением своей работы программа LOGLOCK разблокирует записи и удалит набор.
// =================================================== // Листинг 24. Блокирование логических записей // Файл loglock\loglock.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <conio.h>
#define BYTE unsigned char #define WORD unsigned int
extern "C" int GetNetWareShellVersion(char *,char *, char *); extern "C" int LogLogicalRecord(char *, BYTE, WORD); extern "C" int LockLogicalRecordSet(WORD); extern "C" void ReleaseLogicalRecordSet(void); extern "C" void ClearLogicalRecordSet(void);
void main(void) {
char MajorVersion=0; char MinorVersion=0; char Revision=0;
char LogicalRecordName[100];
int ccode;
printf("\n*LOGLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si
if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; }
// Создаем набор логических записей, которые будут заблокированы
for(;;) { printf("\nВведите имя логической записи или '-':"); gets(LogicalRecordName);
if(LogicalRecordName[0] == '-') break;
// Добавляем логическую запись в набор
ccode = LogLogicalRecord(LogicalRecordName, 0, 0);
if(!ccode) printf("Логическая запись %s добавлена к списку\n", LogicalRecordName); else printf("Ошибка при добавлении %02.2X\n", ccode); }
// Блокируем набор логических записей
ccode = LockLogicalRecordSet(0);
if(!ccode) printf("Логические записи заблокированы\n"); else printf("Ошибка при блокировании " "логических записей %02.2X\n", ccode);
printf("Для разблокирования логических записей " " нажмите любую клавишу\n"); getch();
// Разблокируем набор логических записей
ReleaseLogicalRecordSet();
// Удаляем набор логических записей
ClearLogicalRecordSet(); }
Программа PHYSLOCK
Для иллюстрации блокирования физических записей файла мы составили две программы - PHYSLOCK (листинг 22) и RECACC (листинг 23). Первая программа вводит с консоли имена файлов, смещения и размеры записей.
Все введенные файлы открываются, так как для добавления записей нужен индекс файлов. Затем записи блокируются функцией LockPhysicalRecordSet(), после чего программа переходит в состояние ожидания и находится в нем до тех пор, пока оператор не нажмет любую клавишу. В это время записи заблокированы. С помощью программы RECACC вы можете попытаться выполнить запись в заблокированные или свободные записи.
Когда оператор нажмет клавишу, программа PHYSLOCK разблокирует все записи и удалит набор записей.
// =================================================== // Листинг 22. Блокирование физических записей файлов // Файл physlock\physlock.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <conio.h> #include <fcntl.h> #include <io.h> #include <sys\stat.h> #include <share.h>
#define BYTE unsigned char #define WORD unsigned int
extern "C" int GetNetWareShellVersion(char *,char *, char *); extern "C" int LogPhysicalRecord(int, long, long, BYTE, WORD); extern "C" int LockPhysicalRecordSet(BYTE, WORD); extern "C" void ReleasePhysicalRecordSet(void); extern "C" void ClearPhysicalRecordSet(void);
void main(void) {
char MajorVersion=0; char MinorVersion=0; char Revision=0; char FilePath[255]; int FileHandle; char Buff[80]; long RecordStartOffset; long RecordLength;
int ccode;
printf("\n*PHYSLOCK* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si
if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; }
// Создаем набор записей файлов, которые будут заблокированы
for(;;) { printf("\nВведите путь к файлу или '-':"); gets(FilePath); strupr(FilePath); if(FilePath[0] == '-') break;
// Открываем файл, в котором мы будем блокировать физические записи
if ((FileHandle = open(FilePath, O_RDWR | O_BINARY | O_DENYNONE, SH_DENYNONE)) == -1) { printf("Не могу открыть файл\n"); continue; }
// Задаем начало и размер блокируемой области файла
printf("\nВведите смещение начала записи:"); gets(Buff); RecordStartOffset = atol(Buff);
printf("\nВведите размер записи:"); gets(Buff); RecordLength = atol(Buff);
// Добавляем запись в набор
ccode = LogPhysicalRecord(FileHandle, RecordStartOffset, RecordLength, 0, 0);
if(!ccode) printf("Файл %s добавлен к списку\n", FilePath); else printf("Ошибка при добавлении %02.2X\n", ccode); }
// Блокируем набор файлов
ccode = LockPhysicalRecordSet(0, 0);
if(!ccode) printf("Записи файлов заблокированы\n"); else printf("Ошибка при блокировании " "записей файлов %02.2X\n", ccode);
printf("Для разблокирования записей " "файлов нажмите любую клавишу\n"); getch();
// Разблокируем набор файлов
ReleasePhysicalRecordSet();
// Удаляем набор файлов
ClearPhysicalRecordSet();
// Закрываем файл
close(FileHandle); }
Программа RECACC
Программа RECACC (листинг 23) предназначена для работы вместе с программой PHYSLOCK. Она запрашивает с консоли путь к файлу, а также смещение области памяти, в которую затем будет записана небольшая текстовая строка. Если эта область окажется заблокированной, программа завершается сообщением об ошибке.
// =================================================== // Листинг 23. Проверка возможности получения // доступа к физическим записям файла // Файл recacc\recacc.cpp // // (C) A. Frolov, 1993 // ===================================================
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <conio.h> #include <fcntl.h> #include <io.h> #include <dos.h> #include <sys\stat.h> #include <share.h>
#define BYTE unsigned char #define WORD unsigned int
extern "C" int LogPhysicalRecord(int, long, long, BYTE, WORD); extern "C" int LockPhysicalRecordSet(BYTE, WORD); extern "C" void ReleasePhysicalRecordSet(void); extern "C" void ClearPhysicalRecordSet(void);
void main(void) {
char FilePath[255]; int FileHandle; char Buff[80]; long RecordStartOffset; char msg[] = "PATCH!!!";
int ccode; unsigned count;
printf("\n*RECACC* (C) Frolov A., 1993\n");
// Вводим имя файла и открываем его на запись и чтение
printf("\nВведите путь к файлу:"); gets(FilePath);
strupr(FilePath);
if ((FileHandle = open(FilePath, O_RDWR | O_BINARY | O_DENYNONE, SH_DENYNONE)) == -1) { printf("Не могу открыть файл\n"); } // Задаем смещение в файле, начиная с которого // в файл будет записана строка "PATCH!!!"
printf("\nВведите смещение начала записи:"); gets(Buff); RecordStartOffset = atol(Buff);
// Позиционируем на начало записи
lseek(FileHandle, RecordStartOffset, 0);
// Делаем попытку изменить содержимое записи
ccode = _dos_write(FileHandle, msg, strlen(msg), &count);
if(!ccode) printf("Запись обновлена\n"); else printf("Ошибка при обновлении " "записи в файле: %02.2X\n", ccode);
// Закрываем файл
close(FileHandle); }
Программа SEMSIGN
Программа SEMSIGN (листинг 25) демонстрирует использование семафоров.
Эта программа открывает семафор с именем SEMLOCK, определяет его состояние. Вся информация, касающаяся семафора, выводится в стандартный поток вывода. Затем с помощью функции WaitOnSemaphore() программа запрашивает доступ к критическому ресурсу. После того как оператор нажмет любую клавишу, программа вызывает функцию SignalSemaphore(), освобождающую ресурс, и закрывает семафор.
Вы можете запустить эту программу сначала на одной станции. Затем, после того как критический ресурс будет получен, запустите эту же программу на другой станции. Так как первая программа еще не освободила ресурс, вторая не сможет получить к нему доступа.
Если вы завершите работу первой программы в течение 20 секунд, вторая программа получит доступ к ресурсу, если нет - она завершится с сообщением о том, что ресурс занят.
// =================================================== // Листинг 25. Работа с семафорами // Файл semsign\semsign.cpp // // (C) A. Frolov, 1993 // =================================================== #include <stdlib.h> #include <stdio.h> #include <string.h> #include <conio.h>
#define BYTE unsigned char #define WORD unsigned int
extern "C" int GetNetWareShellVersion(char *,char *, char *); extern "C" int OpenSemaphore(char *, int, long *, WORD *); extern "C" int CloseSemaphore(long); extern "C" int ExamineSemaphore(long, int *, WORD *); extern "C" int SignalSemaphore(long); extern "C" int WaitOnSemaphore(long, WORD);
void main(void) {
char MajorVersion=0; char MinorVersion=0; char Revision=0;
int ccode; long SemaphoreHandle; WORD OpenCount; int SemaphoreValue;
printf("\n*SEMSIGN* (C) Frolov A., 1993\n");
// Проверяем наличие сетевой оболочки
asm push si GetNetWareShellVersion(&MajorVersion, &MinorVersion, &Revision); asm pop si
if(MajorVersion == 0) { printf("\nОболочка NetWare не загружена\n"); return; }
// Открываем семафор с именем SEMLOCK
ccode = OpenSemaphore("SEMLOCK", 1, &SemaphoreHandle, &OpenCount);
if(!ccode) { printf("Семафор SEMLOCK открыт\n"); printf("Handle = %ld, OpenCount = %d\n", SemaphoreHandle, OpenCount); } else { printf("Ошибка при открытии семафора " "SEMLOCK %02.2X\n", ccode); return; }
// Определяем текущее состояние семафора
ccode = ExamineSemaphore(SemaphoreHandle, &SemaphoreValue, &OpenCount);
if(!ccode) { printf("SemaphoreValue = %d\n", SemaphoreValue); } else printf(" Ошибка при получении состояния семафора " "SEMLOCK %02.2X\n", ccode);
// Запрашиваем доступ к критическому ресурсу, // ожидаем получение доступа в течение 20 секунд
printf("Запрашиваем доступ к критическому ресурсу...\n");
ccode = WaitOnSemaphore(SemaphoreHandle, 18*20);
if(!ccode) { printf("Доступ к критическому ресурсу получен\n"); } else { printf("Ресурс заблокирован, ошибка %02.2X\n", ccode); return; }
// Теперь, если мы получили доступ к ресурсу, можно выполнять с ним // необходимые действия. В нашей программе мы ничего не делаем, // просто ожидаем, пока оператор не нажмет любую клавишу
printf("Нажмите любую клавишу для освобожения ресурса\n"); getch();
// Освобождаем ресурс
ccode = SignalSemaphore(SemaphoreHandle);
if(!ccode) { printf("Ресурс освобожден\n"); } else printf("Ошибка при освобождении ресурса %02.2X\n", ccode);
// Закрываем семафор
ccode = CloseSemaphore(SemaphoreHandle);
if(!ccode) printf("Семафор SEMLOCK закрыт\n"); else printf("Ошибка при закрытии семафора " "SEMLOCK %02.2X\n", ccode); }
Семафоры
Последнее средство синхронизации процессов, которое мы рассмотрим в этой главе, - семафоры. О семафорах мы уже говорили в томе "Библиотеки системного программиста", посвященном защищенному режиму работы процессоров. Семафоры Novell NetWare - это ресурсы, расположенные физически на файл-сервере.
Программа может открыть (создать) семафор с помощью функции OpenSemaphore(), указав его имя. Функция, открывающая семафор, возвращает индекс семафора, который используется для выполнения всех операций над семафором.
С семафором помимо имени связывается некоторое число, которое может находиться в диапазоне от -127 до 127. Это число называется значением семафора.
Кроме того, для каждого семафора имеется счетчик процессов, открывших семафор. Этот счетчик увеличивает свое значение на 1, когда очередная программа открывает семафор функцией OpenSemaphore(), и уменьшает на единицу, когда одна из программ закрывает семафор функцией CloseSemaphore(). Когда счетчик принимает нулевое значение, семафор уничтожается.
Перед использованием критического ресурса, с которым связан семафор, программа должна уменьшить значение семафора, вызвав функцию WaitOnSemaphore(). Если значение семафора равно нулю или больше нуля, программа может использовать ресурс. Если же семафор имеет отрицательное значение, функция WaitOnSemaphore() ожидает семафор в течение указанного ей времени.
После завершения работы с критическим ресурсом программа должна увеличить значение семафора, вызвав функцию SignalSemaphore().
Начальное значение семафора задается при его создании и обычно равно единице.
Приведем прототип функции OpenSemaphore(), открывающей семафор:
int OpenSemaphore(char *SemaphoreName, int InitialValue, long *SemaphoreHandle, WORD *OpenCount);
Параметр SemaphoreName определяет имя открываемого семафора. Имя должно иметь длину не более 127 символов, включая закрывающий строку двоичный ноль.
Параметр InitialValue задает значение семафора, но только при первом открытии, т. е. при создании семафора.
Если семафор уже создан другим процессом, этот параметр игнорируется. В качестве начального значения вы можете использовать единицу.
Параметр SemaphoreHandle - указатель на переменную, в которую будет записан индекс открытого семафора. Этот индекс необходим для выполнения всех операций с семафором.
Параметр OpenCount - счетчик использования семафора. Когда очередной процесс открывает данный семафор, счетчик увеличивает свое значение на единицу.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Неправильная длина имени семафора |
0xFF | Неправильное начальное значение семафора |
int CloseSemaphore(long SemaphoreHandle);
В качестве параметра этой функции указывается индекс закрываемого семафора.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFF | Неправильное значение индекса семафора |
int ExamineSemaphore(long SemaphoreHandle, int *SemaphoreValue, WORD *OpenCount);
Для заданного первым параметра семафора функция возвращает значение семафора (параметр SemaphoreValue) и счетчик использования (параметр OpenCount).
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFF | Неправильное значение индекса семафора |
int WaitOnSemaphore(long SemaphoreHandle, WORD Timeout);
Параметр SemaphoreHandle определяет используемый семафор.
С помощью параметра Timeout определяется время, в течение которого функция ожидает доступность ресурса (в 18-х долях секунды).
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0xFE | Истекло время ожидания, заданное параметром Timeout |
0xFF | Неправильное значение индекса семафора |
int SignalSemaphore(long SemaphoreHandle);
Индекс семафора задается параметром функции.
Функция возвращает 0 при успешном завершении или код ошибки:
Код ошибки | Значение |
0x01 | Переполнение семафора, значение семафора стало больше 127 |
0xFF | Неправильное значение индекса семафора |
Открытие семафора:
На входе: | AH | = | C5h; |
AL | = | 00h; | |
DS:DX | = | Адрес имени семафора; | |
CL | = | Начальное значение семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C5h; |
AL | = | 01h; | |
CX,DX | = | Индекс семафора; | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок; |
CX | = | Значение семафора; | |
DL | = | Счетчик использований семафора. |
На входе: | AH | = | C5h; |
AL | = | 02h; | |
CX,DX | = | Индекс семафора; | |
BP | = | Время ожидания. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C5h; |
AL | = | 03h; | |
CX,DX | = | Индекс семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
На входе: | AH | = | C5h; |
AL | = | 04h; | |
CX,DX | = | Индекс семафора. | |
На выходе: | AL | = | Код ошибки или 0, если операция завершилась без ошибок. |
СИНХРОНИЗАЦИЯ ПРОЦЕССОВ
5.1.
5.2.
5.3.
5.4.
Локальная сеть - разновидность многопользовательской системы, в которой реализован множественный доступ к файлам, хранящимся на файл-сервере. В однопользовательской однозадачной среде, такой, как MS-DOS, в каждый данный момент времени к любому файлу может обращаться только одна программа. Если пользователь работает в среде Microsoft Windows, являющейся однопользовательской многозадачной средой, существует возможность одновременного обращения к одому и тому же файлу из нескольких работающих одновременно приложений.
В сети Novell NetWare также существует возможность одновременного доступа к файлам, хранящимся в сетевых каталогах, со стороны различных рабочих станций. Причем на этих станциях может работать многозадачная операционная система, например OS/2 или Windows, что еще больше усложняет ситуацию.
К чему может привести неправильная обработка множественного доступа к файлам?
Пусть, например, на счету фирмы лежит 2 млн. долларов, выделенные на покупку некоторого товара. Двум торговым агентам дано поручение купить товар на сумму 1,5 млн. долларов. Первый агент нашел товар и перевел деньги со счета своей фирмы на счет фирмы-владельца товара. Для того чтобы перевести деньги, торговый агент воспользовался программой, которая считывает из файла базы данных содержимое поля, отражающее сумму, выделенную на покупку товара. Затем программа вычитает из нее стоимость товара и записывает в файл новое значение.
Второй торговый агент тоже не терял времени даром и нашел свой товар почти одновременно с первым. Он приступил к переводу денег почти сразу после первого агента. Получилось так, что первый агент успел только считать старое значение соответствующего поля, но не успел записать новое (0,5 млн. долларов). Поэтому программа, запущенная вторым агентом, считала из поля базы данных значение 2 млн. долларов, несмотря на то, что первый агент уже сделал свою покупку и денег осталось меньше.
Теперь первый агент записывает в поле новое значение, и через некоторое время то же самое делает второй агент. Итак, на сумму в 2 млн. долларов успешно сделаны две покупки по 1,5 млн. долларов и еще на счету фирмы осталось 0,5 млн. долларов! Кто же будет покрывать убытки размером 1,5 млн. долларов? Очевидно, программист, который составил такую программу!
Как выйти из такой затруднительной ситуации? Очевидно, что, как только первый торговый агент приступил к переводу денег, файл базы данных или, что лучше, соответствующая запись в базе данных должна быть заблокирована от попыток ее считывания или тем более изменения. Только тогда, когда процесс изменения записи завершен, можно разрешать другим программам читать запись.
Еще один способ основан на использовании глобальных переменных, расположенных на файл-сервере и доступных со всех рабочих станций - так называемых семафоров.