Android中基於CGroup的memory子系統HAL層分析-lmkd


Android在內存管理上於Linux有些小的區別,其中一個就是引入了lowmemorykiller。從lowmemorykiller.c位於drivers/staging/android也可知道,屬於Android專有,沒有進入Linux kernel的mainline。

lmkd,即Low Memory Killer Daemon,基於memory子系統和Kernel lowmemorykiller功能參數,選擇一個合適的進程,然后kill進程,以達到釋放內存的目的。所以也繞不開Kernel模塊lowmemorykiller(drivers/staging/android/lowmemorykiller.c)。

在考慮一個系統服務的功能,不僅要分析其內部功能,還要對其輸入(lmkd socket、memory子系統和lowmemory)和輸出(kill)進行詳細的分析,才能更好的理解整個lmkd建立的生態。

他們之間的關系可以簡要概括如下:

image

lmkd相關模塊關系

啟動lmkd系統服務

在/etc/init/lmkd.rc中,啟動lmkd系統服務,創建了lmkd socket,並且將lmkd設置為system-background類型的進程。

service lmkd /system/bin/lmkd
    class core
    group root readproc
    critical
    socket lmkd seqpacket 0660 system system
    writepid /dev/cpuset/system-background/tasks

lmkd框架分析

正如上圖lmkd相關模塊分析中所示,lmkd通過讀取CGroup中memory子系統和lowmemory兩個模塊作為輸入參數;輸出是kill選定的進程。

正如所有的service一樣,lmkd的起點也是main函數,lmkd的main函數很簡單:

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);  鎖住該實時進程在物理內存上全部地址空間。這將阻止Linux將這個內存頁調度到交換空間(swap space),及時該進程已有一段時間沒有訪問這段空間。參見末尾參考資料。
    sched_setscheduler(0, SCHED_FIFO, &param);  設置lmkd調度類型為SCHED_FIFO的實時進程。
    if (!init())  初始化,主要是socket通信,epoll文件操作memory子系統sysfs
        mainloop();  epoll_wait處理epollfd

    ALOGI("exiting");
    return 0;
}

下面來分析一下主要核心函數init:

static int init(void) {
    struct epoll_event epev;
    int i;
    int ret;

    page_k = sysconf(_SC_PAGESIZE);
    if (page_k == -1)
        page_k = PAGE_SIZE;
    page_k /= 1024;

    epollfd = epoll_create(MAX_EPOLL_EVENTS);  創建全局epoll文件句柄
    if (epollfd == -1) {
        ALOGE("epoll_create failed (errno=%d)", errno);
        return -1;
    }

    ctrl_lfd = android_get_control_socket("lmkd");  打開lmkd socket文件句柄
    if (ctrl_lfd < 0) {
        ALOGE("get lmkd control socket failed");
        return -1;
    }

    ret = listen(ctrl_lfd, 1);
    if (ret < 0) {
        ALOGE("lmkd control socket listen failed (errno=%d)", errno);
        return -1;
    }

    epev.events = EPOLLIN;
    epev.data.ptr = (void *)ctrl_connect_handler;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) {  將lmkd socket加入epoll,處理函數問ctrl_connect_handler
        ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
        return -1;
    }
    maxevents++;

    use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK);

    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
    } else {
        ret = init_mp(MEMPRESSURE_WATCH_LEVEL, (void *)&mp_event); 處理memory pressure相關
        if (ret)
            ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
    }

    for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
        procadjslot_list[i].next = &procadjslot_list[i];
        procadjslot_list[i].prev = &procadjslot_list[i];
    }

    return 0;
}

1.創建epollfd文件,MAX_EPOLL_EVENTS為3,;

2.連接到lmkd socket,並將文件句柄加到epollfd,EPOLLIN的句柄函數問ctrl_connect_handler。

3.init_mp初始化memory pressure相關參數,創建一個用於事件通知的文件句柄,加入到epollfd,EPOLLIN的處理函數為mp_event。

 

init_mp將memory.presure_level的句柄,和創建用於本進程事件通知的evfd,然后和levelstr一起寫入cgroup.event_control。

