繁体   English   中英

如何在C中的分叉进程上使用POSIX信号量?

[英]How to use POSIX semaphores on forked processes in 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.

显然,这意味着信号灯没有按预期的方式工作。 您能解释一下我应该如何在分支进程中使用信号量?

您面临的问题是对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 semsem_t* sem )。 此外,您应该使用它们在本示例中显示的形式。 您不能定义sem_t*并在sem_init()使用它。

Linux最小匿名sem_init + mmap MAP_ANONYMOUS示例

我喜欢此设置,因为它不会像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情况下sem_wait

./main 1

如果没有这种同步,则assert很可能会失败,因为孩子会睡一整秒钟:

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

在Ubuntu 18.04上测试。 GitHub上游

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM