Zoned Allocator -9- (Direct Reclaim-Shrink-2)




 * zone->lru_lock is heavily contended.  Some of the functions that
 * shrink the lists perform better by taking out a batch of pages
 * and working on them outside the LRU lock.
 * For pagecache intensive workloads, this function is the hottest
 * spot in the kernel (apart from copy_*_user functions).
 * Appropriate locks must be held before calling this function.
 * @nr_to_scan: The number of pages to look through on the list.
 * @lruvec:     The LRU vector to pull pages from.
 * @dst:        The temp list to put pages on to.
 * @nr_scanned: The number of pages that were scanned.
 * @sc:         The scan_control struct for this reclaim session
 * @mode:       One of the LRU isolation modes
 * @lru:        LRU list id for isolating
 * returns how many pages were moved onto *@dst.
static unsigned long isolate_lru_pages(unsigned long nr_to_scan,
                struct lruvec *lruvec, struct list_head *dst,
                unsigned long *nr_scanned, struct scan_control *sc,
                isolate_mode_t mode, enum lru_list lru)
        struct list_head *src = &lruvec->lists[lru];
        unsigned long nr_taken = 0;
        unsigned long scan;

        for (scan = 0; scan < nr_to_scan && !list_empty(src); scan++) {
                struct page *page;
                int nr_pages;

                page = lru_to_page(src);
                prefetchw_prev_lru_page(page, src, flags);

                VM_BUG_ON_PAGE(!PageLRU(page), page);

                switch (__isolate_lru_page(page, mode)) {
                case 0:
                        nr_pages = hpage_nr_pages(page);
                        mem_cgroup_update_lru_size(lruvec, lru, -nr_pages);
                        list_move(&page->lru, dst);
                        nr_taken += nr_pages;

                case -EBUSY:
                        /* else it is being freed elsewhere */
                        list_move(&page->lru, src);


        *nr_scanned = scan;
        trace_mm_vmscan_lru_isolate(sc->order, nr_to_scan, scan,
                                    nr_taken, mode, is_file_lru(lru));
        return nr_taken;

지정한 lru 벡터 리스트로부터 nr_to_scan 만큼 스캔을 시도하여 분리된 페이지는 dst 리스트에 담고 분리된 페이지 수를 반환한다.

  • struct list_head *src = &lruvec->lists[lru];
    • 작업할 lru 벡터 리스트
  • for (scan = 0; scan < nr_to_scan && !list_empty(src); scan++) {
    • lru 리스트에 엔트리가 있는 한 nr_to_scan 까지 스캔을 반복한다.
  • page = lru_to_page(src); prefetchw_prev_lru_page(page, src, flags);
    • 이전 페이지의 플래그 값을 캐시에 미리 로드한다.
  • switch (__isolate_lru_page(page, mode)) {
    • 한 페이지의 분리를 시도한다.
      • 성공 시 0,
      • 관련 없는 페이지의 분리를 시도하는 경우 -EINVAL
      • 모드 옵션으로 인해 당장 분리할 수 없는 경우 -EBUSY
  • case 0: nr_pages = hpage_nr_pages(page); mem_cgroup_update_lru_size(lruvec, lru, -nr_pages); list_move(&page->lru, dst); nr_taken += nr_pages; break;
    • 페이지 분리가 성공인 경우 memcg용 lru 벡터의 사이즈(갯수)를 갱신하고 페이지를 dst 리스트에 옮기고 nr_taken 만큼 페이지 수를 추가한다.
      • 1개의 trans huge 페이지로 구성된 페이지는 HPAGE_PMD_NR(아키텍처에 따라 다르다) 수로 구성된다.
  • case -EBUSY: list_move(&page->lru, src); continue;
    • 곧 free될 예정으로 잠시 보류하기 위해 현재 작업중인 lru 벡터 리스트의 선두(hot)로 다시 되돌린다.
  • *nr_scanned = scan; return nr_taken;
    • scan 건 수를 출력 인수 nr_scanned에 대입하고 isolation된 페이지 수를 반환한다.




 * Attempt to remove the specified page from its LRU.  Only take this page
 * if it is of the appropriate PageActive status.  Pages which are being
 * freed elsewhere are also ignored.
 * page:        page to consider
 * mode:        one of the LRU isolation modes defined above
 * returns 0 on success, -ve errno on failure.
int __isolate_lru_page(struct page *page, isolate_mode_t mode)
        int ret = -EINVAL;

        /* Only take pages on the LRU. */
        if (!PageLRU(page))
                return ret;

        /* Compaction should not handle unevictable pages but CMA can do so */
        if (PageUnevictable(page) && !(mode & ISOLATE_UNEVICTABLE))
                return ret;

        ret = -EBUSY;

         * To minimise LRU disruption, the caller can indicate that it only
         * wants to isolate pages it will be able to operate on without
         * blocking - clean pages for the most part.
         * ISOLATE_CLEAN means that only clean pages should be isolated. This
         * is used by reclaim when it is cannot write to backing storage
         * ISOLATE_ASYNC_MIGRATE is used to indicate that it only wants to pages
         * that it is possible to migrate without blocking
                /* All the caller can do on PageWriteback is block */
                if (PageWriteback(page))
                        return ret;

                if (PageDirty(page)) {
                        struct address_space *mapping;

                        /* ISOLATE_CLEAN means only clean pages */
                        if (mode & ISOLATE_CLEAN)
                                return ret;

                         * Only pages without mappings or that have a
                         * ->migratepage callback are possible to migrate
                         * without blocking
                        mapping = page_mapping(page);
                        if (mapping && !mapping->a_ops->migratepage)
                                return ret;

        if ((mode & ISOLATE_UNMAPPED) && page_mapped(page))
                return ret;

        if (likely(get_page_unless_zero(page))) {
                 * Be careful not to clear PageLRU until after we're
                 * sure the page is not being freed elsewhere -- the
                 * page release code relies on it.
                ret = 0;

        return ret;

요청 페이지를 분리를 시도하고 성공 시 0을 반환한다. 만일 관련 없는 페이지의 분리를 시도하는 경우 -EINVAL을 반환하고, 모드 옵션으로 인해 당장 분리할 수 없는 경우 -EBUSY를 반환한다.

  • if (!PageLRU(page)) return ret;
    • lru 페이지가 아닌 경우 분리를 포기한다.
  • if (PageUnevictable(page) && !(mode & ISOLATE_UNEVICTABLE)) return ret;
    • unevictable 페이지이면서 모드에 unevictable의 분리를 허용하지 않은 경우 분리를 포기한다.
  • if (mode & (ISOLATE_CLEAN|ISOLATE_ASYNC_MIGRATE)) { if (PageWriteback(page)) return ret;
    • 모드에 clean 또는 비동기 migration이 있는 경우 writeback 페이지는 분리를 포기한다.
  • if (PageDirty(page)) { struct address_space *mapping; if (mode & ISOLATE_CLEAN) return ret;
    • dirty 페이지인 경우 모드에 clean 요청이 있는 경우 분리를 포기한다.
  • mapping = page_mapping(page); if (mapping && !mapping->a_ops->migratepage) return ret;
    • mapping을 알아오고 해당 핸들러 함수 migratepage()를 수행한 결과가 실패하는 경우 분리를 포기한다.
  • if ((mode & ISOLATE_UNMAPPED) && page_mapped(page)) return ret;
    • 모드에 unmapped를 요청한 경우에는 mapped 페이지는 분리를 포기한다.
  • if (likely(get_page_unless_zero(page))) { ClearPageLRU(page); ret = 0; }
    • 높은 확률로 참조카운터를 증가시키며 이 전에 사용하지 않은 경우 페이지의 lru 플래그 비트를 클리어하고 성공적으로 리턴한다.




 * mem_cgroup_update_lru_size - account for adding or removing an lru page
 * @lruvec: mem_cgroup per zone lru vector
 * @lru: index of lru list the page is sitting on
 * @nr_pages: positive when adding or negative when removing
 * This function must be called when a page is added to or removed from an
 * lru list.
void mem_cgroup_update_lru_size(struct lruvec *lruvec, enum lru_list lru,
                                int nr_pages)
        struct mem_cgroup_per_zone *mz;
        unsigned long *lru_size;

        if (mem_cgroup_disabled())

        mz = container_of(lruvec, struct mem_cgroup_per_zone, lruvec);
        lru_size = mz->lru_size + lru;
        *lru_size += nr_pages;
        VM_BUG_ON((long)(*lru_size) < 0);

memcg용 lru 벡터의 사이즈에 요청한 nr_pages를 더한 값으로 udpate한다.




 * This moves pages from the active list to the inactive list.
 * We move them the other way if the page is referenced by one or more
 * processes, from rmap.
 * If the pages are mostly unmapped, the processing is fast and it is
 * appropriate to hold zone->lru_lock across the whole operation.  But if
 * the pages are mapped, the processing is slow (page_referenced()) so we
 * should drop zone->lru_lock around each page.  It's impossible to balance
 * this, so instead we remove the pages from the LRU while processing them.
 * It is safe to rely on PG_active against the non-LRU pages in here because
 * nobody will play with that bit on a non-LRU page.
 * The downside is that we have to touch page->_count against each page.
 * But we had to alter page->flags anyway.

static void move_active_pages_to_lru(struct lruvec *lruvec,
                                     struct list_head *list,
                                     struct list_head *pages_to_free,
                                     enum lru_list lru)
        struct zone *zone = lruvec_zone(lruvec);
        unsigned long pgmoved = 0;
        struct page *page;
        int nr_pages;

        while (!list_empty(list)) {
                page = lru_to_page(list);
                lruvec = mem_cgroup_page_lruvec(page, zone);

                VM_BUG_ON_PAGE(PageLRU(page), page);

                nr_pages = hpage_nr_pages(page);
                mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
                list_move(&page->lru, &lruvec->lists[lru]);
                pgmoved += nr_pages;

                if (put_page_testzero(page)) {
                        del_page_from_lru_list(page, lruvec, lru);

                        if (unlikely(PageCompound(page))) {
                        } else
                                list_add(&page->lru, pages_to_free);
        __mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
        if (!is_active_lru(lru))
                __count_vm_events(PGDEACTIVATE, pgmoved);

active 리스트에 있는 옮길 페이지를 inactive 리스트로 옮긴다. 만일 사용자가 없는 페이지인 경우 pages_to_free에 옮긴다.

  • while (!list_empty(list)) {
    • 리스트의 모든 엔트리 수 만큼 루프를 돈다.
  • lruvec = mem_cgroup_page_lruvec(page, zone);
    • 해당 페이지의 memcg용 lruvec를 알아온다.
  • SetPageLRU(page);
    • 페이지의 lru 플래그 비트를 설정한다.
  • nr_pages = hpage_nr_pages(page);
    • transparent huge page인 경우 그 페이지 수를 알아온다. 그렇지 않은 경우 1이다.
  •  mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
    • memcg용 lru 사이즈(갯수)를 갱신한다.
  • list_move(&page->lru, &lruvec->lists[lru]);
    • 페이지를 lru 벡터로 옮긴다. (hot 방향)
  • pgmoved += nr_pages;
    • pgmoved 카운터에 페이지  수 만큼 추가한다.
  • if (put_page_testzero(page)) { __ClearPageLRU(page); __ClearPageActive(page); del_page_from_lru_list(page, lruvec, lru);
    • 참조 카운터를 감소시켜 0이된 경우, 즉 사용자가 없어져 free해도 될 경우 lru 및 active 플래그 비트를 클리어하고 다시 lru 벡터 리스트에서 제거한다.
  • if (unlikely(PageCompound(page))) { spin_unlock_irq(&zone->lru_lock); mem_cgroup_uncharge(page); (*get_compound_page_dtor(page))(page); spin_lock_irq(&zone->lru_lock); } else list_add(&page->lru, pages_to_free);
    • 작은 확률로 compound 페이지인 경우 memcg에 uncharge 정보를 전달하고 compound 페이지의 파괴자를 호출한다. compound 페이지가 아닌 경우 pages_to_free 리스트에 추가하여 함수 종료 후 free를 시도한다.
  • __mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
    • 지정 lru 벡터 stat에 pgmoved 만큼 추가한다.
  • if (!is_active_lru(lru)) __count_vm_events(PGDEACTIVATE, pgmoved);
    • active lru가 아닌 경우 PGDEACTIVATE stat에 pgmoved 만큼 추가한다.




 * shrink_page_list() returns the number of reclaimed pages
static unsigned long shrink_page_list(struct list_head *page_list,
                                      struct zone *zone,
                                      struct scan_control *sc,
                                      enum ttu_flags ttu_flags,
                                      unsigned long *ret_nr_dirty,
                                      unsigned long *ret_nr_unqueued_dirty,
                                      unsigned long *ret_nr_congested,
                                      unsigned long *ret_nr_writeback,
                                      unsigned long *ret_nr_immediate,
                                      bool force_reclaim)
        int pgactivate = 0;
        unsigned long nr_unqueued_dirty = 0;
        unsigned long nr_dirty = 0;
        unsigned long nr_congested = 0;
        unsigned long nr_reclaimed = 0;
        unsigned long nr_writeback = 0;
        unsigned long nr_immediate = 0;


        while (!list_empty(page_list)) {
                struct address_space *mapping;
                struct page *page;
                int may_enter_fs;
                enum page_references references = PAGEREF_RECLAIM_CLEAN;
                bool dirty, writeback;


                page = lru_to_page(page_list);

                if (!trylock_page(page))
                        goto keep;

                VM_BUG_ON_PAGE(PageActive(page), page);
                VM_BUG_ON_PAGE(page_zone(page) != zone, page);


                if (unlikely(!page_evictable(page)))
                        goto cull_mlocked;

                if (!sc->may_unmap && page_mapped(page))
                        goto keep_locked;

전달받은 페이지 리스트에 대해 shrink를 수행하고 회수된 페이지의 수를 반환한다.


  • while (!list_empty(page_list)) {
    • page_list의 페이지 수 만큼 루프를 돈다.
  • page = lru_to_page(page_list); list_del(&page->lru);
    • 페이지를 리스트에서 page_list에서 분리한다.
  • if (!trylock_page(page)) goto keep;
    • page lock 획득을 시도해보고 실패하는 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • if (unlikely(!page_evictable(page))) goto cull_mlocked;
    • 작은 확률로 페이지가 unevictable인 경우 lruvec.lists[LRU_UNEVICTABLE]로 되돌린다.
  • if (!sc->may_unmap && page_mapped(page)) goto keep_locked;
    • mapped 페이지이지만 may_unmap 요청이 없는 경우 다음에 처리하도록 page_list에 다시 추가한다.


                /* Double the slab pressure for mapped and swapcache pages */
                if (page_mapped(page) || PageSwapCache(page))

                may_enter_fs = (sc->gfp_mask & __GFP_FS) ||
                        (PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));

                 * The number of dirty pages determines if a zone is marked
                 * reclaim_congested which affects wait_iff_congested. kswapd
                 * will stall and start writing pages if the tail of the LRU
                 * is all dirty unqueued pages.
                page_check_dirty_writeback(page, &dirty, &writeback);
                if (dirty || writeback)

                if (dirty && !writeback)

                 * Treat this page as congested if the underlying BDI is or if
                 * pages are cycling through the LRU so quickly that the
                 * pages marked for immediate reclaim are making it to the
                 * end of the LRU a second time.
                mapping = page_mapping(page);
                if (((dirty || writeback) && mapping &&
                     bdi_write_congested(inode_to_bdi(mapping->host))) ||
                    (writeback && PageReclaim(page)))
  • if (page_mapped(page) || PageSwapCache(page)) sc->nr_scanned++;
    • mapped 페이지 이거나 swapcache 페이지인 경우 nr_scanned 카운터를 증가시킨다.
  • may_enter_fs = (sc->gfp_mask & __GFP_FS) || (PageSwapCache(page) && (sc->gfp_mask & __GFP_IO));
    • 파일시스템의 이용이 허용되었거나 swapcache 페이지이면서 io가 허용된 경우 may_enter_fs에 true가 대입된다.
  • page_check_dirty_writeback(page, &dirty, &writeback); if (dirty || writeback) nr_dirty++;
    • dirty 또는 writeback 페이지인 경우 nr_dirty 카운터를 증가한다.
  • if (dirty && !writeback) nr_unqueued_dirty++;
    • dirty이면서 writeback은 없는 경우 nr_unqueued_dirty 카운터를 증가한다.
  • mapping = page_mapping(page); if (((dirty || writeback) && mapping && bdi_write_congested(inode_to_bdi(mapping->host))) || (writeback && PageReclaim(page))) nr_congested++;
    • 다음 두 가지의 경우 nr_congested 카운터를 증가한다.
      • dirty 또는 writeback이고 mapping->host inode를 담당하는 backing device가 write 혼잡 상태인 경우
      • writeback 및 reclaim 페이지인 경우


                 * If a page at the tail of the LRU is under writeback, there
                 * are three cases to consider.
                 * 1) If reclaim is encountering an excessive number of pages
                 *    under writeback and this page is both under writeback and
                 *    PageReclaim then it indicates that pages are being queued
                 *    for IO but are being recycled through the LRU before the
                 *    IO can complete. Waiting on the page itself risks an
                 *    indefinite stall if it is impossible to writeback the
                 *    page due to IO error or disconnected storage so instead
                 *    note that the LRU is being scanned too quickly and the
                 *    caller can stall after page list has been processed.
                 * 2) Global reclaim encounters a page, memcg encounters a
                 *    page that is not marked for immediate reclaim or
                 *    the caller does not have __GFP_IO. In this case mark
                 *    the page for immediate reclaim and continue scanning.
                 *    __GFP_IO is checked  because a loop driver thread might
                 *    enter reclaim, and deadlock if it waits on a page for
                 *    which it is needed to do the write (loop masks off
                 *    __GFP_IO|__GFP_FS for this reason); but more thought
                 *    would probably show more reasons.
                 *    Don't require __GFP_FS, since we're not going into the
                 *    FS, just waiting on its writeback completion. Worryingly,
                 *    ext4 gfs2 and xfs allocate pages with
                 *    grab_cache_page_write_begin(,,AOP_FLAG_NOFS), so testing
                 *    may_enter_fs here is liable to OOM on them.
                 * 3) memcg encounters a page that is not already marked
                 *    PageReclaim. memcg does not have any dirty pages
                 *    throttling so we could easily OOM just because too many
                 *    pages are in writeback and there is nothing else to
                 *    reclaim. Wait for the writeback to complete.
                if (PageWriteback(page)) {
                        /* Case 1 above */
                        if (current_is_kswapd() &&
                            PageReclaim(page) &&
                            test_bit(ZONE_WRITEBACK, &zone->flags)) {
                                goto keep_locked;

                        /* Case 2 above */
                        } else if (global_reclaim(sc) ||
                            !PageReclaim(page) || !(sc->gfp_mask & __GFP_IO)) {
                                 * This is slightly racy - end_page_writeback()
                                 * might have just cleared PageReclaim, then
                                 * setting PageReclaim here end up interpreted
                                 * as PageReadahead - but that does not matter
                                 * enough to care.  What we do want is for this
                                 * page to have PageReclaim set next time memcg
                                 * reclaim reaches the tests above, so it will
                                 * then wait_on_page_writeback() to avoid OOM;
                                 * and it's also appropriate in global reclaim.

                                goto keep_locked;

                        /* Case 3 above */
                        } else {
  • if (PageWriteback(page)) {
    • writeback 페이지인 경우
  • if (current_is_kswapd() && PageReclaim(page) && test_bit(ZONE_WRITEBACK, &zone->flags)) { nr_immediate++; goto keep_locked;
    • 현재 태스크가 kswapd이면서 writeback이 허용된 zone에서 reclaim 페이지를 만나면 nr_immediate 카운터를 증가시키고 다음에 처리하도록 page_list에 다시 추가한다.
  • } else if (global_reclaim(sc) || !PageReclaim(page) || !(sc->gfp_mask & __GFP_IO)) { SetPageReclaim(page); nr_writeback++; goto keep_locked;
    • global reclaim이거나 reclaim 페이지가 아니거나 io가 허용되지 않은 경우 페이지에 reclaim 플래그를 설정하고 nr_writeback을 증가시키고 다음에 처리하도록 page_list에 다시 추가한다.
  • } else { wait_on_page_writeback(page); }
    • 해당 페이지의 writeback이 완료될 때까지 대기한다.


                if (!force_reclaim)
                        references = page_check_references(page, sc);

                switch (references) {
                case PAGEREF_ACTIVATE:
                        goto activate_locked;
                case PAGEREF_KEEP:
                        goto keep_locked;
                case PAGEREF_RECLAIM:
                case PAGEREF_RECLAIM_CLEAN:
                        ; /* try to reclaim the page below */

                 * Anonymous process memory has backing store?
                 * Try to allocate it some swap space here.
                if (PageAnon(page) && !PageSwapCache(page)) {
                        if (!(sc->gfp_mask & __GFP_IO))
                                goto keep_locked;
                        if (!add_to_swap(page, page_list))
                                goto activate_locked;
                        may_enter_fs = 1;

                        /* Adding to swap updated mapping */
                        mapping = page_mapping(page);

                 * The page is mapped into the page tables of one or more
                 * processes. Try to unmap it here.
                if (page_mapped(page) && mapping) {
                        switch (try_to_unmap(page, ttu_flags)) {
                        case SWAP_FAIL:
                                goto activate_locked;
                        case SWAP_AGAIN:
                                goto keep_locked;
                        case SWAP_MLOCK:
                                goto cull_mlocked;
                        case SWAP_SUCCESS:
                                ; /* try to free the page below */
  • if (!force_reclaim) references = page_check_references(page, sc);
    • force_reclaim이 아닌 경우 페이지 참조 상태를 알아온다.
  • switch (references) { case PAGEREF_ACTIVATE: goto activate_locked;
    • 활동중인 경우 페이지에 Active 비트를 설정하고 다음에 처리하도록 page_list에 다시 추가한다.
  • case PAGEREF_KEEP: goto keep_locked;
    • 다음에 처리하도록 page_list에 다시 추가한다.
    • 페이지 회수를 위해 아래 루틴을 계속 수행한다.
  • if (PageAnon(page) && !PageSwapCache(page)) {
    • anon 페이지이면서 swapcache 되지 않은 경우
  • if (!(sc->gfp_mask & __GFP_IO)) goto keep_locked;
    • io 처리를 하지 못하게한 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • if (!add_to_swap(page, page_list)) goto activate_locked;
    • swap 영역에 추가(기록)하되 실패한 경우 page의 Active 플래그 비트를 설정하고 다음에 처리하도록 page_list에 다시 추가한다.
  • may_enter_fs = 1; mapping = page_mapping(page); }
    • may_enter_fs를 설정하고 매핑을 알아온다.
  • if (page_mapped(page) && mapping) {
    • mapped 페이지이면서 매핑이 있는 경우
  • switch (try_to_unmap(page, ttu_flags)) { case SWAP_FAIL: goto activate_locked;
    • 페이지 언매핑을 시도하고 실패한 경우 page의 Active 플래그 비트를 설정하고 다음에 처리하도록 page_list에 다시 추가한다.
  • case SWAP_AGAIN: goto keep_locked;
    • 다음에 처리하도록 page_list에 다시 추가한다.
  • case SWAP_MLOCK: goto cull_mlocked;
    • lruvec.lists[]로 되돌린다.
  • case SWAP_SUCCESS: }
    • 페이지가 정상적으로 unmap되었고 아래 루틴을 통해 페이지를 free 시킨다.


                if (PageDirty(page)) {
                         * Only kswapd can writeback filesystem pages to
                         * avoid risk of stack overflow but only writeback
                         * if many dirty pages have been encountered.
                        if (page_is_file_cache(page) &&
                                        (!current_is_kswapd() ||
                                         !test_bit(ZONE_DIRTY, &zone->flags))) {
                                 * Immediately reclaim when written back.
                                 * Similar in principal to deactivate_page()
                                 * except we already have the page isolated
                                 * and know it's dirty
                                inc_zone_page_state(page, NR_VMSCAN_IMMEDIATE);

                                goto keep_locked;

                        if (references == PAGEREF_RECLAIM_CLEAN)
                                goto keep_locked;
                        if (!may_enter_fs)
                                goto keep_locked;
                        if (!sc->may_writepage)
                                goto keep_locked;

                        /* Page is dirty, try to write it out here */
                        switch (pageout(page, mapping, sc)) {
                        case PAGE_KEEP:
                                goto keep_locked;
                        case PAGE_ACTIVATE:
                                goto activate_locked;
                        case PAGE_SUCCESS:
                                if (PageWriteback(page))
                                        goto keep;
                                if (PageDirty(page))
                                        goto keep;

                                 * A synchronous write - probably a ramdisk.  Go
                                 * ahead and try to reclaim the page.
                                if (!trylock_page(page))
                                        goto keep;
                                if (PageDirty(page) || PageWriteback(page))
                                        goto keep_locked;
                                mapping = page_mapping(page);
                        case PAGE_CLEAN:
                                ; /* try to free the page below */
  • if (PageDirty(page)) {
    • dirty 페이지인 경우
  • if (page_is_file_cache(page) && (!current_is_kswapd() || !test_bit(ZONE_DIRTY, &zone->flags))) { inc_zone_page_state(page, NR_VMSCAN_IMMEDIATE); SetPageReclaim(page); goto keep_locked; }
    • file 캐시 페이지이면서 direct reclaim 또는 zone이 dirty를 허용하지 않는 경우 NR_VMSCAN_IMMEDIATE stat을 증가시키고 페이지를 reclaim 비트 플래그를 설정한 후 다음에 처리하도록 page_list에 다시 추가한다.
  • if (references == PAGEREF_RECLAIM_CLEAN) goto keep_locked;
    • 다음에 처리하도록 page_list에 다시 추가한다.
  • if (!may_enter_fs) goto keep_locked;
    • 파일 시스템을 이용할 수 없으면 다음에 처리하도록 page_list에 다시 추가한다.
  • if (!sc->may_writepage) goto keep_locked;
    • 파일에 기록을 할 수 없으면 다음에 처리하도록 page_list에 다시 추가한다.
  • switch (pageout(page, mapping, sc)) {
    • 페이지를 파일에 기록한다.
  • case PAGE_KEEP: goto keep_locked;
    • 다음에 처리하도록 page_list에 다시 추가한다.
  • case PAGE_ACTIVATE: goto activate_locked;
    • page의 Active 플래그 비트를 설정하고 다음에 처리하도록 page_list에 다시 추가한다.
  • case PAGE_SUCCESS: if (PageWriteback(page)) goto keep;
    • 페이지를 파일에 기록을 성공적으로 완료시켰고 여전히 writeback 플래그 상태인 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • if (PageDirty(page)) goto keep;
    • 여전히 dirty 플래그 상태인 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • if (!trylock_page(page)) goto keep;
    • 페이지의 락 획득을 시도하여 실패한 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • if (PageDirty(page) || PageWriteback(page)) goto keep_locked;
    • 다시 한 번 확인차 페이지가 dirty 상태이거나 writeback 상태이면 다음에 처리하도록 page_list에 다시 추가한다.
  • mapping = page_mapping(page);
    • 매핑을 얻어온다.
  • case PAGE_CLEAN: }
    • 페이지를 free하기 위해 아래 루틴을 계속한다.


                 * If the page has buffers, try to free the buffer mappings
                 * associated with this page. If we succeed we try to free
                 * the page as well.
                 * We do this even if the page is PageDirty().
                 * try_to_release_page() does not perform I/O, but it is
                 * possible for a page to have PageDirty set, but it is actually
                 * clean (all its buffers are clean).  This happens if the
                 * buffers were written out directly, with submit_bh(). ext3
                 * will do this, as well as the blockdev mapping.
                 * try_to_release_page() will discover that cleanness and will
                 * drop the buffers and mark the page clean - it can be freed.
                 * Rarely, pages can have buffers and no ->mapping.  These are
                 * the pages which were not successfully invalidated in
                 * truncate_complete_page().  We try to drop those buffers here
                 * and if that worked, and the page is no longer mapped into
                 * process address space (page_count == 1) it can be freed.
                 * Otherwise, leave the page on the LRU so it is swappable.
                if (page_has_private(page)) {
                        if (!try_to_release_page(page, sc->gfp_mask))
                                goto activate_locked;
                        if (!mapping && page_count(page) == 1) {
                                if (put_page_testzero(page))
                                        goto free_it;
                                else {
                                         * rare race with speculative reference.
                                         * the speculative reference will free
                                         * this page shortly, so we may
                                         * increment nr_reclaimed here (and
                                         * leave it off the LRU).

                if (!mapping || !__remove_mapping(mapping, page, true))
                        goto keep_locked;

                 * At this point, we have no other references and there is
                 * no way to pick any more up (removed from LRU, removed
                 * from pagecache). Can use non-atomic bitops now (and
                 * we obviously don't have to worry about waking up a process
                 * waiting on the page lock, because there are no references.
  • if (page_has_private(page)) { if (!try_to_release_page(page, sc->gfp_mask)) goto activate_locked;
    • private 페이지인 경우 페이지를 release 시도하고 실패한 경우 page의 Active 플래그 비트를 설정하고 다음에 처리하도록 page_list에 다시 추가한다.
  • if (!mapping && page_count(page) == 1) { unlock_page(page); if (put_page_testzero(page)) goto free_it; else { nr_reclaimed++; continue; } }
    • 매핑이 없으면서 페이지 참조 횟수가 1인 경우 0으로 변경하여 사용자가 없는 경우 free_it 레이블로 이동하여 페이지를 free 한다. 만일 아직 사용자가 있는 경우 nr_reclaimed 카운트를 증가하고 루프를 계속 수행한다.
  •  if (!mapping || !__remove_mapping(mapping, page, true)) goto keep_locked;
    • 매핑이 없거나 매핑을 제거하다 실패한 경우 다음에 처리하도록 page_list에 다시 추가한다.
  • __clear_page_locked(page);
    • 더 이상 사용자가 없는 페이지에 대해 lock 비트 플래그를 클리어한다.



                 * Is there need to periodically free_page_list? It would
                 * appear not as the counts should be low
                list_add(&page->lru, &free_pages);

                if (PageSwapCache(page))

                /* Not a candidate for swapping, so reclaim swap space. */
                if (PageSwapCache(page) && vm_swap_full())
                VM_BUG_ON_PAGE(PageActive(page), page);
                list_add(&page->lru, &ret_pages);
                VM_BUG_ON_PAGE(PageLRU(page) || PageUnevictable(page), page);

        free_hot_cold_page_list(&free_pages, true);

        list_splice(&ret_pages, page_list);
        count_vm_events(PGACTIVATE, pgactivate);

        *ret_nr_dirty += nr_dirty;
        *ret_nr_congested += nr_congested;
        *ret_nr_unqueued_dirty += nr_unqueued_dirty;
        *ret_nr_writeback += nr_writeback;
        *ret_nr_immediate += nr_immediate;
        return nr_reclaimed;
  • free_it: nr_reclaimed++; list_add(&page->lru, &free_pages); continue;
    • nr_reclaimed 카운터를 증가시키고 페이지를 free_pages 리스트에 추가하고 루프를 계속 수행한다.
      • 루프 완료 후 free_pages에 있는 페이지 항목들은 모두 버디 시스템으로 free될 예정이다.
  • cull_mlocked: if (PageSwapCache(page)) try_to_free_swap(page); unlock_page(page); putback_lru_page(page); continue;
    • 회수될 페이지가 아니라 판단되어 lruvec로 다시 되돌리고 루프를 계속 수행한다. 단 swapcache 페이지인 경우 swap 공간을 free하기 위해 시도한다.
  • activate_locked: if (PageSwapCache(page) && vm_swap_full()) try_to_free_swap(page); SetPageActive(page); pgactivate++;
    • swapcache 페이지이면서 swap 공간이 가득찬 경우 swap 공간을 free하기 위해 시도한다. 그런 후 페이지의 active 플래그 비트를 설정하고 pgactivate 카운터를 증가한다.
  • keep_locked: unlock_page(page);
    • 페이지 락을 클리어한다.
  • keep:  list_add(&page->lru, &ret_pages);
    • 페이지를 ret_pages에 추가한다.
  • mem_cgroup_uncharge_list(&free_pages);
    • memcg에 free_pages에 대한 uncharge 정보를 전달한다.
  • free_hot_cold_page_list(&free_pages, true);
    • free_pages 들을 버디 시스템에 cold 방향으로 free 시킨다.
  •  list_splice(&ret_pages, page_list);
    • ret_pages에 남은 페이지들을 page_list에 포함시킨다.


다음 그림은 스캔하여 isolation한 page_list를 대상으로 페이지를 회수하여 free page를 확보하는 흐름을 보여준다.





/* Check if a page is dirty or under writeback */
static void page_check_dirty_writeback(struct page *page,
                                       bool *dirty, bool *writeback)
        struct address_space *mapping;

         * Anonymous pages are not handled by flushers and must be written
         * from reclaim context. Do not stall reclaim based on them
        if (!page_is_file_cache(page)) {
                *dirty = false;
                *writeback = false;

        /* By default assume that the page flags are accurate */
        *dirty = PageDirty(page);
        *writeback = PageWriteback(page);

        /* Verify dirty/writeback state if the filesystem supports it */
        if (!page_has_private(page))

        mapping = page_mapping(page);
        if (mapping && mapping->a_ops->is_dirty_writeback)
                mapping->a_ops->is_dirty_writeback(page, dirty, writeback);

출력 인수에 dirty 및 writeback 여부를 알아온다.

  • if (!page_is_file_cache(page)) { *dirty = false; *writeback = false; return; }
    • file 캐시 페이지가 아닌 경우 출력 인수 dirty와 writeback에 false를 담고 함수를 종료한다.
  • *dirty = PageDirty(page);
    • dirty 페이지인 경우 출력 인수 dirty에 true를 대입한다.
  • *writeback = PageWriteback(page);
    • writeback 페이지인 경우 출력 인수 writeback에 true를 대입한다.
  • if (!page_has_private(page)) return;
    • private page가 아닌 경우 함수를 종료한다.
  • mapping = page_mapping(page); if (mapping && mapping->a_ops->is_dirty_writeback) mapping->a_ops->is_dirty_writeback(page, dirty, writeback);
    • mapping 페이지의 경우 is_dirty_writeback() 핸들러 함수를 통해 dirty 및 writeback 여부를 알아온다.




static enum page_references page_check_references(struct page *page,
                                                  struct scan_control *sc)
        int referenced_ptes, referenced_page;
        unsigned long vm_flags;

        referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup,
        referenced_page = TestClearPageReferenced(page);

         * Mlock lost the isolation race with us.  Let try_to_unmap()
         * move the page to the unevictable list.
        if (vm_flags & VM_LOCKED)
                return PAGEREF_RECLAIM;

        if (referenced_ptes) {
                if (PageSwapBacked(page))
                        return PAGEREF_ACTIVATE;
                 * All mapped pages start out with page table
                 * references from the instantiating fault, so we need
                 * to look twice if a mapped file page is used more
                 * than once.
                 * Mark it and spare it for another trip around the
                 * inactive list.  Another page table reference will
                 * lead to its activation.
                 * Note: the mark is set for activated pages as well
                 * so that recently deactivated but used pages are
                 * quickly recovered.

                if (referenced_page || referenced_ptes > 1)
                        return PAGEREF_ACTIVATE;

                 * Activate file-backed executable pages after first usage.
                if (vm_flags & VM_EXEC)
                        return PAGEREF_ACTIVATE;

                return PAGEREF_KEEP;

        /* Reclaim if clean, defer dirty pages to writeback */
        if (referenced_page && !PageSwapBacked(page))
                return PAGEREF_RECLAIM_CLEAN;

        return PAGEREF_RECLAIM;

페이지 참조를 확인하여 그 상태를 다음과 같이 4 가지로 알아온다.

    • 페이지 회수를 진행
    • 페이지 회수를 진행
    • 다음에 처리하게 유보
    • 페이지가 active 중이므로 다음에 처리하게 유보


  • referenced_ptes = page_referenced(page, 1, sc->target_mem_cgroup, &vm_flags);
    • 페이지 참조 횟수를 알아온다.
  • referenced_page = TestClearPageReferenced(page);
    • 참조 비트를 알아오고 페이지의 참조 비트는 클리어한다.
  • if (vm_flags & VM_LOCKED) return PAGEREF_RECLAIM;
    • VM_LOCKED가 설정된 경우 페이지 회수 중 상태로 반환한다.
  • if (referenced_ptes) { if (PageSwapBacked(page)) return PAGEREF_ACTIVATE;
    • 페이지 참조 중이면서 swapbacked 플래그 상태이면 활동중 상태로 반환한다.
  • SetPageReferenced(page);
    • 페이지의 참조 플래그를 설정한다.
  • if (referenced_page || referenced_ptes > 1) return PAGEREF_ACTIVATE;
    • 기존에 참조 비트가 설정된 상태였었거나 참조 횟수가 1보다 큰 경우 활동중 상태로 반환한다.
  • if (vm_flags & VM_EXEC)  return PAGEREF_ACTIVATE;
    • VM_EXEC 플래그가 설정된 경우 활동 중 상태로 반환한다.
  • return PAGEREF_KEEP; }
  • if (referenced_page && !PageSwapBacked(page)) return PAGEREF_RECLAIM_CLEAN;
    • 기존에 참조 비트가 설정되었으면서 swapbacked가 아닌 경우 RECLAIM_CLEAN 상태로 반환한다.
    • 회수 상태로 반환한다.




 * shrink_slab - shrink slab caches
 * @gfp_mask: allocation context
 * @nid: node whose slab caches to target
 * @memcg: memory cgroup whose slab caches to target
 * @nr_scanned: pressure numerator
 * @nr_eligible: pressure denominator
 * Call the shrink functions to age shrinkable caches.
 * @nid is passed along to shrinkers with SHRINKER_NUMA_AWARE set,
 * unaware shrinkers will receive a node id of 0 instead.
 * @memcg specifies the memory cgroup to target. If it is not NULL,
 * only shrinkers with SHRINKER_MEMCG_AWARE set will be called to scan
 * objects from the memory cgroup specified. Otherwise all shrinkers
 * are called, and memcg aware shrinkers are supposed to scan the
 * global list then.
 * @nr_scanned and @nr_eligible form a ratio that indicate how much of
 * the available objects should be scanned.  Page reclaim for example
 * passes the number of pages scanned and the number of pages on the
 * LRU lists that it considered on @nid, plus a bias in @nr_scanned
 * when it encountered mapped pages.  The ratio is further biased by
 * the ->seeks setting of the shrink function, which indicates the
 * cost to recreate an object relative to that of an LRU page.
 * Returns the number of reclaimed slab objects.
static unsigned long shrink_slab(gfp_t gfp_mask, int nid,
                                 struct mem_cgroup *memcg,
                                 unsigned long nr_scanned,
                                 unsigned long nr_eligible)
        struct shrinker *shrinker;
        unsigned long freed = 0;

        if (memcg && !memcg_kmem_is_active(memcg))
                return 0;

        if (nr_scanned == 0)
                nr_scanned = SWAP_CLUSTER_MAX;

        if (!down_read_trylock(&shrinker_rwsem)) {
                 * If we would return 0, our callers would understand that we
                 * have nothing else to shrink and give up trying. By returning
                 * 1 we keep it going and assume we'll be able to shrink next
                 * time.
                freed = 1;
                goto out;

        list_for_each_entry(shrinker, &shrinker_list, list) {
                struct shrink_control sc = {
                        .gfp_mask = gfp_mask,
                        .nid = nid,
                        .memcg = memcg,

                if (memcg && !(shrinker->flags & SHRINKER_MEMCG_AWARE))

                if (!(shrinker->flags & SHRINKER_NUMA_AWARE))
                        sc.nid = 0;

                freed += do_shrink_slab(&sc, shrinker, nr_scanned, nr_eligible);

        return freed;

slub 캐시를 shrink한다.

  • if (memcg && !memcg_kmem_is_active(memcg)) return 0;
    • memcg->kmem_acct_active가 0인 경우 처리를 중단한다.
  • if (nr_scanned == 0) nr_scanned = SWAP_CLUSTER_MAX;
    • nr_scanned가 0인 경우 SWAP_CLUSTER_MAX(128)로 대입한다.
  • if (!down_read_trylock(&shrinker_rwsem)) { freed = 1; goto out; }
    • read 세마포어락 획득을 시도하여 실패한 경우 1을 리턴한다.
  • list_for_each_entry(shrinker, &shrinker_list, list) {
    • 등록된 모든 shrink들 만큼 루프를 돈다.
  • if (memcg && !(shrinker->flags & SHRINKER_MEMCG_AWARE)) continue;
    • flag가 SHRINKER_MEMCG_AWARE가 없는 경우 skip
  • if (!(shrinker->flags & SHRINKER_NUMA_AWARE)) sc.nid = 0;
    • flag에 SHRINKER_NUMA_AWARE가 없는 경우 노드 id에 0을 대입한다.
  • freed += do_shrink_slab(&sc, shrinker, nr_scanned, nr_eligible);
    • shrink의 slub 캐시를 shrink하고 free된 object 수를 알아와서 freed에 더한다.
  • up_read(&shrinker_rwsem);
    • read 세마포어락을 release한다.




static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
                                    struct shrinker *shrinker,
                                    unsigned long nr_scanned,
                                    unsigned long nr_eligible)
        unsigned long freed = 0;
        unsigned long long delta;
        long total_scan;
        long freeable;
        long nr;
        long new_nr;
        int nid = shrinkctl->nid;
        long batch_size = shrinker->batch ? shrinker->batch
                                          : SHRINK_BATCH;

        freeable = shrinker->count_objects(shrinker, shrinkctl);
        if (freeable == 0)
                return 0;

         * copy the current shrinker scan count into a local variable
         * and zero it so that other concurrent shrinker invocations
         * don't also do this scanning work.
        nr = atomic_long_xchg(&shrinker->nr_deferred[nid], 0);

        total_scan = nr;
        delta = (4 * nr_scanned) / shrinker->seeks;
        delta *= freeable;
        do_div(delta, nr_eligible + 1);
        total_scan += delta;
        if (total_scan < 0) {
                pr_err("shrink_slab: %pF negative objects to delete nr=%ld\n",
                       shrinker->scan_objects, total_scan);
                total_scan = freeable;

         * We need to avoid excessive windup on filesystem shrinkers
         * due to large numbers of GFP_NOFS allocations causing the
         * shrinkers to return -1 all the time. This results in a large
         * nr being built up so when a shrink that can do some work
         * comes along it empties the entire cache due to nr >>>
         * freeable. This is bad for sustaining a working set in
         * memory.
         * Hence only allow the shrinker to scan the entire cache when
         * a large delta change is calculated directly.
        if (delta < freeable / 4)
                total_scan = min(total_scan, freeable / 2);

         * Avoid risking looping forever due to too large nr value:
         * never try to free more than twice the estimate number of
         * freeable entries.
        if (total_scan > freeable * 2)
                total_scan = freeable * 2;

        trace_mm_shrink_slab_start(shrinker, shrinkctl, nr,
                                   nr_scanned, nr_eligible,
                                   freeable, delta, total_scan);

slub 캐시를 shrink한다.


  • long batch_size = shrinker->batch ? shrinker->batch : SHRINK_BATCH;
    • 한 번에 처리할 slub object 갯수로 지정되지 않는 경우 SHRINK_BATCH(128) 개를 대입한다.
  • freeable = shrinker->count_objects(shrinker, shrinkctl); if (freeable == 0) return 0;
    • 지정된 shrinker의 slub object의 수를 알아오고 처리할 object가 없으면 처리를 중단한다.
  • nr = atomic_long_xchg(&shrinker->nr_deferred[nid], 0);
    • 현재 shrinker의 스캔 카운터를 0으로 설정하여 다른 곳과 동시에 호출되는 것을 막는다.
  • total_scan = nr; delta = (4 * nr_scanned) / shrinker->seeks; delta *= freeable; do_div(delta, nr_eligible + 1); total_scan += delta;
    • total_scan에 delta를 더해 교정한다.
  • if (delta < freeable / 4) total_scan = min(total_scan, freeable / 2);
    • delta가 freeable의 25%보다 작은 경우 total_scan 수가 freeable의 절반을 초과하지 않도록 제한한다.
  • if (total_scan > freeable * 2) total_scan = freeable * 2;
    • total_scan이 freeable의 두 배를 초과하지 않도록 한다.


         * Normally, we should not scan less than batch_size objects in one
         * pass to avoid too frequent shrinker calls, but if the slab has less
         * than batch_size objects in total and we are really tight on memory,
         * we will try to reclaim all available objects, otherwise we can end
         * up failing allocations although there are plenty of reclaimable
         * objects spread over several slabs with usage less than the
         * batch_size.
         * We detect the "tight on memory" situations by looking at the total
         * number of objects we want to scan (total_scan). If it is greater
         * than the total number of objects on slab (freeable), we must be
         * scanning at high prio and therefore should try to reclaim as much as
         * possible.
        while (total_scan >= batch_size ||
               total_scan >= freeable) {
                unsigned long ret;
                unsigned long nr_to_scan = min(batch_size, total_scan);

                shrinkctl->nr_to_scan = nr_to_scan;
                ret = shrinker->scan_objects(shrinker, shrinkctl);
                if (ret == SHRINK_STOP)
                freed += ret;

                count_vm_events(SLABS_SCANNED, nr_to_scan);
                total_scan -= nr_to_scan;


         * move the unused scan count back into the shrinker in a
         * manner that handles concurrent updates. If we exhausted the
         * scan, there is no need to do an update.
        if (total_scan > 0)
                new_nr = atomic_long_add_return(total_scan,
                new_nr = atomic_long_read(&shrinker->nr_deferred[nid]);

        trace_mm_shrink_slab_end(shrinker, nid, freed, nr, new_nr, total_scan);
        return freed;
  • while (total_scan >= batch_size || total_scan >= freeable) {
    • total_scan이 batch_size 또는 freeable 보다 큰 경우
  • unsigned long nr_to_scan = min(batch_size, total_scan);
    • 스캔할 건수로 batch_size와 total_scan보다 작은 수로 한다.
  • ret = shrinker->scan_objects(shrinker, shrinkctl); if (ret == SHRINK_STOP) break;
    • shrinker에 등록한 scan_objects() 핸들러 함수를 호출하여 object를 scan하고 free된 object 수를 알아온다. 만일 결과가 SHRINK_STOP인 경우 루프를 벗어난다.
  • freed += ret; total_scan -= nr_to_scan;
    • freed에 free된 object 수를 더하고, total_scan에서는 scan한 수만큼 감소시키고 루프를 계속 수행한다.
  • if (total_scan > 0) new_nr = atomic_long_add_return(total_scan, &shrinker->nr_deferred[nid]);
    • 남은 total_scan은 nr_deferred[]에 대입한다.



댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다