On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

5. СИНХРОНИЗАЦИЯ ПРОЦЕССОВ

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 млн. долларов? Очевидно, программист, который составил такую программу!

Как выйти из такой затруднительной ситуации? Очевидно, что, как только первый торговый агент приступил к переводу денег, файл базы данных или, что лучше, соответствующая запись в базе данных должна быть заблокирована от попыток ее считывания или тем более изменения. Только тогда, когда процесс изменения записи завершен, можно разрешать другим программам читать запись.

Еще один способ основан на использовании глобальных переменных, расположенных на файл-сервере и доступных со всех рабочих станций - так называемых семафоров.

5.1. Блокирование файлов

В этом разделе мы рассмотрим методы синхронизации работы программ, основанные на блокировании файлов.

Принцип блокирования файлов достаточно прост. Например, в 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Добавляемый файл блокируется для совместного использования

Параметр Timeout определяет период времени (в 18-х долях секунды), в течение которого файл-сервер будет ожидать, если файл нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.

Функция возвращает ноль при успешном завершении или код ошибки:

Код ошибкиЗначение
0x96Мало памяти на файл-сервере
0xFEИстек период ожидания, заданный параметром Timeout, но файл так и не удалось заблокировать
0xFFСбой при блокировании файла

Для удаления файла из группы можно использовать функцию ClearFile():

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 прерывания INT 21h:

На входе:AH= EBh;
AL= Параметр LockDirective;
BP= Параметр Timeout.;
DS:DX= Адрес буфера, в котором находится путь к добавляе-мому файлу в формате текстовой строки, закрытой двоичным нулем.
На выходе:AL Код ошибки или 0, если операция завершилась без ошибок.

Для удаления файла из списка вместо функции ClearFile() можно использовать функцию EDh прерывания INT 21h:

На входе:AH= EDh;
DS:DX= Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для удаления группы файлов и разблокирования всех файлов вместо функции ClearFileSet() можно использовать функцию CFh прерывания INT 21h:

На входе:AH= CFh;
DS:DX= Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для блокирования группы файлов вместо функции LockFileSet() можно использовать функцию CBh прерывания INT 21h:

На входе:AH= CBh;
AL= Регистр должен содержать нулевое значение;
BP= Параметр Timeout.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования файла вместо функции ReleaseFile() можно использовать функцию ECh прерывания INT 21h:

На входе:AH= ECh;
DS:DX= Адрес буфера, в котором находится путь к файлу в формате текстовой строки, закрытой двоичным нулем.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования группы файлов вместо функции ReleaseFileSet() можно использовать функцию CDh прерывания INT 21h:

На входе:AH= CDh.
На выходе: Регистры не используются.

5.1.1. Программа 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();
}


5.2. Блокирование физических записей

Если вы разрабатываете СУБД с коллективным доступом к файлам базы данных, расположенным на сервере, метод блокирования файлов может оказаться не слишком удобным. Так как разные пользователи в разные моменты времени работают с различными участками (записями) базы данных, едва ли стоит блокировать весь файл, если один из пользователей решил изменить содержимое только одной записи в базе данных. Было бы лучше заблокировать только эту запись.

Средства сетевой оболочки позволяют вам блокировать отдельные записи в разных файлах, образуя группу блокируемых записей (по аналогии с группой файлов, рассмотренной нами в предыдущем разделе). В группу могут входить записи из одного или нескольких файлов. Каждая запись идентифицируется индексом файла (файл необходимо открыть функцией 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Сбой при блокировании записи

Для удаления записи из группы можно использовать функцию ClearPhysicalRecord():

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 прерывания INT 21h:

На входе:AH= BCh;
AL= Параметр LockDirective;
BP= Параметр Timeout;
BX= Индекс файла;
CX= Старшее слово смещения записи относительно начала файла;
DX= Младшее слово смещения;
SI= Длина записи.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для удаления записи из списка вместо функции ClearPhysicalRecord() можно использовать функцию BEh прерывания INT 21h:

На входе:AH= BEh;
BX= Индекс файла;
CX= Старшее слово смещения записи относительно начала файла;
DX= Младшее слово смещения.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для удаления группы записей и разблокирования всех записей вместо функции ClearPhysicalRecordSet() можно использовать функцию C4h прерывания INT 21h:

На входе:AH= C4h.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для блокирования группы записей вместо функции LockPhysicalRecordSet() можно использовать функцию C2h прерывания INT 21h:

На входе:AH= C2h;
AL= Параметр LockDirective;
BP= Параметр Timeout.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования записи вместо функции ReleasePhysicalRecord() можно использовать функцию BDh прерывания INT 21h:

На входе:AH= BDh;
BX= Индекс файла;
CX= Старшее слово смещения записи относительно начала файла;
DX= Младшее слово смещения.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования группы записей вместо функции ReleasePhysicalRecordSet() можно использовать функцию C3h прерывания INT 21h:

На входе:AH= C3h.
На выходе:= Регистры не используются.

5.2.1. Программа 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);
}


5.2.2. Программа 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);
}


5.3. Блокирование логических записей

Для синхронизации процессов можно использовать не только физическое блокирование записей файлов, когда программа не сможет изменить их содержимое, как бы она ни старалась, но и так называемое блокирование логических записей. Логическая запись реально не существует в виде записи на диске, у нее есть только имя. Пользуясь этим именем, программа может блокировать и разблокировать логическую запись. Если запись уже была заблокирована одним процессом, второй процесс не сможет ее заблокировать до тех пор, пока первый процесс не разблокирует данную запись.

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

