【问题标题】:How does limits on the shared memory work on LinuxLinux 上共享内存的限制是如何工作的
【发布时间】:2016-09-08 19:37:57
【问题描述】:

我正在研究 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,这个限制很快就会达到吗?

【问题讨论】:

    标签: linux linux-kernel ipc shared-memory


    【解决方案1】:

    我在 do_shmat 函数 (linux/ipc/shm.c) 中没有找到任何物理内存分配

    https://github.com/torvalds/linux/blob/5469dc270cd44c451590d40c031e6a71c1f637e8/ipc/shm.c

    所以 shmat 只消耗 vm(你的进程地址空间), shmat的主要功能是mmap

    【讨论】:

    • 但我的问题不同。它是关于“/proc/sys/kernel/shmall”指定的限制。这是否等于为所有共享内存段分配的所有虚拟地址空间的总和?我已经很清楚 shmat 在进程地址空间的 vm 中分配内存。
    • 可能是因为它只设置了限制,如果需要分配它们,就会发生实际的内存分配。
    【解决方案2】:

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

    你可以在内核中跟踪它,只有一个地方检查了这个限制——在分配新段的newseg() function中(ns-&gt;shm_ctlall比较)。 shmat() implementation 忙于很多事情,但根本不关心 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 的值进行 cat 处理时,它仍然显示旧值 18446744073692774399。你能解释一下原因吗?
    • @Nuetrino:那是因为我正在恢复原始的 shmall 值,这只是一个测试,所以我认为你最好在它之后有适当的系统默认值。
    • 不完全在代码中我可以看到你不要恢复它。
    • @Nuetrino:我认为这就是if (shmall != 0 &amp;&amp; set_shmall(shmall) != 0) 的意义所在(在out: 附近)。
    猜你喜欢
    • 2014-03-22
    • 2014-08-16
    • 1970-01-01
    • 2012-02-18
    • 1970-01-01
    • 2011-02-18
    • 1970-01-01
    • 1970-01-01
    • 2012-03-29
    相关资源
    最近更新 更多