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

Posted

技术标签:

【中文标题】用信号量保护共享内存段不起作用【英文标题】:protect a shared memory segment with semaphore does not work 【发布时间】:2014-06-17 18:49:00 【问题描述】:

我有一个创建 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,但是输出不同。我不知道为什么这段代码不能正常工作。 有人可以帮我解决我的问题吗? 谢谢

【问题讨论】:

sema 变量是否应该在进程间共享?他们不是都有自己的副本吗? 您是否考虑过检查系统调用的错误?你知道他们中的任何一个都失败了吗? 【参考方案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);

当您对它正常工作感到满意时,您可以设置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的函数,带有一个格式字符串和一个变量参数列表,但是这个简单的版本对于这个程序来说已经足够了,而且更简单。

【讨论】:

谢谢乔纳森,但这并没有解决我的问题。程序的输出和以前一样。我还检查了函数的返回值,但没有收到任何错误。 您看到 1000 条 PID 死亡消息了吗? (您是否考虑过用 10 或 20 代替 1000 来测试您的程序?)另外请注意,与您的不同,我的 while 循环后没有分号。 是的,我尝试了 10 个进程。我编辑了我的原始帖子并进行了所有更改 是的,你是对的。我在帖子中对其进行了编辑。现在我明白了,所有 semop 都失败了,但我不知道为什么。 此时我最好的猜测是semop()errno 设置为EFBIG。然而,更重要的是,您可以通过在semget()semop() 失败后打印errno(和/或strerror(errno))来找出失败的原因。这将告诉您操作系统认为是什么问题。最有可能的是,信号量编号从 0 到 nsems-1(从零开始的索引,而不是从一开始的索引)。【参考方案2】:

我尝试了你的代码(在解决了所有孩子的加入之后),所有孩子都在 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 键来避免与其他程序或先前运行的交互。

【讨论】:

以上是关于用信号量保护共享内存段不起作用的主要内容,如果未能解决你的问题,请参考以下文章

共享内存的访问控制不起作用?

睡觉的理发师 - 共享内存队列不起作用?

子资源完整性保护在 Chrome 中不起作用

pthread读写锁不起作用吗? [关闭]

更改 NSURLCache 内存和磁盘容量不起作用

删除智能指针的内存释放不起作用[重复]