Linux上共享内存的限制是如何工作的?

9

我正在研究Linux内核对共享内存的限制。

/proc/sys/kernel/shmall

指定可分配的最大页面数。将此数字视为x,将页面大小视为p。我假设“x * p”字节是系统范围内共享内存的限制。

现在,我编写了一个小程序来创建一个共享内存段,并使用下面的方法将其附加到该共享内存段两次:

shm_id = shmget(IPC_PRIVATE, 4*sizeof(int), IPC_CREAT | 0666);

if (shm_id < 0) {
    printf("shmget error\n");
    exit(1);
}
printf("\n The shared memory created is %d",shm_id);

ptr = shmat(shm_id,NULL,0);
ptr_info = shmat(shm_id,NULL,0);

在上面的程序中,ptrptr_info是不同的。因此,共享内存在我的进程地址空间中被映射到了2个虚拟地址。
当我执行 ipcs 命令时,它看起来像这样:
...
0x00000000 1638416    sun        666        16000000   2 
...

现在谈到我问题中提到的shmall限制x * p。这个限制是适用于每个共享内存段分配的所有虚拟内存总和吗?还是应用于物理内存?
这里只有一个物理内存(共享内存),从上面的程序可以看出,当我执行2个shmat时,在进程地址空间中分配了两倍的内存。所以如果在单个共享内存段上连续执行shmat,这个限制很快就会受到影响。
2个回答

1

但我的问题不同。它涉及到“/proc/sys/kernel/shmall”指定的限制。这是否等于为所有共享内存段分配的所有虚拟地址空间的总和?对我来说,shmat已经在进程地址空间的vm中分配了内存,这一点已经很清楚了。 - Nuetrino
可能是因为它只设置了限制,实际的内存分配只会在需要分配时发生。 - peterh

1

这个限制仅适用于物理内存,也就是为所有段分配的真实共享内存,因为shmat()只是将该分配的段映射到进程地址空间中。

你可以在内核中跟踪它,在newseg()函数中检查此限制的地方只有一个(ns->shm_ctlall比较)。shmat()实现忙于处理许多事情,但完全不关心shmall限制,因此您可以将一个段映射多次(嗯,地址空间也受限,但在实践中您很少关心此限制)。

您还可以尝试使用像这样的简单程序从用户空间进行一些测试:

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

unsigned long int get_shmall() {
        FILE *f = NULL;
        char buf[512];
        unsigned long int value = 0;

        if ((f = fopen("/proc/sys/kernel/shmall", "r")) != NULL) {
                if (fgets(buf, sizeof(buf), f) != NULL)
                        value = strtoul(buf, NULL, 10); // no proper checks                                                                                                                                                             
                fclose(f); // no return value check                                                                                                                                                                                     
        }
        return value;
}

int set_shmall(unsigned long int value) {
        FILE *f = NULL;
        char buf[512];
        int retval = 0;

        if ((f = fopen("/proc/sys/kernel/shmall", "w")) != NULL) {
                if (snprintf(buf, sizeof(buf), "%lu\n", value) >= sizeof(buf) ||
                    fwrite(buf, 1, strlen(buf), f) != strlen(buf))
                        retval = -1;
                fclose(f); // fingers crossed                                                                                                                                                                                           
        } else
                retval = -1;
        return retval;
}

int main()
{
        int shm_id1 = -1, shm_id2 = -1;
        unsigned long int shmall = 0, shmused, newshmall;
        void *ptr1, *ptr2;
        struct shm_info shminf;

        if ((shmall = get_shmall()) == 0) {
                printf("can't get shmall\n");
                goto out;
        }
        printf("original shmall: %lu pages\n", shmall);
        if (shmctl(0, SHM_INFO, (struct shmid_ds *)&shminf) < 0) {
                printf("can't get SHM_INFO\n");
                goto out;
        }
        shmused = shminf.shm_tot * getpagesize();
        printf("shmused: %lu pages (%lu bytes)\n", shminf.shm_tot, shmused);
        newshmall = shminf.shm_tot + 1;
        if (set_shmall(newshmall) != 0) {
                printf("can't set shmall\n");
                goto out;
        }
        if (get_shmall() != newshmall) {
                printf("something went wrong with shmall setting\n");
                goto out;
        }
        printf("new shmall: %lu pages (%lu bytes)\n", newshmall, newshmall * getpagesize());
        printf("shmget() for %u bytes: ", (unsigned int) getpagesize());
        shm_id1 = shmget(IPC_PRIVATE, (size_t)getpagesize(), IPC_CREAT | 0666);
        if (shm_id1 < 0) {
                printf("failed: %s\n", strerror(errno));
                goto out;
        }
        printf("ok\nshmat 1: ");
        ptr1 = shmat(shm_id1, NULL, 0);
        if (ptr1 == 0) {
                printf("failed\n");
                goto out;
        }
        printf("ok\nshmat 2: ");
        ptr2 = shmat(shm_id1, NULL, 0);
        if (ptr2 == 0) {
                printf("failed\n");
                goto out;
        }
        printf("ok\n");
        if (ptr1 == ptr2) {
                printf("ptr1 and ptr2 are the same with shm_id1\n");
                goto out;
        }
        printf("shmget() for %u bytes: ", (unsigned int) getpagesize());
        shm_id2 = shmget(IPC_PRIVATE, (size_t)getpagesize(), IPC_CREAT | 0666);
        if (shm_id2 < 0)
                printf("failed: %s\n", strerror(errno));
        else
                printf("ok, although it's wrong\n");
out:
        if (shmall != 0 && set_shmall(shmall) != 0)
                printf("failed to restrore shmall\n");

        if (shm_id1 >= 0 && shmctl(shm_id1, IPC_RMID, NULL) < 0)
                printf("failed to remove shm_id1\n");

        if (shm_id2 >= 0 && shmctl(shm_id2, IPC_RMID, NULL) < 0)
                printf("failed to remove shm_id2\n");

        return 0;
}

它所做的是将shmall限制设置为当前系统使用量上方的一页,然后尝试获取页面大小的新段并映射两次(全部成功),然后尝试获取一个更多的页面大小段并未能成功(因为它写入了/proc/sys/kernel/shmall,请以超级用户身份运行程序)。
$ sudo ./a.out 
original shmall: 18446744073708503040 pages
shmused: 21053 pages (86233088 bytes)
new shmall: 21054 pages (86237184 bytes)
shmget() for 4096 bytes: ok
shmat 1: ok
shmat 2: ok
shmget() for 4096 bytes: failed: No space left on device

这是一个很好的解释。我有几个备注。这个程序需要以sudo身份运行,才能写入“/proc/sys/kernel/shmall”。另一个备注是,在程序执行后,我期望“/proc/sys/kernel/shmall”的值保持程序中设置的值,直到下一次重启。但是当我查看/proc/sys/kernel/shmall的值时,它仍然显示旧值18446744073692774399。你能解释一下为什么吗? - Nuetrino
@Nuetrino:那是因为我正在恢复原始的shmall值,这只是一个测试,所以在此之后最好有适当的系统默认值,我想。 - Roman Khimov
根据我所看到的代码,你没有完全还原它。 - Nuetrino
@Nuetrino:我认为if (shmall != 0 && set_shmall(shmall) != 0)就是关于这个的(靠近out:)。 - Roman Khimov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接