static int init_mp(char *levelstr, void *event_handler)
{
    int mpfd;
    int evfd;
    int evctlfd;
    char buf[256];
    struct epoll_event epev;
    int ret;

    mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC);
    if (mpfd < 0) {
        ALOGI("No kernel memory.pressure_level support (errno=%d)", errno);
        goto err_open_mpfd;
    }

    evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control", O_WRONLY | O_CLOEXEC);
    if (evctlfd < 0) {
        ALOGI("No kernel memory cgroup event control (errno=%d)", errno);
        goto err_open_evctlfd;
    }

    evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 參見末尾參考資料,eventfd用於創建本進程事件通知的文件句柄。
    if (evfd < 0) {
        ALOGE("eventfd failed for level %s; errno=%d", levelstr, errno);
        goto err_eventfd;
    }

    ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr); ???
    if (ret >= (ssize_t)sizeof(buf)) {
        ALOGE("cgroup.event_control line overflow for level %s", levelstr);
        goto err;
    }

    ret = write(evctlfd, buf, strlen(buf) + 1);
    if (ret == -1) {
        ALOGE("cgroup.event_control write failed for level %s; errno=%d",
              levelstr, errno);
        goto err;
    }

    epev.events = EPOLLIN;
    epev.data.ptr = event_handler;
    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev);
    if (ret == -1) {
        ALOGE("epoll_ctl for level %s failed; errno=%d", levelstr, errno);
        goto err;
    }
    maxevents++;
    mpevfd = evfd;
    return 0;

err:
    close(evfd);
err_eventfd:
    close(evctlfd);
err_open_evctlfd:
    close(mpfd);
err_open_mpfd:
    return -1;
}

ctrl_connect_handler是lmkd socket相關句柄函數,accept之后又會創建ctrl_dfd句柄。如果是EPOLLHUP,則關閉ctrl_dfd;如果是EPOLLIN,則會根據cmd類型進行不同處理。

static void ctrl_data_handler(uint32_t events) {
    if (events & EPOLLHUP) {
        ALOGI("ActivityManager disconnected");
        if (!ctrl_dfd_reopened)
            ctrl_data_close();
    } else if (events & EPOLLIN) {
        ctrl_command_handler();
    }
}

LMK_TARGET類型對應cmd_targt,用於設置"/sys/module/lowmemorykiller/parameters/minfree"和"/sys/module/lowmemorykiller/parameters/adj"。

LMK_PROCPRIO類型對應cmd_procprio,用於寫入/proc/xxx/oom_score_adj,並將pid加入pidhash表中。

LMK_PROCREMOVE類型對應cmd_procremove,用於將pid從pidhash中移除。

在vmpressure上報low事件后,lmkd就會觸發mp_event處理memory pressure相關事件。mp_event就是low的處理函數,通過kill進程來釋放內存空間。

static void mp_event(uint32_t events __unused) {
    int ret;
    unsigned long long evcount;
    struct sysmeminfo mi;
    int other_free;
    int other_file;
    int killed_size;
    bool first = true;

    ret = read(mpevfd, &evcount, sizeof(evcount));
    if (ret < 0)
        ALOGE("Error reading memory pressure event fd; errno=%d",
              errno);

    if (time(NULL) - kill_lasttime < KILL_TIMEOUT)
        return;

    while (zoneinfo_parse(&mi) < 0) {
        // Failed to read /proc/zoneinfo, assume ENOMEM and kill something
        find_and_kill_process(0, 0, true);
    }  解析/proc/zoneinfo,主要解析nr_free_pages、nr_file_pages、nr_shmem、high、protection:。

    other_free = mi.nr_free_pages - mi.totalreserve_pages;
    other_file = mi.nr_file_pages - mi.nr_shmem;

基於zoneinfo解析,計算出other_free和other_file兩個參數,用於選取待kill的進程。

    do {
        killed_size = find_and_kill_process(other_free, other_file, first);這是最核心的地方。
        if (killed_size > 0) {
            first = false;
            other_free += killed_size;
            other_file += killed_size;
        }
    } while (killed_size > 0);循環釋放,直到killed_size<=0,也即滿足了最低內存需求。
}

find_and_kill_process根據other_free和other_file兩個參數,確定在哪個adj組中尋找進程。然后尋找最近使用進程kill。

