前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >内存lru file比cache大的一种场景介绍

内存lru file比cache大的一种场景介绍

原创
作者头像
cdh
修改2024-09-15 13:01:50
修改2024-09-15 13:01:50
90300
代码可运行
举报
文章被收录于专栏:笔记+笔记+
运行总次数:0
代码可运行

在定位一个线上问题时发现Active(file)+Inactive(file)要比cached统计值大很多,看起来不太符合预期,正常情况下Active(file)+Inactive(file)的统计值都会同时计算到cached里,也就是一般cached的值会比Active(file)+Inactive(file)要大。

/proc/meminfo输出Cached信息内核统计方式如下,从前面meminfo信息看buffer的值并不大,也没有使用swap分区

从前面的截图可以看到AnonPages的值要比Active(anon)+Inactive(anon)大,推测是有部分anon page被统计到lru file page里,但是没有统计到lru anon中去。

搜下内核代码确实有相关的逻辑会将内存从LRU active annon移到lru inactive file的情况(但是这部分内存不会统计到cache里,这也是导致meminfo统计到的cache值比inactive file + active file小的原因):

代码语言:javascript
代码运行次数:0
复制
static void lru_lazyfree_fn(struct page *page, struct lruvec *lruvec)
{
        if (PageAnon(page) && PageSwapBacked(page) &&
            !PageSwapCache(page) && !PageUnevictable(page)) {
                bool active = PageActive(page);

                del_page_from_lru_list(page, lruvec,
                                       LRU_INACTIVE_ANON + active); //这里从LRU的active anon移除
                ClearPageActive(page);
                ClearPageReferenced(page);
                /*
                 * lazyfree pages are clean anonymous pages. They have
                 * SwapBacked flag cleared to distinguish normal anonymous
                 * pages
                 */
                ClearPageSwapBacked(page);
                add_page_to_lru_list(page, lruvec, LRU_INACTIVE_FILE);//把page加到lru INACTIVE FILE上

                __count_vm_events(PGLAZYFREE, hpage_nr_pages(page));
                count_memcg_page_event(page, PGLAZYFREE);
                update_page_reclaim_stat(lruvec, 1, 0);
        }
}

strace下业务进程看下业务进程都是如何使用内存的,看到有大量的madvise MADV_FREE的系统调用:

网上找了个madvise实例验证确认下:

代码语言:javascript
代码运行次数:0
复制
# cat madvise-sample.c
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

bool cont = true;
//static const ptrdiff_t len = 1 << 25; // 32 MB
static const ptrdiff_t len = (1*1024*1024*1024);
void
handle_signal(int sig)
{
        if (sig != SIGINT) {
                return;
        }

        cont ^= true;
}

void
wait()
{
        cont = false;
        signal(SIGINT, handle_signal);

        printf("CTRL+C to continue\n");

        while (!cont) {
                sleep(1);
        }
}

int
main(void)
{
        char *start, *end;
        void* pb;

        pb = sbrk(0);
        if (pb == (void*)-1) {
                perror("sbrk");
                return 1;
        }

        start = (char*)pb;
        end = start + len;

        // "allocate" mem by increasing the program break
        //
        if (!~brk(end)) {
                perror("brk");
                return 1;
        }
        printf("allocate mem\r\n");
        wait();

        // "touch" the memory so that we get it really utilized - at this point,
        // we should see the faults taking place, and both RSS and active anon
        // going up
        //
        for (; start < end;) {
                *(start++) = 123;
        }
        printf("using allocate mem\r\n");
        wait();

        // let the kernel know that we don't really need half of the memory we
        // allocated anymore - while this will not change RSS, it'll definitely
        // change active and inactive.
        //
/*        if (!~madvise(pb + (len >> 1), (len >> 1), MADV_FREE)) {
                perror("madvise");
                return 1;
        }*/
        if (!~madvise(pb  , len, MADV_FREE)) {
                perror("madvise");
                return 1;
        }

        printf("madvise MADV_FREE mem\r\n");

        wait();

        return 0;
}

compile it
gcc -O2 -static -o sample ./madvise-sample.c

run it in a terminal that has the current proc in a cgroup
mkdir /sys/fs/cgroup/memory/test
echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs
./sample

in another terminal, observe memory.stat for that cgroup
cat /sys/fs/cgroup/memory/test/memory.stat

use CTRL+C to make sample advance - see memory.stat as you do it.

验证效果:

1.开始执行测试程序时只申请了虚拟内存,所以看到内存Active(anon)并没有涨

2. 开始使用申请的内存后Active(anon)上涨

3. 调用madvise MADV_FREE后内存会被从Active(anon)移到Inactive(file)上,并且此时去看测试进程的内存的rss占用并不会降低。

执行drop cache并不会释放这部分内存,进程退出后这部分内存会自动释放回收,另外当系统内存紧张也就是出现低于水位线时该部分内存也会有机会被回收

MADV_FREE特性在linux 4.5内核版本才开始生效,这里用如下程序测试下在centos7 3.10内核和tllinux 6.6内核的区别:

代码语言:txt
复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#define SIZE_3GB (3ull * 1024 * 1024 * 1024)

int main() {
    void *addr = mmap(NULL, SIZE_3GB, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }


    for (size_t i = 0; i < SIZE_3GB; i += sysconf(_SC_PAGESIZE)) {
        ((char *)addr)[i] = 42;
    }
    printf("begin sleep\r\n");
    sleep(20);
    printf("end sleep\r\n");

    if (madvise(addr, SIZE_3GB, MADV_FREE) == -1) {
        perror("madvise");
        exit(1);
    }


    while (1) {
        sleep(1);
    }

    return 0;
}
~                      

在3.10.0-1160.119.1.el7.x86_64内核上测试,当程序使用madvise MADV_FREE后可以看到应用程序的rRssAnon直接被释放,系统可用内存统计MemAvailable也符合预期

在高版本内核比如5.4或者6.6内核当应用程序调用madvise MADV_FREE

后应用程序的RssAnon并没有减少,也就是内存并没有真正的归还给操作系统。

如果使用的是golang且版本是Go.12-Go1.15那么经常会碰到在高版本内核跟低版本内核内存统计表现不同的现象,相关介绍参考:

https://github.com/golang/go/issues/42330

https://go-review.googlesource.com/c/go/+/135395

https://blog.csdn.net/EDDYCJY/article/details/113750201

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档