Межпроцессное взаимодействие — именованные и неименованные семафоры POSIX

задняя часть Операционная система Linux

Оригинальный адрес:blogof33.com/post/9/

предисловие

Взаимодействие между процессами — интересная тема в системах POSIX.

Процесс семафора POSIX является одним из трех механизмов IPC (межпроцессного взаимодействия), которые являются производными от расширения реального времени POSIX.1. Единая спецификация UNIX помещает 3 механизма (очереди сообщений, семафоры и общее хранилище) в дополнительный раздел. До SUSv4 интерфейс семафора POSIX был включен в параметр семафора. В SUSv4 эти интерфейсы были перемещены в базовую спецификацию, а очередь сообщений и интерфейсы общего хранилища остаются необязательными.

Интерфейс семафора POSIX предназначен для устранения нескольких недостатков интерфейса семафора XSI.

  • По сравнению с интерфейсом XSI интерфейс семафора POSIX обеспечивает более высокую производительность.

  • Семафоры POSIX проще в использовании: набор семафоров отсутствует, а некоторые интерфейсы созданы по образцу знакомых операций с файловой системой. Хотя это и не требуется для реализации в файловой системе, в некоторых системах это необходимо.

  • Семафоры POSIX ведут себя лучше при удалении. Напомним, что при удалении семафора XSI операции, использующие идентификатор семафора, завершатся сбоем, а для параметра errno установлено значение EIDRM. При использовании семафора POSIX операции продолжают нормально работать до тех пор, пока не будет освобождена последняя ссылка на семафор.

    ——Выдержки из «Расширенное программирование в среде UNIX (китайское 3-е издание)», страницы 465–466.

Некоторое время назад, когда я писал канал связи, я исследовал два метода связи семафоров между процессами POSIX: именованный семафор и безымянный семафор. Многие считают, что для межпроцессного взаимодействия можно использовать только именованные семафоры, а безымянные семафоры — только для многопоточного взаимодействия между отдельными процессами. Фактически, анонимные семафоры также могут использоваться для межпроцессного взаимодействия.

разница

Разница между именованными и безымянными семафорами заключается в форме создания и уничтожения, но в остальном работа одинакова.

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

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

Безымянные семафоры проще при использовании семафоров POSIX в рамках одного процесса. Именованные семафоры проще при использовании семафоров POSIX между несколькими процессами.

соединять

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

wait

weitВсего существует три функции для операции вычитания единицы из значения семафора Прототип функции выглядит следующим образом:

#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

Link with -pthread.这一句表示 gcc 编译时,要加 -pthread.返回值:若成功,返回 0 ;若出错,返回-1

Среди них функция первой функции заключается в том, что если sem меньше 0, поток блокируется на семафоре sem до тех пор, пока sem не станет больше 0; в противном случае значение семафора уменьшается на 1.

Вторая функция аналогична первой, за исключением того, что эта функция не блокирует поток, и если sem меньше 0, она возвращает ошибку напрямую (ошибка устанавливается в EAGAIN).

Третья функция такая же, как и первая.Второй параметр представляет собой время блокировки.Если sem меньше 0, будет блокировка.Параметр указывает продолжительность времени блокировки.abs_timeoutуказывает на структуру, которая определяется1970-01-01 00:00:00 +0000 (UTC)Состоит из начальных секунд и наносекунд. Структура определяется следующим образом:

struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };

Если указанное время блокировки истекло, но sem по-прежнему меньше 0, возвращается ошибка (для ошибки устанавливается значение ETIMEDOUT).

post

postЧтобы добавить единицу к значению семафора, прототип функции выглядит следующим образом:

#include <semaphore.h>

int sem_post(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1

Приложения

именованный семафор

Создайте

Создание именованного семафора можно назватьsem_openфункция, описание функции выглядит следующим образом:

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);  
sem_t *sem_open(const char *name, int oflag,	
                       mode_t mode, unsigned int value);	

Link with -pthread.返回值:若成功,返回指向信号量的指针;若出错,返回SEM_FALLED

Первая из этих функций вызывается при использовании существующего именованного семафора.flagпараметр установлен на0.

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

когда мы используемO_CREATПри пометке необходимо указать два дополнительных параметра:

modeПараметр указывает, кто может получить доступ к семафору, группе разрешений,modeЗначение такое же, как биты разрешения открытого файла, например0666выражатьВсе пользователи могут читать и писать. Поскольку значение имеет только доступ для чтения и записи, реализации часто открывают семафоры для чтения и записи.

valueУказывает начальное значение семафора в диапазоне от 0 до SEM_VALUE_MAX.

Если семафор существует, вызов второй функции игнорирует последние два аргумента (т.е.modeа такжеvalue).

освобожден

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

#include <semaphore.h>

