POSIX 信号量:为啥父进程在子进程释放信号量之前获取信号量?

Posted

技术标签:

【中文标题】POSIX 信号量:为啥父进程在子进程释放信号量之前获取信号量?【英文标题】:POSIX semaphore: Why come the parent process acquire semaphore before child releases it?POSIX 信号量:为什么父进程在子进程释放信号量之前获取信号量? 【发布时间】:2013-06-24 17:19:47 【问题描述】:
#include <semaphore.h>

int main(void)

    int pfds[2];
    char buff[20];
    sem_t sem;

    sem_init(&sem, 1, 1);
    pipe(pfds);


    if (!fork()) 
        printf("Child: Waiting to acquire semaphore\n");
        sem_wait(&sem);
        printf("child acquires lock\n");
        sleep(5);
        write(pfds[1], "Hello", 6);   /* make stdout same as pfds[1] */
        close(pfds[0]); /* we don't need this */
        printf("child releases lock\n");
        sem_post(&sem);
    
    else 
        printf("Parent: Waiting to acquire semaphore\n");
        sem_wait(&sem);
        printf("Parent acquires lock\n");
        read(pfds[0],buff,6); /* we don't need this */
        printf("Parent read: %s",buff);
        printf("parent releases lock\n");
        sem_post(&sem);
    
    sem_destroy(&sem);
    return 0;

上面是我创建的一个简单管道,孩子在其中写入,父母在其中读取。我已经把一个信号量变量初始化为 1,所以当孩子写的时候,父母应该等待。 添加了故意的“睡眠”,以便查看旋转“父:等待获取信号量”的父进程。

The expected sequence should be:
Child: Waiting to acquire semaphore
child acquires lock...(delay of 5 secs here)
child releases lock

Parent: Waiting to acquire semaphore
Parent acquires lock
Parent read..bla-bla
parent releases lock

However it happens:
[root@localhost interview]# ./a.out
Child: Waiting to acquire semaphore
child acquires lock
Parent: Waiting to acquire semaphore
Parent acquires lock -> NOTE: Parent acquires lock before child releases it
child releases lock
Parent read: Hello
parent releases lock

问题是:为什么 parent 获得锁而 child 延迟并仍然持有它,但尚未释放信号量?

另外,有什么方法可以确保孩子总是首先获得信号量,因为它应该写入管道?

编辑:感谢@jxh。这是修改后的代码,效果很好(粘贴供大家参考)。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/mman.h>

int main(void)

    int pfds[2];
    char buff[20];


    sem_t *sem = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE,
                  MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    sem_init(sem, 1, 0);
    pipe(pfds);


    if (!fork()) 
        write(pfds[1], "Hello", 6);   /* make stdout same as pfds[1] */
        close(pfds[0]); /* we don't need this */
        printf("child releases semaphore\n");
        sem_post(sem);
    
    else 
        printf("Parent: Waiting to acquire semaphore\n");
        sem_wait(sem);
        printf("Parent acquires lock\n");
        read(pfds[0],buff,6); /* we don't need this */
        printf("Parent read: %s\n",buff);
        printf("parent releases lock\n");
        sem_post(sem);
    
    sem_destroy(sem);
    return 0;

【问题讨论】:

【参考方案1】:

fork() 之后,无法保证子进程在父进程之前运行,我很确定在fork() 之后继续在操作系统调度程序上执行的通常是父进程。

按照 VoidPointer 和 QWR 的建议,信号量必须位于共享内存中,并在 man page 中声明:

如果 pshared 不为零,则信号量在进程之间共享,并且应该位于共享内存区域中(请参阅shm_open(3)mmap(2)shmget(2))

您可以像这样使用mmap() 在共享内存中分配信号量:

sem_t *sem = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE,
                  MAP_SHARED|MAP_ANONYMOUS, -1, 0);
assert(sem != MAP_FAILED);

您已将信号量初始化为1,这允许对sem_wait() 的第一次调用成功。在您的代码中,父母和孩子都尝试在“同一时间”调用sem_wait(),这意味着谁先到达那里,谁就先获得它。

您可以将信号量初始化为0,而不是同时调用sem_wait(),并且只让父级调用sem_wait()。子进程不等待,完成后调用sem_post(),这会唤醒父进程。

【讨论】:

但是为什么父级在子级释放之前获得锁呢?很奇怪 @kingsmasher1:您的父母和孩子正在竞相同时获取信号量。这不是你想要的。将信号量初始化为0,这样父级将阻止等待子级。移除孩子对sem_wait() 的调用。 我会讲一件有趣的事情。你知道,同样的代码只是将信号量初始化为“0”会导致死锁。父母和孩子都打印:“等待获取..”并继续等待:-) @kingsmasher1:请仔细阅读我的评论,并注意我编辑答案中突出显示的部分。 @jxh 这个页面pubs.opengroup.org/onlinepubs/7908799/xsh/sem_init.htmlIf the pshared argument has a non-zero value, then the semaphore is shared between processes; in this case, any process that can access the semaphore sem can use sem for performing sem_wait(), sem_trywait(), sem_post(), and sem_destroy() operations. 说只有当两个进程都可以访问sem 时才在进程之间共享,这里不是这种情况。【参考方案2】:

fork 之后,父和子将在完全不同的区域包含不同的sem 变量。执行sem_wait(&amp;sem) 将在父母和孩子中引用完全不同的记忆,因此两者都在获取信号量。

如果你想在不同进程之间使用信号量,那么你应该使用shared memory,如 sem_init 的手册页 here 和令人大开眼界的页面 is here 中所述。

【讨论】:

@jxh(还有 at-me!):POSIX 页面似乎相当模糊,它可以解释为“用户必须将 sem 链接到共享区域”或“系统将分配共享区域和在 sem 中指出这一点”。看起来各种手册页不那么模棱两可了。 @torek:是的,我最初也有同样的直觉反应,但改变我想法的是“应该”在共享内存中。我想如果手册指定如何更改信号量,它会说“将”在共享内存中。【参考方案3】:

http://linux.die.net/man/7/sem_overview 进程共享信号量必须放置在共享内存区域中(例如,使用 shmget(2) 创建的 System V 共享内存段,或使用 shm_open(3) 创建的 POSIX 共享内存对象)。

【讨论】:

@torek:你确定。手册页似乎表明信号量应该已经在共享内存中,并且如果信号量未共享(在我的答案中以 cmets 表示),则发布者报告的行为就像我所期望的那样。 @torek 嗨 torek。我认为他的问题是由于共享内存。他的逻辑似乎是正确的。 我在实现中四处寻找并认为它是自动共享的,然后我测试了......它不是。所以我错了。

以上是关于POSIX 信号量:为啥父进程在子进程释放信号量之前获取信号量?的主要内容,如果未能解决你的问题,请参考以下文章

IPC之Posix信号量详解

并发控制:进程通信之信号量

并发控制:进程通信之信号量

进程与信号之僵尸进程

POSIX 信号量

Nodejs 进程信号