Продолжаем публикацию моей книги о программирование на языке ассемблера (GAS) в операционной системе Linux (x86-64). Сегодня говорим о взаимодействии процессов через объекты pipe.
Это последний параграф книги, которую я публиковал все это время. Это будет учебное пособие. Сейчас, в ближайшее время, предстоит вычитывать, проверять, дополнять. Потом публикация.
В будущем году планирую расширять книгу, но это будет уже другая история.
Параграф 7.7
Программирование семафоров
Синхронизация процессов одна из самых важных проблем многозадачности. Эта задача возникает, когда процессы, в частности, работают с одним и тем же ресурсом. Например с файлом. Представим, что две программы работают с одним и тем же файлом. Даже если ситуация предельно проста: одна программа пишет, а другая только читает, все равно здесь необходима синхронизация. Первая программа меняет содержимое файла, но процесс записи определенной логической порции еще не закончен, а вторая программа уже начинает читать. В результате она может получить рассогласованные данные. Значит должна быть некая блокировка, на время, когда одна программа пишет, а вторая читает.
Одним из удобных механизмов синхронизации в операционной системе Linux являются семафоры. Как работает семафор? Семафор это объект ядра. Представим себе, что после его создания к нему имеют доступ два процесса. Значение счетчика в начале равно 1. Через специальную функцию доступ получает один и значение счетчика уменьшается до 0. Второй процесс, обратившись к этой же функции будет находится в состоянии ожидания. Когда первый процесс закончит свою работу, он обратившись к той же функции (с другими параметрами) увеличивает значение значение счетчика до 1. Тогда с ресурсом начинает работу второй процесс и пока он работает ресурс будет заблокирован для первого. В результате семафор не позволит двум процессам работать одновременно с одним процессом. Фрагмент программы между декрементом и инкрементом счетчика можно назвать критической областью. Такой подход легко обобщается на больше количество процессов. При чем возможны два варианта:
1. Из n процессов только один может работать с ресурсом в данный момент времени. Тогда максимальное значение счетчика также будет равно 1.
2. Но задачу можно поставить и по другому. Например, разрешить работать одновременно m процессам. Тогда максимальное значение счетчика будетm. Каждый процесс при получении доступа к ресурсу уменьшает счетчика на 1, а когда заканчивает работу с ресурсом увеличивает счетчик на 1. Если счетчик равен нулю, то это означает, что с ресурсом работают сразу m процессов (но не больше).
Семафор обычно создается массивом, даже если в массиве только один элемента. Но массив семафоров в некоторых случаях может оказаться очень удобен. Представим себе, что у нас есть некоторое количество ресурсов, например 10 принтеров. Каждому принтеру будет соответствовать свой семафор. Разные процессы время от времени обращаются с запросом на печать. Обслуживающая система проверяет массив семафоров и находит свободный (значение счетчика 1) и отправляет задание на печать именно на него. При этом сам алгоритм выбора свободного принтера также можно обогатить равномерно распределяя нагрузку на них.
В примере, который представлен ниже (см. Листинги 73 и 74) мы рассматриваем два процесса, которые по очереди выводят пары символов из двух строк. Строки разные, чтобы было понятно, что выводит разный процесс. Кроме того они выводятся парами, чтобы было понятно, что между выводом одного и второго символа другой процесс не может выводить свой символ, а ждет, когда счетчик семафора станет равным 1.
Пояснение к программе из листинга 73.
Поскольку такую системную функцию, как fork() мы уже неоднократно использовали, остановимся на работе с самим семафором.
1. Для создания семафора используется функция semget(), имеющая номер 64. Этой же функцией можно получить доступ к уже существующему семафору, если первым параметром укажем ключ, с которым тот создавался. При создании такой ключ, а это числовое значение, может быть сгенерирован с помощью специальной функции. На этом подходе мы останавливаться не будем. Если взять первый параметр равным нулю, то система сама сгенерирует нужный ключ, отличный от ключей семафоров, которые уже существуют в системе. Вторым параметром указывается количество элементов в массиве семафоров. Если мы создаем только семафор (один элемент в массиве), то вторым параметром следует взять значение 1. Третий параметр это режим создания. Он такой же как у файлов, т.е. берется число 0666 (в восьмеричной системе счисления). Кроме того, берется константа IPC_CREAT=512. Если процесс создания семафора закончится удачно, то функция возвратит уникальный номер семафора (дескриптор), который мы и будем в дальнейшем использовать. В противном случае будет возвращено отрицательное значение.
2. Вторая функция, которую мы используем в программе semctl(). Ее номер 66. Эта функция может выполнять разные действия над семафором. В начале мы ее используем для инициализации семафора. Первым параметром является дескриптор семафора. Вторым индекс семафора в массиве (дескриптор указывает ведь на массив семафоров). Третий параметр это выполняемая команда. Эта команда SETVAL=16, которая говорит о том, что нужно инициализировать семафор. Последний параметр функции — адрес целой переменной, значение которой это начальное значение счетчика семафора. У нас это 1. Функции semctl() используется в конце программы для удаления существующего семафора. Первый параметр в этом случае также является дескриптор существующего семафора. Остальные параметры функции равны нулю. Последний параметр IPC_RMID=0.
3. Для управления счетчиком семафора используется функция semop()с номером 65. Первым параметром функции является дескриптор семафора. Второй параметр это адрес структуры, состоящей из трех 16-битовых чисел. Первое число индекс семафора в массиве. У нас это 0. Второе число — шаг изменения счетчика. -1 или 1., в зависимости от того, входит или выходит процесс из критической области. Третий параметр у нас SEM_UNDO=4096.
4. В программе создается дочерний процесс. И далее, родительский и дочерний процесс по очереди выводят по два одинаковых символа из разных строк s1 и s2. Оба одинаковых символа выводятся в рамках критической области одного процесса. Кроме того, чтобы подчеркнуть, что другой процесс именно ждет, когда первый процесс закончит выводить оба символа, между этими выводами стоит вызов функции задержки на 1 секунду. В результате процесс ожидания можно увидеть «не вооруженным глазом». В результат все символы будут выведены парами, что так же показывает работу семафором. Чтобы убедиться в этом, достаточно исключить вызов semop() в тексте программы.
Пояснение к модулю из листинга 74.
Модуль состоит из трех функций: вывод символа, задержка во времени и сообщений об ошибках. Тексты не вызывают трудностей. Заметим только, что при выводе символа используется функция fflush() которая сбрасывает буфер вывода на консоль, т.е. вызывает вывод, без задержек.
Для трансляции программы следует выполнить следующие строки
as --64 l82.s -o l82.o
gcc -c l83.c
gcc -no-pie l83.o l82.o -o l82
Результат выполнения программы (см. рисунок 1)
На сегодня все. Подписываемся на мой канал Old Programmer и ставьте "лайки". А я продолжаю заниматься книгой Ассемблер для Linux 64.
<--Глава 7. Параграф 7.6
#программирование #программисты #ассемблер #assembler #языки программирования