Подтвердить что ты не робот

Как использовать семафоры POSIX на разветвленных процессах в C?

Я хочу разветкить несколько процессов, а затем использовать семафор на них. Вот что я пробовал:

sem_init(&sem, 1, 1);   /* semaphore*, pshared, value */
.
.
.
if(pid != 0){ /* parent process */
    wait(NULL); /* wait all child processes */

    printf("\nParent: All children have exited.\n");
    .
    .
    /* cleanup semaphores */
    sem_destroy(&sem);      
    exit(0);
}
else{ /* child process */
    sem_wait(&sem);     /* P operation */
    printf("  Child(%d) is in critical section.\n",i);
    sleep(1);
    *p += i%3;  /* increment *p by 0, 1 or 2 based on i */
    printf("  Child(%d) new value of *p=%d.\n",i,*p);
    sem_post(&sem);     /* V operation */
    exit(0);
}

И результат:

child(0) forked
child(1) forked
  Child(0) is in critical section.
  Child(1) is in critical section.
child(2) forked
  Child(2) is in critical section.
child(3) forked
  Child(3) is in critical section.
child(4) forked
  Child(4) is in critical section.
  Child(0) new value of *p=0.
  Child(1) new value of *p=1.
  Child(2) new value of *p=3.
  Child(3) new value of *p=3.

  Child(4) new value of *p=4.
Parent: All children have exited.

Это явно означает, что семафор не работал, как предполагалось. Можете ли вы объяснить, как использовать семафоры на разветвленных процессах?

4b9b3361

Ответ 1

Проблема, с которой вы сталкиваетесь, заключается в неправильном понимании функции sem_init(). Когда вы прочитаете страницу руководства, вы увидите это:

Аргумент pshared указывает, будет ли этот семафор разделен между потоками процесса или между процессами.

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

Если pshared не равен нулю, то семафор разделяется между процессами и должен находиться в области общей памяти (см. Shm_open (3), mmap (2) и shmget (2)). (Так как дочерний элемент, созданный fork (2), наследует свои родительские отображения памяти, он также может обращаться к семафору.) Любой процесс, который может получить доступ к области совместно используемой памяти, может работать на семафоре, используя sem_post (3), sem_wait (3) и т.д.,

Я считаю этот подход более сложным, чем другие, поэтому я хочу призвать людей использовать sem_open() вместо sem_init().

Ниже вы можете увидеть полную программу иллюстрирует следующее:

  • Как распределить общую память и использовать общие переменные между разветвленными процессами.
  • Как инициализировать семафор в области общей памяти и используется несколькими процессами.
  • Как обработать несколько процессов и заставить родителя ждать, пока не выйдут все его дочерние процессы.
#include <stdio.h>          /* printf()                 */
#include <stdlib.h>         /* exit(), malloc(), free() */
#include <sys/types.h>      /* key_t, sem_t, pid_t      */
#include <sys/shm.h>        /* shmat(), IPC_RMID        */
#include <errno.h>          /* errno, ECHILD            */
#include <semaphore.h>      /* sem_open(), sem_destroy(), sem_wait().. */
#include <fcntl.h>          /* O_CREAT, O_EXEC          */


int main (int argc, char **argv){
    int i;                        /*      loop variables          */
    key_t shmkey;                 /*      shared memory key       */
    int shmid;                    /*      shared memory id        */
    sem_t *sem;                   /*      synch semaphore         *//*shared */
    pid_t pid;                    /*      fork pid                */
    int *p;                       /*      shared variable         *//*shared */
    unsigned int n;               /*      fork count              */
    unsigned int value;           /*      semaphore value         */

    /* initialize a shared variable in shared memory */
    shmkey = ftok ("/dev/null", 5);       /* valid directory name and a number */
    printf ("shmkey for p = %d\n", shmkey);
    shmid = shmget (shmkey, sizeof (int), 0644 | IPC_CREAT);
    if (shmid < 0){                           /* shared memory error check */
        perror ("shmget\n");
        exit (1);
    }

    p = (int *) shmat (shmid, NULL, 0);   /* attach p to shared memory */
    *p = 0;
    printf ("p=%d is allocated in shared memory.\n\n", *p);

    /********************************************************/

    printf ("How many children do you want to fork?\n");
    printf ("Fork count: ");
    scanf ("%u", &n);

    printf ("What do you want the semaphore value to be?\n");
    printf ("Semaphore value: ");
    scanf ("%u", &value);

    /* initialize semaphores for shared processes */
    sem = sem_open ("pSem", O_CREAT | O_EXCL, 0644, value); 
    /* name of semaphore is "pSem", semaphore is reached using this name */

    printf ("semaphores initialized.\n\n");


    /* fork child processes */
    for (i = 0; i < n; i++){
        pid = fork ();
        if (pid < 0) {
        /* check for error      */
            sem_unlink ("pSem");   
            sem_close(sem);  
            /* unlink prevents the semaphore existing forever */
            /* if a crash occurs during the execution         */
            printf ("Fork error.\n");
        }
        else if (pid == 0)
            break;                  /* child processes */
    }


    /******************************************************/
    /******************   PARENT PROCESS   ****************/
    /******************************************************/
    if (pid != 0){
        /* wait for all children to exit */
        while (pid = waitpid (-1, NULL, 0)){
            if (errno == ECHILD)
                break;
        }

        printf ("\nParent: All children have exited.\n");

        /* shared memory detach */
        shmdt (p);
        shmctl (shmid, IPC_RMID, 0);

        /* cleanup semaphores */
        sem_unlink ("pSem");   
        sem_close(sem);  
        /* unlink prevents the semaphore existing forever */
        /* if a crash occurs during the execution         */
        exit (0);
    }

    /******************************************************/
    /******************   CHILD PROCESS   *****************/
    /******************************************************/
    else{
        sem_wait (sem);           /* P operation */
        printf ("  Child(%d) is in critical section.\n", i);
        sleep (1);
        *p += i % 3;              /* increment *p by 0, 1 or 2 based on i */
        printf ("  Child(%d) new value of *p=%d.\n", i, *p);
        sem_post (sem);           /* V operation */
        exit (0);
    }
}