int sem_close(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1

Если процесс завершится без вызова этой функции, ядро ​​автоматически закроет все открытые семафоры. Ни вызов этой функции, ни автоматическое отключение ядра не изменят значение семафора до освобождения.

разрушать

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

#include <semaphore.h>

int sem_unlink(const char *name);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1

sem_unlinkФункция удаляет имя семафора. Если на семафор нет открытой ссылки, семафор уничтожается, в противном случае уничтожение откладывается до закрытия последней открытой ссылки.

пример

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

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>

#include<fcntl.h>

int main(){
    int pid1,pid2;
    sem_t *resource1; 
    sem_t *resource2; 
    int Cpid1,Cpid2=-1;
    int fd[2];//0为读出段,1为写入端
    char outpipe1[100],inpipe[200],outpipe2[100];
    pipe(fd);//建立一个无名管道

    pid1 = fork();
    if(pid1<0){
        printf("error in the first fork!");
    }else if(pid1==0){//子进程1
        resource1=sem_open("name_sem1",O_CREAT,0666,0);
        Cpid1 = getpid();
        close(fd[0]);//关掉读出端
        lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe2));
        lockf(fd[1],0,0);//解锁
        sem_post(resource1);
        sem_close(resource1);
        exit(0);
   }else{
        
        pid2 = fork();
        if(pid2<0){
            printf("error in the second fork!\n");
        }else if(pid2==0){  
                resource1=sem_open("name_sem1",O_CREAT,0666,0);
                resource2=sem_open("name_sem2",O_CREAT,0666,0);
                Cpid2 = getpid();
                sem_wait(resource1);
				close(fd[0]);
                lockf(fd[1],1,0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1],0,0);//解锁
                sem_post(resource2);
                sem_close(resource1);
                sem_close(resource2);
                exit(0);
        }
        if(pid1 > 0 && pid2 >0){
                resource2=sem_open("name_sem2",O_CREAT,0666,0);
                sem_wait(resource2);
                waitpid(pid1,NULL,0);
                waitpid(pid2,NULL,0);
                close(fd[1]);//关掉写端
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);
                sem_close(resource2);
                
                exit(0);
        }
        sem_unlink("name_sem1");
        sem_unlink("name_sem2");
    }
    return 0;
}

безымянный семафор

Создайте

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

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1

psharedПараметр указывает, используется ли семафор несколькими потоками процесса или несколькими процессами.

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

еслиpsharedненулевое, то семафор будет общим для процессов, и семафор должен находиться в области разделяемой памяти.

разрушать

Если использование безымянного семафора завершено, вы можете вызватьsem_destoryФункция уничтожает семафор. Описание функции выглядит следующим образом:

#include <semaphore.h>

int sem_destroy(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1

Уведомление:

  • Уничтожение семафора, который в данный момент заблокирован другим процессом или потоком, приводит к неопределенному поведению.
  • Использование уничтоженного семафора приводит к неопределенным результатам, если вы не используетеsem_initПовторно инициализируйте семафор.
  • Безымянный семафор следует использовать до освобождения памяти, в которой он находится.sem_destroyразрушать. Невыполнение этого требования может привести к утечке ресурсов для некоторых реализаций.

пример

Пример реализации именованных семафоров с использованием безымянных семафоров:

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include<fcntl.h>

int main(){
    int pid1,pid2;
    int Cpid1,Cpid2=-1;
    int fd[2];//0为读出段,1为写入端
    char outpipe1[100],inpipe[200],outpipe2[100];
    void *shm = NULL;
    sem_t *shared;
    int shmid = shmget((key_t)(1234), sizeof(sem_t *), 0666 | IPC_CREAT);//创建一个共享内存,返回一个标识符
    if(shmid == -1){
        perror("shmat :");
        exit(0);
    }
    shm = shmat(shmid, 0, 0);//返回指向共享内存第一个字节的指针
    shared = (sem_t *)shm;
    sem_init(shared, 1, 0);//初始化共享内存信号量值为0
    pipe(fd);//建立一个无名管道

    pid1 = fork();
    if(pid1<0){
        printf("error in the first fork!");
    }else if(pid1==0){//子进程1

        Cpid1 = getpid();
        close(fd[0]);//关掉读出端
        lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe1));
        lockf(fd[1],0,0);//解锁
        sem_post(shared);

        exit(0);
   }else{

        pid2 = fork();
        if(pid2<0){
            printf("error in the second fork!\n");
        }else if(pid2==0){
                sem_wait(shared);
                Cpid2 = getpid();
				close(fd[0]);
                lockf(fd[1],1,0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1],0,0);//解锁

                exit(0);
        }
        if(pid1 > 0 && pid2 >0){

                waitpid(pid2,NULL,0);//同步,保证子进程先写父进程再读
                close(fd[1]);//关掉写端
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);

                exit(0);
        }

    }
    return 0;
}