只读共享内存分段错误

Read Only shared memory segmentation fault

我正在为 linux 平台上的共享内存而苦苦挣扎。 考虑以下代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <semaphore.h>

#define SEM_NAME "mysem"

int main (int argc, char *argv[])
{
    int     fd, zero = 0;
    int     *ptr;
    sem_t   *mutex;
    pid_t   PID = getpid();

    int mmap_prot = PROT_WRITE;

    if (argc < 2)
    {
        printf(" Usage: Test [OPTION]\n\tW = Write Only\n\tR = Read Only\n");
        return 1;
    }

    if (*argv[1] == 'W')
    {
        fd = open("Test_SHM", O_RDWR | O_CREAT, -1);

        if (fd == -1)
            perror("open");

        write(fd, &zero, sizeof(int));
    }
    else
    {
        fd = open("Test_SHM", O_RDONLY| O_CREAT, -1);

        if (fd == -1)
            perror("open");

        mmap_prot = PROT_READ;
    }

    ptr = mmap(NULL, sizeof(int), mmap_prot, MAP_SHARED, fd, 0);

    close(fd);

    if (ptr == MAP_FAILED)
    {
        perror("mmap");

        return 1;
    }

    // create, initialize, and unlink semaphore
    mutex = sem_open(SEM_NAME, O_CREAT | O_EXCL, -1, 1);
    sem_unlink(SEM_NAME);

    setbuf(stdout, NULL);   /* stdout is unbuffered */

    printf("Shared Mem ready..\n");

    while(1)
    {
        sem_wait(mutex);
        printf("PID %d Count: %d\n", PID, (*ptr)++);
        sem_post(mutex);

        sleep(1);
    }

    return 0;
}

如果我为只读共享内存启动应用程序,如预期的那样,第一次 *ptr 在主循环内递增时出现分段错误。

我正在开发一个抽象 Linux 共享内存的库。

此库将部署给第三方开发人员,他们将在嵌入式目标上为我的应用程序实施一些流程。

此库将在进程之间实现 "global variables"。我想知道我是否可以避免开发 getset 函数,而只是 return 分配内存的地址。

如果权限访问错误,我想向调用者提供有关其代码错误的信息。读取终端上的分段错误和进程终止没有给用户一个好的信息。

EDIT2

@Ctx 回答后,我尝试了以下解决方案,但它在第一个分段错误中起作用。第二次触发标准段错误和程序终止。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <setjmp.h>

#define SEM_NAME "mysem"

#define TEST 1

jmp_buf env;

void segvhandler(int arg) {
    longjmp(env, 1);
}

bool Test ( int *ptr, sem_t *mutex, pid_t PID)
{
#if (TEST == 1)
    signal(SIGSEGV, segvhandler);
#elif (TEST == 2)
    sig_t segvhandler_OLD = signal(SIGSEGV, segvhandler);
#endif

    int val = setjmp(env);

    if (val != 0)
    {
        printf("Segmentation fault catched.\n");

        sem_post(mutex);

        #if (TEST == 1)
            signal(SIGSEGV, SIG_DFL);
        #elif (TEST == 2)
            signal(SIGSEGV, segvhandler_OLD);
        #endif

        return false;
    }

    sem_wait(mutex);
    printf("PID %d Count: %d\n", PID, (*ptr)++);
    sem_post(mutex);

#if (TEST == 1)
    signal(SIGSEGV, SIG_DFL);
#elif (TEST == 2)
    signal(SIGSEGV, segvhandler_OLD);
#endif

    return true;
}

int main (int argc, char *argv[])
{
    int     fd, zero = 0;
    int     *ptr;
    sem_t   *mutex;
    pid_t   PID = getpid();

    int mmap_prot = PROT_WRITE;

    if (argc < 2)
    {
        printf(" Usage: Test [OPTION]\n\tW = Write Only\n\tR = Read Only\n");
        return 1;
    }

    if (*argv[1] == 'W')
    {
        fd = open("Test_SHM", O_RDWR | O_CREAT, -1);

        if (fd == -1)
            perror("open");

        write(fd, &zero, sizeof(int));
    }
    else
    {
        fd = open("Test_SHM", O_RDONLY| O_CREAT, -1);

        if (fd == -1)
            perror("open");

        mmap_prot = PROT_READ;
    }

    ptr = mmap(NULL, sizeof(int), mmap_prot, MAP_SHARED, fd, 0);

    close(fd);

    if (ptr == MAP_FAILED)
    {
        perror("mmap");

        return 1;
    }

    // create, initialize, and unlink semaphore
    mutex = sem_open(SEM_NAME, O_CREAT | O_EXCL, -1, 1);
    sem_unlink(SEM_NAME);

    setbuf(stdout, NULL);   /* stdout is unbuffered */

    printf("Shared Mem ready..\n");

    while(1)
    {
        Test (ptr, mutex, PID);

        sleep(1);
    }

    return 0;
}

