32查运行内存的map文件_关于Linux共享内存的实验 [一] - 现象

03ba2054e01373fa1473817978279fb2.png

前面的文章提到,基于mmap映射可以实现Linux中的共享内存(shared memory)。传统的共享内存是System V形式的,可通过"/proc/sys/kernel/shmall"参数限制其占用的最大物理内存空间,像这样:

echo 1024 > /proc/sys/kernel/shmall

这里"shmall"的数值是以page为单位的,因为page的大小通常为4KiB,所以设置1024意味着其总大小不能超过1024*4KiB=4096KiB。

重启之后,用"free -k"命令看下("k"表示以KiB为单位输出结果):

ece52e2e02107859c96c09a733054403.png

在"free"命令的输出中,"shared"就是表示共享内存的大小,这里是7564KiB,数值超过了4096,是之前的设置没有生效吗?

并不是,因为除了System V形式的共享内存,还有POSIX形式的共享内存,而"shmall"参数只对System V形式的共享内存有效,对POSIX形式的共享内存无效。要想限制POSIX形式的共享内存,得用其他的手段。

System V共享内存由内核管理,对用户不可见,而POSIX共享内存可通过"df -h"命令查看,在df命令的输出结果中,"Filesystem"为"tmpfs"或者"devtmpfs"的就是POSIX共享内存,其中"/run", "/sys/fs/cgroups"是系统目录,而"/dev/shm"是可由用户使用的。

b6c9cc6866e983c91844f91c976f3694.png

这些POSIX共享内存是以内存文件系统的形式挂载的,如果不设置,那么挂载的默认大小为内存的一半(代码位于/mm/shmem.c)。

#ifdef CONFIG_TMPFS
static unsigned long shmem_default_max_blocks(void)
{
    return totalram_pages / 2;
}
#endif

在我的机器上,内存大小是接近8个GiB,所以其默认大小是3.9GiB。如果要限制"/dev/shm"这个挂载点的共享文件系统的大小,则可以在"/etc/fstab"配置文件中添加如下的语句:

echo 'tmpfs /dev/shm tmpfs nodev,nosuid,size=128M 0 0' >> /etc/fstab

这里"size"的设置就比较简单直观了,不像"shmall"那样还需要换算一下大小。重启之后再用"df -h /dev/shm"命令看一下,"Size"表示的大小已经变成了我们之前设定的值。

1ae091d0f8a7ff33e04f134b572f978c.png

创建

那这个限制真的能起作用吗?我们来做个实验。写如下一段代码:

#define MAP_SIZE (512*1024*1024)

int main(int argc, char *argv[])
{
    int fd;
    void* result;

    fd = shm_open("shm1", O_RDWR|O_CREAT, 0644);
    if(fd < 0){
        printf("shm_open failedn");
        exit(1);
    }

    if (ftruncate(fd, MAP_SIZE) < 0){
        printf("ftruncate failedn");
        exit(1);
    }
       
    result = mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(result == MAP_FAILED){
        printf("mapped failedn");
        exit(1);
    }

    while(1);
}

shm_open()的作用主要就是在"/dev/shm"目录下打开(创建)一个文件,因为创建文件的时候没有设置大小,所以需要用ftruncate()来指定这个文件的大小。接下来是使用mmap()来将文件对应的共享内存区域映射到进程的地址空间,这里映射的范围和文件大小一致,都是512MiB。使用while()循环是为了方便查看该测试程序运行过程中,内存使用的情况。

在执行这段测试程序之前,先来看一下现在系统的一些信息,以便之后对比:

064373b4c4490cc23abc9ba64b489d9a.png

"/proc/meminfo"中的"Shmem"的值等同于"free"命令输出中的"shared"的值,其实这篇文章就介绍过,"free"命令的结果就是根据"/proc/meminfo"中的值计算出来的。"Mapped"表示映射后被进程使用的内存,"PageTables"表示所有进程的页表占用的内存之和。为了方便一起查看,我们这里就用"Shmem"来观察共享内存的的大小变化。

编译并执行上述的测试程序,虽然mmap映射的大小(512MiB)超过了"/dev/shm"的限制大小(128MiB),但进程运行过程中并没有出现什么异常。除了"/dev/shm"目录下因为open操作多了一个文件"shm1",其他几项数据的值都没有什么大的变化。

58df232c9f8141dcb2a8cebb423ab9e1.png

这是因为啊,mmap只是完成了一次内存的映射,并没有真正使用任何物理内存,所以不会出现超出限制大小的异常,"Mapped", "Shmem"和"PageTables"的值也都不会受到影响。

