使用信号量保护共享内存段不起作用

时间:2014-06-17 18:49:00

标签: c semaphore shared-memory

我有一个可以创建1000个子进程的程序。每个进程都应该访问一个int变量,该变量存储在共享内存段中。为了保护int变量,我创建了一个信号量:

#define _XOPEN_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define KEY 1000
#define LOCK -1
#define UNLOCK 1

int *ptr;
int pid;
int shm_id;
int sem_id;

struct sembuf sema;

int main()
{
    if( ( sem_id = semget( KEY, 1, IPC_CREAT | 0666 ) ) < 0 )
    {
        printf( "semid error\n" );
        exit( EXIT_SUCCESS );
    }
    sema.sem_num = 1;
    sema.sem_op = 0;
    sema.sem_flg = SEM_UNDO;
    if( ( shm_id = shmget( KEY, 1, IPC_CREAT | 0666 ) ) < 0 )
    {
        printf( "ERROR\n" );
        exit( EXIT_SUCCESS );
    }
    ptr = shmat( shm_id, NULL, 0 );
    *ptr = 0;
    for( int i = 0; i < 10; ++i )
    {

        pid = fork();
        if( pid == 0 )
        {
            // critical part
            sema.sem_op = LOCK;
            if( semop( sem_id, &sema, 1 ) < 0 )
            {
                printf( "ERROR\n" );
            }
            ++( *ptr );
            sema.sem_op = UNLOCK;
            if( semop( sem_id, &sema, 1 ) < 0 )
            {
                printf( "ERROR\n" );
            }
            // end of the critical part
            exit( EXIT_SUCCESS );
        }
    }
    int return_stat;
    enum { debug = 1 };
    int corpse;

    while ( ( corpse = waitpid( ( pid_t )-1, &return_stat, 0 ) ) > 0 )
        if ( debug )
            printf( "PID %d died 0x%.4X\n", corpse, return_stat ); 
    //while( waitpid( pid, &return_stat, 0 ) == 0 );
    printf( "value   %d\n", *ptr );
    shmdt( NULL );
    semctl( sem_id, 1, IPC_RMID, 0 );
}

以下是输出示例:

PID 7288 died 0x0000
PID 7289 died 0x0000
PID 7290 died 0x0000
PID 7291 died 0x0000
PID 7292 died 0x0000
PID 7293 died 0x0000
PID 7294 died 0x0000
PID 7295 died 0x0000
PID 7296 died 0x0000
PID 7297 died 0x0000
value   9

PID 7276 died 0x0000
PID 7277 died 0x0000
PID 7278 died 0x0000
PID 7279 died 0x0000
PID 7280 died 0x0000
PID 7281 died 0x0000
PID 7282 died 0x0000
PID 7283 died 0x0000
PID 7284 died 0x0000
PID 7285 died 0x0000
value   10

每次输出应为1000,但输出会有所不同。我不知道为什么这段代码不能正常工作。 有人可以帮我解决我的问题吗? 谢谢

2 个答案:

答案 0 :(得分:1)

您的进程清理循环错误:

while( waitpid( pid, &return_stat, 0 ) == 0 );

由于waitpid()返回它报告的PID,这不是你想要的循环 - 它只等待一个PID死掉。这可能是你需要的:

enum { debug = 1 };
int corpse;

while ((corpse = waitpid((pid_t)-1. &return_stat, 0)) > 0)
{
    if (debug)
        printf("PID %d died 0x%.4X\n", corpse, return_stat);
}

如果您对“{1}}感到满意,可以设置debug = 0

。”

查看示例输出

您的问题还存在于子代码中:

    if( pid == 0 )
    {
        // critical part
        sema.sem_op = LOCK;
        if( semop( sem_id, &sema, 1 ) < 0 )
        ++( *ptr );
        sema.sem_op = UNLOCK;
        if( semop( sem_id, &sema, 1 ) < 0 )
        // end of the critical part
        exit( EXIT_SUCCESS );
    }

仅在第一个semop()失败时才递增指针;只有当第二个semop()失败时才会退出(成功)。

