在多个进程之间共享 POSIX 信号量

Share POSIX semaphore among multiple processes

我需要创建两个子进程,每个子进程调用 execvp ater being forked,可执行文件在它们之间共享 POSIX 信号量。

我需要创建共享内存还是只需要实现命名信号量?

我从以下链接得到了两个答案:

  1. Do forked child processes use the same semaphore?
  2. How to share semaphores between processes using shared memory

但我对如何进行实施感到困惑。

Do I need to create a shared memory or just implement named semaphores?

这两种方法都行得通。选择一个并使用它——尽管我个人更喜欢命名信号量,因为您不必处理内存分配和设置共享内存段。在我看来,创建和使用命名信号量的界面更加友好。

使用命名信号量,在您的示例场景中,会发生以下情况:

  • 您使用 sem_open(3) 在 parent 进程中创建并初始化信号量。给它一个 well-known 名字,child 进程会知道;此名称用于在系统中查找信号量。
  • 关闭 parent 中的信号量,因为它不会使用它。
  • 分叉并执行
  • 取消信号量与 sem_unlink(3) 的链接。这必须恰好完成一次;在哪里并不重要(任何引用信号量 object 的进程都可以做到)。如果其他进程仍然打开一个信号量,则可以取消链接它:信号量只有在所有其他进程都关闭它时才会被销毁,但请记住,该名称会立即被删除,因此新进程将无法找到并打开信号量。

child 进程使用 well-known 名称调用 sem_open(3) 以查找并获取对信号量的引用。使用信号量完成进程后,您需要使用 sem_close(3).

关闭它

下面是我刚才描述的例子。一个 parent 进程创建一个命名信号量,并 forks + 执行 2 个 child 进程,每个进程找到并打开信号量,使用它在彼此之间进行同步。

它假定 parent 分叉并执行 ./sem_chld 二进制文件。请记住,信号量的名称必须以正斜杠开头,后跟一个或多个非斜杠字符(请参阅 man sem_overview)。在此示例中,信号量的名称为 /semaphore_example.

这是 parent 过程的代码:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SEM_NAME "/semaphore_example"
#define SEM_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
#define INITIAL_VALUE 1

#define CHILD_PROGRAM "./sem_chld"

int main(void) {

    /* We initialize the semaphore counter to 1 (INITIAL_VALUE) */
    sem_t *semaphore = sem_open(SEM_NAME, O_CREAT | O_EXCL, SEM_PERMS, INITIAL_VALUE);

    if (semaphore == SEM_FAILED) {
        perror("sem_open(3) error");
        exit(EXIT_FAILURE);
    }

    /* Close the semaphore as we won't be using it in the parent process */
    if (sem_close(semaphore) < 0) {
        perror("sem_close(3) failed");
        /* We ignore possible sem_unlink(3) errors here */
        sem_unlink(SEM_NAME);
        exit(EXIT_FAILURE);
    }

    pid_t pids[2];
    size_t i;

    for (i = 0; i < sizeof(pids)/sizeof(pids[0]); i++) {
        if ((pids[i] = fork()) < 0) {
            perror("fork(2) failed");
            exit(EXIT_FAILURE);
        }

        if (pids[i] == 0) {
            if (execl(CHILD_PROGRAM, CHILD_PROGRAM, NULL) < 0) {
                perror("execl(2) failed");
                exit(EXIT_FAILURE);
            }
        }
    }

    for (i = 0; i < sizeof(pids)/sizeof(pids[0]); i++)
        if (waitpid(pids[i], NULL, 0) < 0)
            perror("waitpid(2) failed");

    if (sem_unlink(SEM_NAME) < 0)
        perror("sem_unlink(3) failed");

    return 0;
}

注意sem_unlink(3)在两个children终止后被调用;虽然这不是必需的,但如果在 parent 进程取消链接信号量和两个 child 进程启动并打开信号量之前调用它,就会出现竞争条件。不过,一般来说,只要您知道所有必需的进程都已打开信号量并且没有新进程需要找到它,您就可以取消链接。

这是 sem_chld 的代码,它只是一个显示共享信号量用法的小玩具程序:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#define SEM_NAME "/semaphore_example"
#define ITERS 10

int main(void) {
    sem_t *semaphore = sem_open(SEM_NAME, O_RDWR);
    if (semaphore == SEM_FAILED) {
        perror("sem_open(3) failed");
        exit(EXIT_FAILURE);
    }

    int i;
    for (i = 0; i < ITERS; i++) {
        if (sem_wait(semaphore) < 0) {
            perror("sem_wait(3) failed on child");
            continue;
        }

        printf("PID %ld acquired semaphore\n", (long) getpid());

        if (sem_post(semaphore) < 0) {
            perror("sem_post(3) error on child");
        }

        sleep(1);
    }

    if (sem_close(semaphore) < 0)
        perror("sem_close(3) failed");

    return 0;
}

您可以通过在公共头文件中定义信号量并将其包含在每个程序的代码中来消除在两个源文件之间保持信号量名称同步的需要。

请注意,此示例中的错误处理并不理想(只是说明性的),还有很大的改进空间。它只是为了确保您在决定更改此示例以满足您的需要时不会忘记进行正确的错误处理。

共享内存方法在这里也适用,唯一的问题是父进程必须初始化共享内存。 这段代码中似乎有一个错误,而不是两个子进程,父函数将在这里分叉 3 个子进程。

里面应该有break语句
    if (pids[i] == 0) {
        if (execl(CHILD_PROGRAM, CHILD_PROGRAM, NULL) < 0) {
            perror("execl(2) failed");
            exit(EXIT_FAILURE);
        }
     break; //this is required
  }