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

2024-10-23 08:47:00
admin
原创
61
摘要:问题描述:我想分叉多个进程,然后对它们使用信号量。以下是我尝试的:sem_init(&sem, 1, 1); /* semaphore*, pshared, value */ . . . if(pid != 0){ /* parent process */ wait(NULL); ...

问题描述:

我想分叉多个进程,然后对它们使用信号量。以下是我尝试的:

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

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

输出为:

子(0)分叉
child(1) 分叉
  Child(0)处于关键部分。
  Child(1)处于关键区域。
child(2) 分叉
  Child(2)处于关键部分。
child(3) 分叉
  Child(3)处于关键部分。
child(4) 分叉
  Child(4)处于关键区域。
  Child(0) 的新值 *p=0。
  Child(1) 的新值 *p=1。
  Child(2) 的新值 *p=3。
  Child(3) 的新值 *p=3。

  Child(4) *p 的新值=4。
家长:所有孩子都已经离开了。

这显然意味着信号量没有按预期工作。您能解释一下我应该如何在分叉进程上使用信号量吗?


解决方案 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
", shmkey);
    shmid = shmget (shmkey, sizeof (int), 0644 | IPC_CREAT);
    if (shmid < 0){                           /* shared memory error check */
        perror ("shmget
");
        exit (1);
    }

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

", *p);

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

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

    printf ("What do you want the semaphore value to be?
");
    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.

");


    /* 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.
");
        }
        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 ("
Parent: All children have exited.
");

        /* 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.
", i);
        sleep (1);
        *p += i % 3;              /* increment *p by 0, 1 or 2 based on i */
        printf ("  Child(%d) new value of *p=%d.
", 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 semvs sem_t* sem)中所做的不同。此外,您应该按照本例中的方式使用它们。您不能sem_t*在 中定义和使用它sem_init()

解决方案 2:

Linux 最小匿名sem_init+mmap MAP_ANONYMOUS示例

我喜欢这种设置,因为它不会污染任何全局命名空间sem_open

唯一的缺点是它MAP_ANONYMOUS不是 POSIX,而且我不知道有什么替代品:例如,匿名共享内存? 像一样采用全局标识符。shm_open`sem_open`

主程序:

#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上游。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用