static int find_and_kill_process(int other_free, int other_file, bool first)
{
    int i;
    int min_score_adj = OOM_SCORE_ADJ_MAX + 1;
    int minfree = 0;
    int killed_size = 0;

    for (i = 0; i < lowmem_targets_size; i++) {
        minfree = lowmem_minfree[i];
        if (other_free < minfree && other_file < minfree) {
            min_score_adj = lowmem_adj[i];
            break;
        }
    }

lowmem_minfree和lowmem_adj是從/sys/module/lowmemorykiller/parameters/minfree和/sys/module/lowmemorykiller/parameters/adj中解析出來的。釋放內存以達到最低使用內存,adj從0到906,每一個adj都有對應的最低內存,逐級釋放。

0,100,200,300,900,906
18432,23040,27648,32256,55296,80640

    if (min_score_adj == OOM_SCORE_ADJ_MAX + 1)
        return 0;

    for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) {
        struct proc *procp;

retry:
        procp = proc_adj_lru(i);  procadjslot_list尋找最近使用的proc

        if (procp) {
            killed_size = kill_one_process(procp, other_free, other_file, minfree, min_score_adj, first);  殺死procp指定的進程,返回釋放的內存大小。
            if (killed_size < 0) {
                goto retry;
            } else {
                return killed_size;
            }
        }
    }

    return 0;
}

lowmemorykiller分析

lowmemorykiller作為內核一個module,輸入參數有如下:

/sys/module/lowmemorykiller/parameters/adj  0,100,200,300,900,906
/sys/module/lowmemorykiller/parameters/cost  32
/sys/module/lowmemorykiller/parameters/debug_level  1
/sys/module/lowmemorykiller/parameters/minfree  18432,23040,27648,32256,55296,80640

adj文件包含oom_adj的閾值,minfree存放着對應的閾值,以page為單位。當對應的minfree值達到,則進程的oom_adj如果大於這個值將被殺掉。

ProcessList.java中定義的mOomAdj的值通過writeLmkd寫入sysfs節點,和上面對應:

private final int[] mOomAdj = new int[] {
        FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
        BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};

 

// These are the low-end OOM level limits.  This is appropriate for an
// HVGA or smaller phone with less than 512MB.  Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
        12288, 18432, 24576,
        36864, 43008, 49152
};
// These are the high-end OOM level limits.  This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM.  Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
        73728, 92160, 110592,
        129024, 147456, 184320
};

frameworks/base/services/core/java/com/android/server/am/ProcessList.java中定義了,不同類型進程對應的adj值:

static final int CACHED_APP_MAX_ADJ = 906;
static final int CACHED_APP_MIN_ADJ = 900;

static final int SERVICE_B_ADJ = 800;

static final int PREVIOUS_APP_ADJ = 700;

static final int HOME_APP_ADJ = 600;

static final int SERVICE_ADJ = 500;

static final int HEAVY_WEIGHT_APP_ADJ = 400;

static final int BACKUP_APP_ADJ = 300;

static final int PERCEPTIBLE_APP_ADJ = 200;

static final int VISIBLE_APP_ADJ = 100;
static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1;

static final int FOREGROUND_APP_ADJ = 0;

static final int PERSISTENT_SERVICE_ADJ = -700;

static final int PERSISTENT_PROC_ADJ = -800;

static final int SYSTEM_ADJ = -900;

static final int NATIVE_ADJ = -1000;

 

lowmem_init是整個模塊的入口,主要注冊一個shrinker,lowmem_shrinker。shrinker是內核內存回收機制。

static struct shrinker lowmem_shrinker = {
    .scan_objects = lowmem_scan,  如果count_objects返回值不為0,則被調用。
    .count_objects = lowmem_count,  返回緩存中可被釋放的內存大小。
    .seeks = DEFAULT_SEEKS * 16
};

 

lowmem_scan是shrinker的核心:

