linux内存之swap

简介

swap是磁盘上一块存储空间。当系统内存使用超过一定值的时候,操作系统就会启动内核进程kswapd,kswapd将部分内存数据置换到swap,从而释放一部分内存出来。swap让进程可以使用超过物理内存大小的内存空间。由于swap是磁盘上的一块空间,所以其读写性能和内存差了1000~10000倍。

功能

如果没有swap的存在,那么一旦进程使用超过物理内存大小的空间,进程就会被oom机制(操作系统为了保证自己能正常运行,强制结束应用进程)强制结束。有了swap的存在,运维人员可以监控swap的使用情况,然后有计划的调整服务器内存。如果开发人员写的代码存在内存泄漏的问题,而被泄漏的内存很可能长时间不被访问,有很大概率被置换到swap,所以swap可以延缓内存泄漏带来的内存耗尽问题。

原理

操作系统将内存分为不同的zone,每个zone管理一片内存区域,/proc/zoneinfo有各个zone管理的内存块信息,每个zone的信息里面有pages free,low,high,min,pages free是zone的空闲页(4KB)数量,low,high,min是三个水位线(见下图)。当某个zone的pages free低于low,kswapd进程就会被唤醒,kswapd扫描内存并将部分内存数据置换到swap(简称为swap out),导致pages free增加;当pages free高于high,内核进程kswapd进入睡眠状态,停止swap out。被swap out到磁盘上的swap块上面的内存数据可能需要被应用程序访问,数据又会被读取到内存(简称为swap in)。通过命令vmstat可以监控到系统的swap out和swap in行为。min的值就是当某个zone的pages free低于min的时候,就会触发oom(为了避免操作系统崩溃,强制结束应用进程),大致过程如下图。

swap.png

常用命令

free -m  #查看swap大小
vmstat -w -t 1  #观察so,si列(swap out,swap in),当这两列
               #持续不为0,意味着内存不够用了,需要排查应用
               #是否存在泄漏,是否需要扩服务器内存
sar -B 1  #如果pgscank大于0,那说明kswapd在工作
         #如果pgsteal不为0,说明有内存被回收
cat /proc/zoneinfo
cat /proc/meminfo
cat /proc/[pid]/status   #VmSwap代表进程有多少内存在磁盘swap分区上

#redhat8创建swap  
lvcreate -L 8G -n swaplv2  rootvg  #创建lv
mkswap /dev/rootvg/swaplv2  #创建swap
swapon /dev/rootvg/swaplv2   #激活swap
free -g  #查看free

测试

写个程序分配内存,再写个程序监控zone的pages使用情况

//sbrk.c 
//通过系统调用sbrk获取内存
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

//操作系统按页管理内存,每个页4KB
#define PG  4096

//获取n个页,需要对获取的内存写点数据,否则
//无法实际占用物理内存
void getm(int n){
    void *ds=sbrk(n*PG);
    if(ds==(void*)-1){
        printf("Out of Memory\n");
        return 0;
    }
    void *end=ds+n*PG;
    for(;;ds+=PG){
        if(ds>=end) break;
        *(int *)(ds)=1;
    }
}

int num=0;
int main(int argc,char *argv[]){
    int def=5000; //默认每秒分配5000个页
    if(argc == 2){
        def=atoi(argv[1]);  //可以传递一个参数设置每秒分配多少页
    }
    while(1){
        getm(def);
        num+=def;
        printf("%d\n",num);
        sleep(1);
    }
    return 0;
}
#!/usr/bin/python3
#通过/proc/zoneinfo观察zone内存状态
import re
import datetime
import sys
import time


def getnamelen():
    global namelen
    namelen=0
    fname='/proc/zoneinfo'
    f=open(fname,'r')
    fcontent=f.read()    
    pattern='^Node.*'
    zonelist=re.findall(pattern,fcontent,flags=re.M)
    namelen=len(zonelist[0])
    f.close()

def pzoneinfo():
    ISOTIMEFORMAT='%m-%d %H:%M:%S'
    times=datetime.datetime.now().strftime(ISOTIMEFORMAT)
    fname='/proc/zoneinfo'
    f=open(fname,'r')
    fcontent=f.read()
    
   #通过正则表达式提取/proc/zoneinfo里面的zone name,pages free,min,high,low
    pattern='^Node.*'
    zonelist=re.findall(pattern,fcontent,flags=re.M)
    
    pattern='^        min.*?(\d+)'
    min=re.findall(pattern,fcontent,flags=re.M)
    
    pattern='^        high.*?(\d+)'
    high=re.findall(pattern,fcontent,flags=re.M)
    
    pattern='^        low.*?(\d+)'
    low=re.findall(pattern,fcontent,flags=re.M)
    
    pattern='^  pages free.*?(\d+)'
    free=re.findall(pattern,fcontent,flags=re.M)
    
    print("{:^{namelen}}    {:<10} {:<10} {:<10} {:<10} {:<10}".format("zonename","free","high","low","min","time",namelen=namelen))
    zonen=0
    for z in zonelist:
        print("{}    {:<10} {:<10} {:<10} {:<10} {}".format(z,free[zonen],high[zonen],low[zonen],min[zonen],times))
        zonen=zonen+1
    totalfree=0
    for f in free:
        totalfree+=int(f)
    print("{:^{namelen}}    {:<10}".format("total free",str(totalfree),namelen=namelen)) 


if __name__=='__main__':
    getnamelen()
    if len(sys.argv)==1:
        print(namelen)
        pzoneinfo()
    elif len(sys.argv)==2:
        ss=int(sys.argv[1])
        while(1):
            pzoneinfo()
            time.sleep(ss)

执行程序

gcc sbrk.c -o sbrk
./sbrk  10000  #根据自己测试服务内存大小调整内存获取速度
python3 watchzone.py  #开新的窗口执行
vmstat -w -t 1 #开新的窗口执行
sar -B 1  #开新的窗口执行

当vmstat -w -t 1观察到si so持续不为0就可以中断各个窗口程序了


vmstat.png
watchzone.png
  • 从vmwat.png可以观察到从11:19:43开始出现so,直接定位watchzone.png的11:19:43左右的数据
  • 从watchzone.png可以看到11:19:42之前total free是每秒减少10000,但是11:19:43的时候total free只减少了4000,猜测是kswapd回收了近6000个页(将6000和vmstat的so对比,存在一定误差,可以接受)
  • 对比free列和low列的值,发现11:19:42的free非常接近low,分配10000个页必然触发低于low

swappiness

除了少部分特殊用途的内存,剩余的内存可以分为两种用途:缓存文件,动态分配(malloc函数);swappiness的作用是调整kswapd回收内存时候是偏向回收缓存文件的内存还是动态分配的内存。文件服务器建议设置较大的swappiness,而不太依赖文件读写的建议设置较小的swappiness。

参考

redhat swappiness
kernel doc

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容