根据 mmap() man page:

   Use of a mapped region can result in these signals:

   SIGSEGV
          Attempted write into a region mapped as read-only.

如果你想在修改不起作用的情况下继续,你可以为 SIGSEGV 安装一个信号处理程序并使用 (sig)setjmp/longjmp 在定义的点继续执行:

#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

jmp_buf env;

void segvhandler(int arg) {
    siglongjmp(env, 1);
}

void somefunc(void) {
    char *ptr = NULL;
    signal(SIGSEGV, segvhandler);
    if (!sigsetjmp(env, 1)) {
        // Direct invocation, try the memory access
        *ptr++;
    }
    signal(SIGSEGV, SIG_DFL);
}

int main (void) {
    while (1) {
        somefunc();
        printf("One more iteration...\n");
    }

    exit(EXIT_SUCCESS); // Never reached
}

sigsetjmp(env, 1) 也将阻塞的信号保存在 env 中,当它的第二个参数为非零时,siglongjmp() 然后恢复这些信号。否则,信号在 longjmp() 之后仍将被阻塞,因为它不是来自信号处理程序的真实 return

请记住,您应该只在进行有问题的内存访问之前直接安装处理程序,然后再将其卸载。

使用调试器几分钟显示程序在调用 sem_wait() 时崩溃。

如果,在调用 sem_open() 之后插入:

    if( SEM_FAILED == mutex )
    {
        perror( "sem_open failed" );
        exit( EXIT_FAILURE );
    }

然后移动语句:

sem_unlink(SEM_NAME);

到声明之前:

mutex = sem_open(SEM_NAME, O_CREAT | O_EXCL, -1, 1);

那么很明显剩下的问题就在这个语句中:

printf("PID %d Count: %d\n", PID, (*ptr)++);

这会引发总线错误信号。此总线错误信号发生在第一次通过 while() 循环时。

原因很简单。

printf() 语句,最后一个参数试图读取和写入内存映射文件,但内存映射仅用于(取决于命令行参数)'PROT_READ' 允许阅读或 'PROT_WRITE' 允许写作。调用 mmap() 的参数需要包含两种功能,并且调用 open() 也需要 有模式:O_RDWR。 (open() 和 mmap() 模式需要匹配

这是 Ctx 回答后更正的代码。我还发现 THIS 这有助于理解为什么 longjmp 不是正确的信号解决方案。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <setjmp.h>

#define SEM_NAME "mysem"

#define TEST 1

jmp_buf env;

void segvhandler(int arg) {
    siglongjmp(env, 1);
}

bool Test ( int *ptr, sem_t *mutex, pid_t PID)
{
#if (TEST == 1)
    signal(SIGSEGV, segvhandler);
#elif (TEST == 2)
    sig_t segvhandler_OLD = signal(SIGSEGV, segvhandler);
#endif

    int val = sigsetjmp(env, 1);

    if (val != 0)
    {
        printf("Segmentation fault catched.\n");

        sem_post(mutex);

        #if (TEST == 1)
            signal(SIGSEGV, SIG_DFL);
        #elif (TEST == 2)
            signal(SIGSEGV, segvhandler_OLD);
        #endif

        return false;
    }

    sem_wait(mutex);
    printf("PID %d Count: %d\n", PID, (*ptr)++);
    sem_post(mutex);

#if (TEST == 1)
    signal(SIGSEGV, SIG_DFL);
#elif (TEST == 2)
    signal(SIGSEGV, segvhandler_OLD);
#endif

    return true;
}

int main (int argc, char *argv[])
{
    int     fd, zero = 0;
    int     *ptr;
    sem_t   *mutex;
    pid_t   PID = getpid();

    int mmap_prot = PROT_WRITE;

    if (argc < 2)
    {
        printf(" Usage: Test [OPTION]\n\tW = Write Only\n\tR = Read Only\n");
        return 1;
    }

    if (*argv[1] == 'W')
    {
        fd = open("Test_SHM", O_RDWR | O_CREAT, -1);

        if (fd == -1)
            perror("open");

        write(fd, &zero, sizeof(int));
    }
    else
    {
        fd = open("Test_SHM", O_RDONLY| O_CREAT, -1);

        if (fd == -1)
            perror("open");

        mmap_prot = PROT_READ;
    }

    ptr = mmap(NULL, sizeof(int), mmap_prot, MAP_SHARED, fd, 0);

    close(fd);

    if (ptr == MAP_FAILED)
    {
        perror("mmap");

        return 1;
    }

    // create, initialize, and unlink semaphore
    mutex = sem_open(SEM_NAME, O_CREAT | O_EXCL, -1, 1);
    sem_unlink(SEM_NAME);

    setbuf(stdout, NULL);   /* stdout is unbuffered */

    printf("Shared Mem ready..\n");

    while(1)
    {
        Test (ptr, mutex, PID);

        sleep(1);
    }

    return 0;
}