static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
    struct task_struct *tsk;
    struct task_struct *selected = NULL;
    unsigned long rem = 0;
    int tasksize;
    int i;
    short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
    int minfree = 0;
    int selected_tasksize = 0;
    short selected_oom_score_adj;
    int array_size = ARRAY_SIZE(lowmem_adj);
    int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
    int other_file = global_page_state(NR_FILE_PAGES) -
                        global_page_state(NR_SHMEM) -
                        total_swapcache_pages();

    if (lowmem_adj_size < array_size)
        array_size = lowmem_adj_size;
    if (lowmem_minfree_size < array_size)
        array_size = lowmem_minfree_size;
    for (i = 0; i < array_size; i++) {
        minfree = lowmem_minfree[i];
        if (other_free < minfree && other_file < minfree) {
            min_score_adj = lowmem_adj[i];  確定min_score_adj,從adj小的開始,也即內存最緊張的adj開始。直到找到other_free/other_file都小於minfree的adj。比這個adj大的進程都可以釋放。
            break;
        }
    }

    lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n",
            sc->nr_to_scan, sc->gfp_mask, other_free,
            other_file, min_score_adj);

    if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
        lowmem_print(5, "lowmem_scan %lu, %x, return 0\n",
                 sc->nr_to_scan, sc->gfp_mask);
        return 0;
    }

    selected_oom_score_adj = min_score_adj;

    rcu_read_lock();
    for_each_process(tsk) {  遍歷所有進程
        struct task_struct *p;
        short oom_score_adj;

        if (tsk->flags & PF_KTHREAD)
            continue;

        p = find_lock_task_mm(tsk);
        if (!p)
            continue;

        if (test_tsk_thread_flag(p, TIF_MEMDIE) &&
            time_before_eq(jiffies, lowmem_deathpending_timeout)) {
            task_unlock(p);
            rcu_read_unlock();
            return 0;
        }
        oom_score_adj = p->signal->oom_score_adj;
        if (oom_score_adj < min_score_adj) {  跳過高優先級的adj,adj小的優先級高。
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(p->mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)  跳過高優先級的adj,adj小的優先級高。
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)  如果adj和選中優先級相同,則選用tasksize大的進程,能釋放更多空間。
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_score_adj = oom_score_adj;
        lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
                 p->comm, p->pid, oom_score_adj, tasksize);
    }  所以總的原則是對所有oom_score_adj大於等於min_score_adj的進程,選取tasksize最大的進進程。也即根據進程的重要性(oom_adj)和釋放量(tasksize)進行選取。
    if (selected) {
        long cache_size = other_file * (long)(PAGE_SIZE / 1024);
        long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
        long free = other_free * (long)(PAGE_SIZE / 1024);

        task_lock(selected);
        send_sig(SIGKILL, selected, 0);  發送SIGKILL信號到選定的進程
        /*
         * FIXME: lowmemorykiller shouldn't abuse global OOM killer
         * infrastructure. There is no real reason why the selected
         * task should have access to the memory reserves.
         */
        if (selected->mm)
            mark_oom_victim(selected);
        task_unlock(selected);
        trace_lowmemory_kill(selected, cache_size, cache_limit, free);
        lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \
                    "   to free %ldkB on behalf of '%s' (%d) because\n" \
                    "   cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \
                    "   Free memory is %ldkB above reserved\n",
                 selected->comm, selected->pid,
                 selected_oom_score_adj,
                 selected_tasksize * (long)(PAGE_SIZE / 1024),
                 current->comm, current->pid,
                 cache_size, cache_limit,
                 min_score_adj,
                 free);
        lowmem_deathpending_timeout = jiffies + HZ;
        rem += selected_tasksize;
    }

    lowmem_print(4, "lowmem_scan %lu, %x, return %lu\n",
             sc->nr_to_scan, sc->gfp_mask, rem);
    rcu_read_unlock();
    return rem;
}

每一個進程都有oom_adj/oom_score/oom_score_adj節點,

oom_adj  -13
oom_score  0
oom_score_adj –800

oom_adj=oom_score_adj*17/1000=800*17/1000=13.6

CGroup memory子系統參數詳解

要理解memory.pressure_level,就要從何為Memory Pressure開始。

pressure_level通知可以被用來監控內存分配代價;基於不同的pressure_level,采取不同的策略管理內存資源。有以下三種pressure_level:

low:系統會采取回收內存給新的內存分配。

medium:系統會使用swap、換出活動文件緩存等方式來騰空內存

critical:表示系統此時已經OOM或者內核OOM即將觸發,應用應該盡可能采取措施騰出內存空間。

