5分钟学会两年经验Linux运维都不懂的内核问题( 四 )


5分钟学会两年经验Linux运维都不懂的内核问题

文章插图
 
 
这里就解释了为什么共享匿名映射内存初始化为0了,但是我们知道用 mmap 分配的内存初始化为0,就是说 mmap 私有匿名映射也为0,那么体现在哪了?
这个在 do_mmap_pgoff 函数内部可没有体现出来,而是在缺页异常,然后分配一种特殊的初始化为0的页 。
那么这个 tmpfs 占有的内存页可以回收吗?
5分钟学会两年经验Linux运维都不懂的内核问题

文章插图
 
 
也就是说 tmpfs 文件占有的 pagecache 是不能回收的,道理也很明显,因为有文件引用这些页,就不能回收 。
4.3 共享内存
posix 共享内存其实和 mmap 共享映射是同一个道理,都是利用在 tmpfs 文件系统上新建一个文件,然后再映射到用户态,最后两个进程操作同一个物理内存,那么 System V 共享内存是否也是利用 tmpfs 文件系统了?
我们可以跟踪到下述函数
5分钟学会两年经验Linux运维都不懂的内核问题

文章插图
 
 
【5分钟学会两年经验Linux运维都不懂的内核问题】这个函数就是新建一个共享内存段,其中函数
shmem_kernel_file_setup
就是在 tmpfs 文件系统上创建一个文件,然后通过这个内存文件实现进程通信,这我就不写测试程序了,而且这也是不能回收的,因为共享内存ipc机制生命周期是随内核的,也就是说你创建共享内存之后,如果不显示删除的话,进程退出之后,共享内存还是存在的 。
之前看了一些技术博客,说到 Poxic 和 System V 两套 ipc 机制(消息队列,信号量以及共享内存)都是使用 tmpfs 文件系统,也就是说最终内存使用的都是 pagecache,但是我在源码中看出了两个共享内存是基于 tmpfs 文件系统,其他信号量和消息队列还没看出来(有待后续考究) 。
posix 消息队列的实现有点类似与 pipe 的实现,也是自己一套 mqueue 文件系统,然后在 inode 上的 i_private 上挂上关于消息队列属性 mqueue_inode_info,在这个属性上,内核2.6时,是用一个数组存储消息,而到了4.6则用红黑树了存储消息(我下载了这两个版本,具体什么时候开始用红黑树,没深究) 。
然后两个进程每次操作都是操作这个 mqueue_inode_info 中的消息数组或者红黑树,实现进程通信,和这个 mqueue_inode_info 类似的还有 tmpfs 文件系统属性shmem_inode_info 和为epoll服务的文件系统 eventloop,也有一个特殊属性struct eventpoll,这个是挂在 file 结构的 private_data 等等 。
说到这,可以小结下,进程空间中代码段,数据段,动态链接库(共享文件映射),mmap 共享匿名映射都存在于 cache 中,但是这些内存页都有被进程引用,所以是不能释放的,基于 tmpfs 的 ipc 进程间通信机制的生命周期是随内核,因此也是不能通过 drop_caches 释放 。
虽然上述提及的cache不能释放,但是后面有提到,当内存不足时,这些内存是可以 swap out 的 。
因此 drop_caches 能释放的就是当从磁盘读取文件时的缓存页以及某个进程将某个文件映射到内存之后,进程退出,这时映射文件的的缓存页如果没有被引用,也是可以被释放的 。
4.4 内存自动释放方式
当系统内存不够时,操作系统有一套自我整理内存,并尽可能的释放内存机制,如果这套机制不能释放足够多的内存,那么只能 OOM 了 。
之前在提及 OOM 时,说道 redis 因为 OOM 被杀死,如下:
5分钟学会两年经验Linux运维都不懂的内核问题

文章插图
 
 
第二句后半部分,
total-vm:186660kB, anon-rss:9388kB, file-rss:4kB
把一个进程内存使用情况,用三个属性进行了说明,即所有虚拟内存,常驻内存匿名映射页以及常驻内存文件映射页 。
其实从上述的分析,我们也可以知道一个进程其实就是文件映射和匿名映射:
  • 文件映射:代码段,数据段,动态链接库共享存储段以及用户程序的文件映射段;
  • 匿名映射:bbs段,堆,以及当 malloc 用 mmap 分配的内存,还有mmap共享内存段;
其实内核回收内存就是根据文件映射和匿名映射来进行的,在 mmzone.h 有如下定义:
5分钟学会两年经验Linux运维都不懂的内核问题

文章插图
 
 
LRU_UNEVICTABLE 即为不可驱逐页 lru,我的理解就是当调用 mlock 锁住内存,不让系统 swap out 出去的页列表 。
简单说下 linux 内核自动回收内存原理,内核有一个 kswapd 会周期性的检查内存使用情况,如果发现空闲内存定于 pages_low,则 kswapd 会对 lru_list 前四个 lru 队列进行扫描,在活跃链表中查找不活跃的页,并添加不活跃链表 。


推荐阅读