Логика использования логических записей проста. С каждой критичной частью, например базы данных, связывается группа имен, т. е. логических записей. Когда программа желает изменить содержимое этой критической части базы данных, она пытается заблокировать соответствующие логические записи. Если никакой другой процесс в сети не изменяет те же самые данные и уже не заблокировал данную группу логических записей, наша программа сможет заблокировать группу для себя.

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

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

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

Для создания группы логических записей используется функция LogLogicalRecord(). Удалить запись из группы можно функцией ClearLogicalRecord(). Вся группа записей удаляется функцией ClearLogicalRecordSet().

Записи можно блокировать сразу при их добавлении в группу либо можно заблокировать сразу все записи, относящиеся к группе, вызвав функцию LockLogicalRecordSet().

Для разблокирования логической записи используется функция ReleaseLogicalRecord(). Если надо разблокировать сразу все логические записи, вызывайте функцию ReleaseLogicalRecordSet().

Функция LogLogicalRecord() имеет следующий прототип:

int LogLogicalRecord(char LogicalRecordName,
    BYTE LockDirective,WORD Timeout);


Параметр LogicalRecordName задает имя логической записи, добавляемой в группу блокируемых записей. Имя может иметь длину до 100 байт и должно быть в формате текстовой строки, закрытой двоичным нулем.

Параметр LockDirective определяет, надо ли блокировать запись сразу после ее добавления в группу:

0x00Запись добавляется в группу, но не блокируется
0x01Добавляемая запись блокируется для использования заблокировавшей его программой в монопольном режиме
0x03Добавляемая запись блокируется для совместного использования

Параметр Timeout определяет период времени (в 18-x долях секунды), в течение которого файл-сервер будет ожидать, если запись нельзя заблокировать немедленно. Если для этого параметра задать нулевое значение, ожидание выполняться не будет.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибкиЗначение
0x96Мало памяти на файл-сервере
0xFEИстек период ожидания, заданный параметром Timeout, но запись так и не удалось заблокировать
0xFFСбой при блокировании записи

Для удаления записи из группы можно использовать функцию ClearLogicalRecord():

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 прерывания INT 21h:

На входе:AH= D0h;
AL= Параметр LockDirective;
BP= Параметр Timeout;
DS:DX= Адрес имени логической записи.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для удаления записи из списка вместо функции ClearLogicalRecord() можно использовать функцию D4h прерывания INT 21h:

На входе:AH= D4h;
DS:DX= Адрес имени логической записи.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для удаления группы записей и разблокирования всех записей вместо функции ClearLogicalRecordSet() можно использовать функцию D5h прерывания INT 21h:

На входе:AH= D5h.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для блокирования группы записей вместо функции LockLogicalRecordSet() можно использовать функцию D1h прерывания INT 21h:

На входе:AH= D1h;
AL= Регистр должен содержать значение 0;
BP= Параметр Timeout.
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования записи вместо функции ReleaseLogicalRecord() можно использовать функцию D2h прерывания INT 21h:

На входе:AH= D2h;
DS:DX= Адрес имени логической записи;
На выходе:AL= Код ошибки или 0, если операция завершилась без ошибок.

Для разблокирования группы записей вместо функции ReleaseLogicalRecordSet() можно использовать функцию D3h прерывания INT 21h:

На входе:AH= D3h.
На выходе: Регистры не используются.

5.3.1. Программа 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();
}


5.4. Семафоры

Последнее средство синхронизации процессов, которое мы рассмотрим в этой главе, - семафоры. О семафорах мы уже говорили в томе "Библиотеки системного программиста", посвященном защищенному режиму работы процессоров. Семафоры 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Неправильное начальное значение семафора

Для того чтобы закрыть семафор, вам необходимо использовать функцию CloseSemaphore():

int CloseSemaphore(long SemaphoreHandle);


В качестве параметра этой функции указывается индекс закрываемого семафора.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибкиЗначение
0xFFНеправильное значение индекса семафора

С помощью функции ExamineSemaphore() вы можете узнать текущее состояние семафора:

int ExamineSemaphore(long SemaphoreHandle,
      int *SemaphoreValue, WORD *OpenCount);


Для заданного первым параметра семафора функция возвращает значение семафора (параметр SemaphoreValue) и счетчик использования (параметр OpenCount).

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибкиЗначение
0xFFНеправильное значение индекса семафора

Перед использованием критического ресурса программа должна вызвать функцию WaitOnSemaphore(), уменьшающую значение семафора:

int WaitOnSemaphore(long SemaphoreHandle, WORD Timeout);


Параметр SemaphoreHandle определяет используемый семафор.

С помощью параметра Timeout определяется время, в течение которого функция ожидает доступность ресурса (в 18-х долях секунды).

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибкиЗначение
0xFEИстекло время ожидания, заданное параметром Timeout
0xFFНеправильное значение индекса семафора

Функция SignalSemaphore(), увеличивающая значение семафора, имеет следующий прототип:

int SignalSemaphore(long SemaphoreHandle);


Индекс семафора задается параметром функции.

Функция возвращает 0 при успешном завершении или код ошибки:

Код ошибкиЗначение
0x01Переполнение семафора, значение семафора стало больше 127
0xFFНеправильное значение индекса семафора

Для работы с семафорами можно использовать функцию C5h прерывания INT 21h. В зависимости от содержимого регистра AL эта функция выполняет ту или иную операцию с семафором.

Открытие семафора:

На входе: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.4.1. Программа 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);
}
Назад       Содержание       Вперёд