那么接下来就实际使用一下这段映射的内存,看看会发生什么,很简单,只需要在mmap()加一句memset()就可以了,像这样:

memset(result, 0, MAP_SIZE/2);

我们这里初始化了256MiB的内存,再次编译执行一下:

6903075cdc4e0671dc4604c1216cdeb4.png

出现了"bus error",也就是SYSBUS错误,这里访问的内存区域(256MiB)并没有超过mmap映射文件的大小(512MiB),但是超过了我们前面对POSIX共享内存做出的限制(128MiB),说明之前使用的限制POXIS共享内存的方法是有效的。

那如何解除这种限制呢?永久性的办法当然还是和前面一样,填写"/etc/fstab"配置文件,但那毕竟需要重启才能生效,如果想立刻生效以便快速进行接下来进一步的实验,只需"remount"一下就可以了,像这样:

mount -o remount,size=2G /dev/shm

现在"/dev/shm"的允许大小就变成2GiB了。

cf8a1a7a446135d204812f73e8a77d03.png

还是刚才那个程序,再执行一下,没有出现异常了。看下现在内存使用的变化吧:

1a2795ac8dd697a7aefce05cc69e4240.png

相比起之前的数据,"df"命令输出中的"Used"项的值增大了256MiB,"/proc/meminfo"中"Mapped"和"Shmem"也分别增大了256MiB,都等于测试程序中memset()使用的大小。

"PageTables"的值增加了500多KiB,单独查看一下测试程序使用的页表项(VmPTE和VmPMD),其占用内存就是500多KiB,这更加证实了"PageTables"新增的部分就来源于正在运行的测试程序。

f42263aefec8090cc15d95ee917fb4e1.png

释放

那如何释放这段共享内存呢?因为这段共享内存是作为"/dev/shm"文件系统中的一个文件存在的,试试直接用"rm"命令删掉这个文件会怎样。

rm /dev/shm/shm1

删除很顺利,并没有因为还有进程(就是我们的测试程序)在使用这段共享内存,就不可以删除这个文件。但是,再用之前的命令走一波:

66a9af81e666dfa333f764f1f816cdd9.png

"/dev/shm"下确实没有"shm1"这个文件了,"du -h"显示的文件系统大小也降下来了,可是其他几项反应内存占用情况的数值,都几乎纹丝不动啊,而且"df"和"du"的输出结果居然是不一致的。

这个时候再用"lsof /dev/shm"命令扫一下,可以看到"shm1"这个文件被标记为了"deleted"的状态,因为lsof(list open files)是列出所有打开的文件,而"shm1"这个文件并没有被正确的close,所以还是会显示在lsof的输出结果中。

64070eaa270ad6ccf1d0385dfd12c4d7.png

看来直接粗暴地删掉代表共享内存的文件是不行的,那……更粗暴地直接杀掉进程呢?重新做一下这个实验,不删掉文件,只kill掉正在运行的测试程序,看下结果如何:

127f8c561453aa1b02a709ec3fe477c9.png

"Mapped"的值减小了256MiB,这个不难理解,因为上文说过,"Mapped"本身就是代表映射后被进程所使用的内存,现在进程都挂掉了,也就不会再使用内存了。进程消亡,进程页表自然也就被内核回收了,所以"PageTables"的值也回到了之前的状态。

可是"Used"和"Shmem"这两项的值还是巍然不动。而且这个时候用lsof检测已经看不出异常了。

1cc3a9fb44f59787a62f78edbccf2bef.png

单独删掉文件和单独杀掉进程都不行,那双管齐下呢?来看下结果吧:

a36200f78619016842be371c1cccbf9d.png

嗯,一切终于都回到了最初的状态。那为什么测试结果会是这样呢?简单地解释就是:当创建一个文件的时候,会生成一个标识文件的inode,同时还会生成一个路径(即"link"),路径是为了方便查找inode的。当使用"rm /dev/shm/shm1"命令的时候,目标文件"shm1"并没有被真正地删除,删除的只是"/dev/shm/shm1"这个路径。

如果你对这个粗略的解释不够满意,那就请看下文详细的探究吧。

参考:

  • 浅析Linux的共享内存与tmpfs文件系统
  • Do tmpfs and devtmpfs share the same memory region?
  • What is the meaning of the command lsof +L1
  • http://man7.org/training/download/posix_shm_slides.pdf

原创文章,转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值