多进程间共享POSIX信号量

16

你只需要使用一个命名信号量。在调用sem_unlink之前,该信号量将一直存在。 - Jason
你的意思是如果我在任何地方取消链接...子级或父级? - dothermitian
任何进程,它们不需要被分叉。 - Jason
2个回答

29
我需要创建共享内存还是只需实现命名信号量?
两种方法都可以。选择其中一种并使用它-尽管我个人更喜欢命名信号量,因为你不必处理内存分配和设置共享内存段。在我看来,创建和使用命名信号量的接口要友好得多。
对于命名信号量,在您的示例场景中,以下是发生的情况:
1. 在父进程中使用sem_open(3)创建和初始化信号量。给它一个众所周知的名称,子进程将了解此名称;此名称用于在系统中查找信号量。
2. 关闭父级中的信号量,因为它不会使用它。
3. Fork并执行
4. 使用sem_unlink(3)取消链接信号量。这必须仅执行一次;它不真正重要(任何具有信号量对象引用的进程都可以执行它)。如果其他进程仍然打开它,则可以取消链接信号量:仅当所有其他进程关闭它时,信号量才被销毁,但请记住,名称立即被删除,因此新进程将无法找到并打开信号量。
子进程使用众所周知的名称调用sem_open(3)以查找并获取对信号量的引用。一旦进程完成了信号量,您需要使用sem_close(3)将其关闭。
下面是我刚才描述的示例。父进程创建了一个命名信号量,并fork +执行2个子进程,每个子进程都找到并打开信号量,使用它们在彼此之间进行同步。
它假定父进程分叉并执行./sem_chld二进制文件。请记住,信号量的名称必须以斜杠开头,后跟一个或多个不是斜杠的字符(请参见man sem_overview)。在此示例中,信号量的名称为/semaphore_example。
以下是父进程的代码:
#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)在两个子进程终止后被调用;尽管这不是必须的,但如果在父进程解除信号量的链接之前调用它,将会在两个子进程启动并打开信号量之间产生竞争条件。通常情况下,当您知道所有必需的进程都已打开了信号量,并且没有新进程需要找到它时,可以立即取消链接。

这是 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;
}

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

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


这里共享内存的方法行不通,因为 OP 在子进程中调用了 execvp - pilcrow
@pilcrow 为什么不呢?这只是一个问题,需要父进程创建和初始化共享内存段(使用shm_open(3) + sem_init(3)),然后让执行的程序代码调用shm_open(3)以访问共享内存段(并因此获得对信号量的引用)。 - Filipe Gonçalves
通过“共享内存方法”,我认为OP想要避免在子进程中调用*_open(通过继承资源,这无法跨越“exec”完成)。 - pilcrow

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
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接