pressure_level出發后產生的events會向上傳播,直到被處理。比如三個cgroup:A->B->C。A、B、C都有事件監聽器,此時C觸發了memory pressure。這種情況下,C會受到通知,而A和B則不會。這是為了避免此類消息廣播,進而打斷系統。

memory.pressure_level只是被用來設置eventfd,節點的讀寫操作都沒有實現,所以在sysfs中無從獲得信息。下面是一個使用示例:

  • 使用eventfd創建一個evfd句柄
  • 打開memory.pressure_level節點mpfd
  • 將“<evfd> <mpfd> <level>”組成的字符串寫入cgroup.event_control

那么如果memory pressure達到一定level(low/medium/critical),相關應用就會通過eventfd被通知到。下面是lmkd中的一個實現:

static int init_mp(char *levelstr, void *event_handler)
{

    mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC);
    evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control", O_WRONLY | O_CLOEXEC);
    evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr);
    ret = write(evctlfd, buf, strlen(buf) + 1);

    epev.events = EPOLLIN;
    epev.data.ptr = event_handler;
    ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, evfd, &epev);
}

所以重點就轉到分析cgroup.event_control

static struct cftype mem_cgroup_legacy_files[] = {
    {
        .name = "cgroup.event_control",        /* XXX: for compat */
        .write = memcg_write_event_control,
        .flags = CFTYPE_NO_PREFIX | CFTYPE_WORLD_WRITABLE,
    },

}

memcg_write_event_control解析lmkd寫入的字符串,然后注冊cgroup的事件處理函數。

static ssize_t memcg_write_event_control(struct kernfs_open_file *of,
                     char *buf, size_t nbytes, loff_t off)
{
    struct cgroup_subsys_state *css = of_css(of);
    struct mem_cgroup *memcg = mem_cgroup_from_css(css);
    struct mem_cgroup_event *event;
    struct cgroup_subsys_state *cfile_css;
    unsigned int efd, cfd;
    struct fd efile;
    struct fd cfile;
    const char *name;
    char *endp;
    int ret;

    buf = strstrip(buf);

    efd = simple_strtoul(buf, &endp, 10);  解析出eventfd文件句柄
    if (*endp != ' ')
        return -EINVAL;
    buf = endp + 1;

    cfd = simple_strtoul(buf, &endp, 10);  解析出字符串的第二個參數句柄
    if ((*endp != ' ') && (*endp != '\0'))
        return -EINVAL;
    buf = endp + 1;  解析出第三個參數

    event = kzalloc(sizeof(*event), GFP_KERNEL);
    if (!event)
        return -ENOMEM;

    event->memcg = memcg;
    INIT_LIST_HEAD(&event->list);
    init_poll_funcptr(&event->pt, memcg_event_ptable_queue_proc);
    init_waitqueue_func_entry(&event->wait, memcg_event_wake);
    INIT_WORK(&event->remove, memcg_event_remove);

    efile = fdget(efd);
    if (!efile.file) {
        ret = -EBADF;
        goto out_kfree;
    }

    event->eventfd = eventfd_ctx_fileget(efile.file);
    if (IS_ERR(event->eventfd)) {
        ret = PTR_ERR(event->eventfd);
        goto out_put_efile;
    }

    cfile = fdget(cfd);
    if (!cfile.file) {
        ret = -EBADF;
        goto out_put_eventfd;
    }

    /* the process need read permission on control file */
    /* AV: shouldn't we check that it's been opened for read instead? */
    ret = inode_permission(file_inode(cfile.file), MAY_READ);
    if (ret < 0)
        goto out_put_cfile;

    /*
     * Determine the event callbacks and set them in @event.  This used
     * to be done via struct cftype but cgroup core no longer knows
     * about these events.  The following is crude but the whole thing
     * is for compatibility anyway.
     *
     * DO NOT ADD NEW FILES.
     */
    name = cfile.file->f_path.dentry->d_name.name;

    if (!strcmp(name, "memory.usage_in_bytes")) {  根據第二個參數文件名,選擇不同注冊/去注冊函數。
        event->register_event = mem_cgroup_usage_register_event;
        event->unregister_event = mem_cgroup_usage_unregister_event;
    } else if (!strcmp(name, "memory.oom_control")) {
        event->register_event = mem_cgroup_oom_register_event;
        event->unregister_event = mem_cgroup_oom_unregister_event;
    } else if (!strcmp(name, "memory.pressure_level")) {
        event->register_event = vmpressure_register_event;
        event->unregister_event = vmpressure_unregister_event;
    } else if (!strcmp(name, "memory.memsw.usage_in_bytes")) {
        event->register_event = memsw_cgroup_usage_register_event;
        event->unregister_event = memsw_cgroup_usage_unregister_event;
    } else {
        ret = -EINVAL;
        goto out_put_cfile;
    }

    /*
     * Verify @cfile should belong to @css.  Also, remaining events are
     * automatically removed on cgroup destruction but the removal is
     * asynchronous, so take an extra ref on @css.
     */
    cfile_css = css_tryget_online_from_dir(cfile.file->f_path.dentry->d_parent,
                           &memory_cgrp_subsys);
    ret = -EINVAL;
    if (IS_ERR(cfile_css))
        goto out_put_cfile;
    if (cfile_css != css) {
        css_put(cfile_css);
        goto out_put_cfile;
    }

    ret = event->register_event(memcg, event->eventfd, buf);  執行注冊
    if (ret)
        goto out_put_css;

    efile.file->f_op->poll(efile.file, &event->pt);

    spin_lock(&memcg->event_list_lock);
    list_add(&event->list, &memcg->event_list);
    spin_unlock(&memcg->event_list_lock);

    fdput(cfile);
    fdput(efile);

    return nbytes;

out_put_css:
    css_put(css);
out_put_cfile:
    fdput(cfile);
out_put_eventfd:
    eventfd_ctx_put(event->eventfd);
out_put_efile:
    fdput(efile);
out_kfree:
    kfree(event);

    return ret;
}