ВЫХОД

./a.out 
shmkey for p = 84214791
p=0 is allocated in shared memory.

How many children do you want to fork?
Fork count: 6 
What do you want the semaphore value to be?
Semaphore value: 2
semaphores initialized.

  Child(0) is in critical section.
  Child(1) is in critical section.
  Child(0) new value of *p=0.
  Child(1) new value of *p=1.
  Child(2) is in critical section.
  Child(3) is in critical section.
  Child(2) new value of *p=3.
  Child(3) new value of *p=3.
  Child(4) is in critical section.
  Child(5) is in critical section.
  Child(4) new value of *p=4.
  Child(5) new value of *p=6.

Parent: All children have exited.

Неплохо проверить shmkey так как когда ftok() завершается неудачей, он возвращает -1. Однако, если у вас есть несколько общих переменных и если ftok() несколько раз завершается с ошибкой, общие переменные, которые имеют shmkey со значением -1 будут находиться в той же области общей памяти, что приведет к изменению одной, влияющей на другую. Поэтому выполнение программы будет запутанным. Чтобы избежать этого, лучше проверить, возвращает ли ftok() -1 или нет (лучше проверять исходный код, а не печатать на экран, как я, хотя я хотел показать вам значения ключей в случае коллизии).

Обратите внимание на то, как семафор объявляется и инициализируется. Это отличается от того, что вы сделали в вопросе (sem_t sem против sem_t* sem). Более того, вы должны использовать их, как они появляются в этом примере. Вы не можете определить sem_t* и использовать его в sem_init().

Ответ 2

Linux минимальный анонимный sem_init + mmap MAP_ANONYMOUS

Мне нравится эта настройка, поскольку она не загрязняет глобальное пространство имен, как sem_open делает sem_open.

Единственным недостатком является то, что MAP_ANONYMOUS не POSIX, и я не знаю никакой замены: анонимная общая память? Например, shm_open принимает глобальный идентификатор, такой же как sem_open.

main.c:

#define _GNU_SOURCE
#include <assert.h>
#include <semaphore.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char **argv) {
    pid_t pid;
    typedef struct {
        sem_t sem;
        int i;
    } Semint;

    Semint *semint;
    size_t size = sizeof(Semint);
    semint = (Semint *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
    assert(semint != MAP_FAILED);
    /* 1: shared across processes
     * 0: initial value, wait locked until one post happens (making it > 0)
     */
    sem_init(&semint->sem, 1, 0);
    semint->i = 0;
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        sleep(1);
        semint->i = 1;
        msync(&semint->sem, size, MS_SYNC);
        sem_post(&semint->sem);
        exit(EXIT_SUCCESS);
    }
    if (argc == 1) {
        sem_wait(&semint->sem);
    }
    /* Was modified on the other process. */
    assert(semint->i == 1);
    wait(NULL);
    sem_destroy(&semint->sem);
    assert(munmap(semint, size) != -1);
    return EXIT_SUCCESS;
}

Обобщение:

gcc -g -std=c99 -Wall -Wextra -o main main.c -lpthread

Запустите с sem_wait:

./main

Запустить без sem_wait:

./main 1

Без этой синхронизации очень вероятно, что assert потерпит неудачу, так как ребенок спит целую секунду:

main: main.c:39: main: Assertion 'semint->i == 1' failed.

Проверено на Ubuntu 18.04. GitHub вверх по течению.