您必须无条件退出。如果第一个semop()成功,您应该只进行增量,如果第一个成功,您应该只执行第二个semop()。在if语句之后,您可能需要一些错误报告代码。


另一个版本

我看到的遗留问题是你的LOCK和UNLOCK值已经反转。

#define _XOPEN_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>

#define KEY    1000
#define LOCK   +1
#define UNLOCK -1

static const char *arg0 = 0;
static void err_setarg0(char *argv0)
{
    arg0 = argv0;
}

static void err_syserr(const char *msg)
{
    int errnum = errno;
    fprintf(stderr, "%s: %s", arg0, msg);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
    int *ptr;
    int pid;
    int shm_id;
    int sem_id;
    struct sembuf sema;

    err_setarg0(argv[argc-argc]);

    if ((sem_id = semget(KEY, 1, IPC_CREAT | 0666)) < 0)
        err_syserr("semget()");
    sema.sem_num = 0;
    sema.sem_op = 0;
    sema.sem_flg = SEM_UNDO;
    if ((shm_id = shmget(KEY, 1, IPC_CREAT | 0666)) < 0)
        err_syserr("shmget()");
    ptr = shmat(shm_id, NULL, 0);
    if (ptr == (int *)-1)
        err_syserr("shmat()");
    *ptr = 0;

    printf("Looping\n");
    for (int i = 0; i < 10; ++i)
    {
        pid = fork();
        if (pid < 0)
            err_syserr("fork()");
        else if (pid == 0)
        {
            // critical part
            sema.sem_op = LOCK;
            if (semop(sem_id, &sema, 1) < 0)
                err_syserr("semop() lock");
            ++(*ptr);
            sema.sem_op = UNLOCK;
            if (semop(sem_id, &sema, 1) < 0)
                err_syserr("semop() unlock");
            // end of the critical part
            exit(EXIT_SUCCESS);
        }
    }
    printf("Looped\n");

    int return_stat;
    enum { debug = 1 };
    int corpse;

    while ((corpse = waitpid((pid_t)-1, &return_stat, 0)) > 0)
    {
        if (debug)
            printf("PID %d died 0x%.4X\n", corpse, return_stat);
    }
    printf("value   %d\n", *ptr);
    if (shmdt(ptr) == -1)
        err_syserr("shmdt()");
    if (semctl(sem_id, 1, IPC_RMID, 0) == -1)
        err_syserr("semctl()");
    if (shmctl(shm_id, IPC_RMID, 0) == -1)
        err_syserr("shmctl()");
    return 0;
}

示例运行:

$ ./semop
Looping
Looped
PID 17976 died 0x0000
PID 17977 died 0x0000
PID 17978 died 0x0000
PID 17979 died 0x0000
PID 17980 died 0x0000
PID 17981 died 0x0000
PID 17982 died 0x0000
PID 17983 died 0x0000
PID 17984 died 0x0000
PID 17985 died 0x0000
value   10
$

请注意使用err_syserr()来简化错误报告。与err_setarg0()一起,它是我常规使用的更大错误报告函数包的一部分。事实上,我的普通版本是一个类似printf的函数,带有格式字符串和变量参数列表,但这个简单版本适用于该程序并且更简单。

答案 1 :(得分:0)

我尝试了你的代码(在解决了所有孩子的联接之后),并且所有的孩子都在semop -1上永远等待。这是因为信号量的初始值设置为0,但为了让一个孩子能够运行,它必须为1。

从Linux semget联机帮助页:

   The values of the semaphores in a newly created set are indeterminate.  (POSIX.1-2001  is  explicit  on  this  point.)   Although
   Linux,  like  many  other  implementations, initializes the semaphore values to 0, a portable application cannot rely on this: it
   should explicitly initialize the semaphores to the desired values.

要初始化,您可以使用:

if(semctl(sem_id, 0, SETVAL, 1) == -1)
{
    perror("semctl");
    exit(0);
}   

如果初始值为0,也可以使用semop +1来实现。

请注意,您可以避免使用IPC_PRIVATE作为sem / shm键与其他程序或之前的运行进行交互。