vmpressure_register_event會將vmpressure通知和eventfs綁定,這樣lmkd就會收到vmpressure的通知了。

memcg:需要關注vmpressure通知的CGroup子系統memory

eventfd:接收vmpressure通知的eventfd句柄

args:設置pressure_level參數

int vmpressure_register_event(struct mem_cgroup *memcg,
                  struct eventfd_ctx *eventfd, const char *args)
{
    struct vmpressure *vmpr = memcg_to_vmpressure(memcg);
    struct vmpressure_event *ev;
    int level;

    for (level = 0; level < VMPRESSURE_NUM_LEVELS; level++) {
        if (!strcmp(vmpressure_str_levels[level], args))  檢查pressure_level有效性:low/medium/critical。
            break;
    }

    if (level >= VMPRESSURE_NUM_LEVELS)
        return -EINVAL;

    ev = kzalloc(sizeof(*ev), GFP_KERNEL);
    if (!ev)
        return -ENOMEM;

    ev->efd = eventfd;
    ev->level = level;

    mutex_lock(&vmpr->events_lock);
    list_add(&ev->node, &vmpr->events);
    mutex_unlock(&vmpr->events_lock);

    return 0;
}

關於Memory Pressure深度閱讀參考:Documents/cgroups/memory.txt 第11小節 Memory Pressure

這里有涉及到一個概念vmpressure。應用不會去關注系統有多少可用空間,但是作為一個整體的系統如果能對內存緊缺進行通知,並讓應用采取相關措施以減少內存分配。vmpressure就是這樣一種機制,通過vmpressure內核能夠通知用戶空間,系統當前處於何種memory pressure等級。

應用?

整個框架提供的配置參數就是應用的切入點:

  1. 根據內存大小?屏幕分辨率?…情況配置不同的minfree值。

  2. 增加adj個數,增加lowmemorykiller的控制粒度;或者修改adj大小,改變不同類型進程的優先級。

  3. memory pressure的levelstr,low?medium?critical?進行不同的處理?

  4. 修改vmpressure觸發不同level的條件?

參考資料

mlockall/munlockall:http://pubs.opengroup.org/onlinepubs/007908799/xsh/mlockall.html

mlockall函數:http://blog.csdn.net/zhjutao/article/details/8652252

event:http://www.man7.org/linux/man-pages/man2/eventfd.2.html

Memory Pressure:https://linux-mm.org/Memory_pressure

vmpressure_fd:https://lwn.net/Articles/524742/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM