page_alloc_init()

cpu_chain에 cpu  up, cpu down, … 등이 발생할 때 통지되는 콜백함수를 등록한다.

page_alloc_init-1

page_alloc_init()

mm/page_alloc.c

void __init page_alloc_init(void)
{
        hotcpu_notifier(page_alloc_cpu_notify, 0);
}
  • cpu_chain에 page_alloc_cpu_notify() 콜백 함수를 등록하여 cpu_notify() 또는 __cpu_notify() 함수에 이해 호출된다.
  • 참고: hotcpu_notifier() | 문c

 

page_alloc_cpu_notify()

page_alloc_cpu_notify-1b

page_alloc_cpu_notify-2a

mm/page_alloc.c

static int page_alloc_cpu_notify(struct notifier_block *self,
                                 unsigned long action, void *hcpu)
{
        int cpu = (unsigned long)hcpu;

        if (action == CPU_DEAD || action == CPU_DEAD_FROZEN) {
                lru_add_drain_cpu(cpu);
                drain_pages(cpu);

                /*
                 * Spill the event counters of the dead processor
                 * into the current processors event counters.
                 * This artificially elevates the count of the current
                 * processor.
                 */
                vm_events_fold_cpu(cpu);

                /*
                 * Zero the differential counters of the dead processor
                 * so that the vm statistics are consistent.
                 *
                 * This is only okay since the processor is dead and cannot
                 * race with what we are doing.
                 */
                cpu_vm_stats_fold(cpu);
        }
        return NOTIFY_OK;
}

CPU_DEAD 또는 CPU_DEAD_FROZEN 액션이 호출된 경우 다운된 cpu가 사용하던 4개의 cpu 페이지 캐시(pagevec)에서 페이지를 회수하여 해당 zone(또는 memory cgroup의 zone)에 있는 lruvec로 이전한다. 그리고 다운된 cpu가 할당받아 두었던 Per-Cpu Page Frame Cache 페이지를 해지한다.

  • if (action == CPU_DEAD || action == CPU_DEAD_FROZEN) {
    • CPU_DEAD 또는 CPU_DEAD_FROZEN action이 인입되면
  • lru_add_drain_cpu(cpu);
    • cpu가 다운되었으니 다운된 cpu가 사용하던 4개의 cpu 페이지 캐시(pagevec)에서페이지를 회수하여 해당 zone(또는 memory cgroup의 zone)에 있는 lruvec로 이전한다.
  • drain_pages(cpu);
    • 다운된 cpu가 할당 받아 두었던 Per-Cpu Page Frame Cache 페이지를 해지한다. (zone->pageset)
  •  vm_events_fold_cpu(cpu);
    • 다운된 cpu의 event 카운터 현재 cpu로 옮기고 clear 한다.
  • cpu_vm_stats_fold(cpu);
    • 다운된 cpu의 전체 pageset event를 zone 및 전역에 옮기고 clear 한다.

 

lru_add_drain_cpu()

mm/swap.c

/*
 * Drain pages out of the cpu's pagevecs.
 * Either "cpu" is the current CPU, and preemption has already been
 * disabled; or "cpu" is being hot-unplugged, and is already dead.
 */
void lru_add_drain_cpu(int cpu)
{
        struct pagevec *pvec = &per_cpu(lru_add_pvec, cpu);

        if (pagevec_count(pvec))
                __pagevec_lru_add(pvec);

        pvec = &per_cpu(lru_rotate_pvecs, cpu);
        if (pagevec_count(pvec)) {
                unsigned long flags;

                /* No harm done if a racing interrupt already did this */
                local_irq_save(flags);
                pagevec_move_tail(pvec);
                local_irq_restore(flags); 
        }
                                      
        pvec = &per_cpu(lru_deactivate_pvecs, cpu);
        if (pagevec_count(pvec))                  
                pagevec_lru_move_fn(pvec, lru_deactivate_fn, NULL);

        activate_page_drain(cpu);               
}

지정된 cpu가 사용하던 4개의 cpu 페이지 캐시 lru_add_pvec, lru_rotate_pvecs, lru_deactivate_pvecs, activate_page_pvecs 에서 페이지를 회수하여 해당 zone(또는 memory cgroup의 zone)에 있는 lruvec로 이전한다

  • if (pagevec_count(pvec)) __pagevec_lru_add(pvec);
    • 지정된 cpu 캐시 lru_add_pvec에 등록된 페이지를 해당 페이지의 zone(또는 memory cgroup의 zone)->lruvec로 이전하고 lru_add_pvec를 비우고 초기화한다.
  • if (pagevec_count(pvec)) { pagevec_move_tail(pvec);
    • 지정된 cpu 캐시 lru_rotate_pvecs에 등록된 페이지를 해당 페이지의 zone(또는 memory cgroup의 zone)->lruvec의 마지막에 추가하고 lru_rotate_pvecs를 비우고 초기화한다.
  • if (pagevec_count(pvec)) pagevec_lru_move_fn(pvec, lru_deactivate_fn, NULL);
    • 지정된 cpu 캐시 lru_deactivate_pvecs에 등록된 페이지를 해당 페이지의 zone(또는 memory cgroup의 zone)->lruvec에서 검색하여 삭제하고 lru_deactivate_pvecs를 비우고 초기화한다.
  • activate_page_drain(cpu);
    • 지정된 cpu 캐시 activate_page_pvecs에 등록된 페이지를 해당 페이지의 zone(또는 memory cgroup의 zone)->lruvec로 이전하고 activate_page_pvecs를 비우고 초기화한다.

 

__pagevec_lru_add()

mm/swap.c

/*
 * Add the passed pages to the LRU, then drop the caller's refcount
 * on them.  Reinitialises the caller's pagevec.
 */
void __pagevec_lru_add(struct pagevec *pvec)
{
        pagevec_lru_move_fn(pvec, __pagevec_lru_add_fn, NULL);
}
EXPORT_SYMBOL(__pagevec_lru_add);
  • cpu 캐시 pagevec에 등록된 페이지를 해당 페이지의 zone(또는 memory cgroup의 zone)->lruvec로 이전하고 pagevec를 비우고 초기화한다.

 

pagevec_lru_move_fn()

mm/swap.c

static void pagevec_lru_move_fn(struct pagevec *pvec,
        void (*move_fn)(struct page *page, struct lruvec *lruvec, void *arg),
        void *arg)
{
        int i;
        struct zone *zone = NULL;
        struct lruvec *lruvec;
        unsigned long flags = 0;

        for (i = 0; i < pagevec_count(pvec); i++) {
                struct page *page = pvec->pages[i];
                struct zone *pagezone = page_zone(page);

                if (pagezone != zone) {
                        if (zone)
                                spin_unlock_irqrestore(&zone->lru_lock, flags);
                        zone = pagezone;
                        spin_lock_irqsave(&zone->lru_lock, flags);
                }

                lruvec = mem_cgroup_page_lruvec(page, zone);
                (*move_fn)(page, lruvec, arg);
        }
        if (zone)
                spin_unlock_irqrestore(&zone->lru_lock, flags);
        release_pages(pvec->pages, pvec->nr, pvec->cold);
        pagevec_reinit(pvec);
}

pagevec에 등록된 페이지를 해당 페이지의 memory control group의 lruvec로 이전하고 pagevec를 비우고 초기화한다.

  • for (i = 0; i < pagevec_count(pvec); i++) {
    • pagevec 수 만큼 루프를 돈다.
  • struct page *page = pvec->pages[i];
    • 해당 페이지
  • struct zone *pagezone = page_zone(page);
    • 해당 페이지의 zone
  • if (pagezone != zone) {
    • zone이 바뀐 경우
  • if (zone) spin_unlock_irqrestore(&zone->lru_lock, flags);
    • zone이 설정된 경우 spin unlock을 수행한다.
  • zone = pagezone;
    • zone을 갱신하고 spin lock을 수행한다.
  • lruvec = mem_cgroup_page_lruvec(page, zone);
    • page 정보로 memory cgroup의 lruvec를 알아온다.
    • 참고: CGroup for Memory | 문c
  • (*move_fn)(page, lruvec, arg);
    • move_fn 인수에 지정된 함수를 호출한다.
    • 예) __pagevec_lru_add_fn()
      • pagevec의 페이지를 lruvec에 추가한다.
  • if (zone) spin_unlock_irqrestore(&zone->lru_lock, flags);
    • zone이 설정된 경우 spin unlock을 수행한다.
  • release_pages(pvec->pages, pvec->nr, pvec->cold);
    • pagevec의 페이지들을 해지한다.
  • pagevec_reinit(pvec);
    • pagevec을 다시 초기화한다.

 

__pagevec_lru_add_fn()

mm/swap.c

static void __pagevec_lru_add_fn(struct page *page, struct lruvec *lruvec,
                                 void *arg)
{
        int file = page_is_file_cache(page);
        int active = PageActive(page);
        enum lru_list lru = page_lru(page);

        VM_BUG_ON_PAGE(PageLRU(page), page);

        SetPageLRU(page);
        add_page_to_lru_list(page, lruvec, lru);
        update_page_reclaim_stat(lruvec, file, active);
        trace_mm_lru_insertion(page, lru);
}

lruvec에 page를 추가한다. 페이지에는 LRU 플래그 비트가 설정된다.

  • int file = page_is_file_cache(page);
    • 정규 화일로 부터 캐시된 페이지인지 여부를 알아온다.
  • int active = PageActive(page);
    • 페이지가 Active 상태인지 여부를 알아온다.
  • enum lru_list lru = page_lru(page);
    • 페이지에 대한 lru 타입을 알아온다.
  • SetPageLRU(page);
    • 페이지에 LRU 플래그를 설정한다.
  • add_page_to_lru_list(page, lruvec, lru);
    • lruvec에 페이지를 추가한다.
  • update_page_reclaim_stat(lruvec, file, active);
    • reclaim 관련 scanned[]와 rocated[] 항목을 증가시킨다

 

page_is_file_cache()

include/linux/mm_inline.h

/**
 * page_is_file_cache - should the page be on a file LRU or anon LRU?
 * @page: the page to test
 *
 * Returns 1 if @page is page cache page backed by a regular filesystem,
 * or 0 if @page is anonymous, tmpfs or otherwise ram or swap backed.
 * Used by functions that manipulate the LRU lists, to sort a page
 * onto the right LRU list.
 *
 * We would like to get this info without a page flag, but the state
 * needs to survive until the page is last deleted from the LRU, which
 * could be as far down as __page_cache_release.
 */
static inline int page_is_file_cache(struct page *page)
{
        return !PageSwapBacked(page);
}

정규 화일이 캐시된 페이지인지 여부를 알아온다.

  • 1: 정규 화일시스템의 페이지 캐시된 페이지
  • 0: tmpfs, ram 또는 swap backed

 

  • return !PageSwapBacked(page)
    • 페이지에 PG_swapbacked 비트 플래그가 설정되어 있지 않으면 1, 그렇지 않으면 0을 리턴한다.

 

page_lru()

include/linux/mm_inline.h

/**     
 * page_lru - which LRU list should a page be on?
 * @page: the page to test
 *      
 * Returns the LRU list a page should be on, as an index
 * into the array of LRU lists.
 */
static __always_inline enum lru_list page_lru(struct page *page)
{
        enum lru_list lru;

        if (PageUnevictable(page))
                lru = LRU_UNEVICTABLE;
        else {
                lru = page_lru_base_type(page);
                if (PageActive(page))
                        lru += LRU_ACTIVE;
        }
        return lru;
}

페이지에 대한 lru(5가지 상태) 값을 알아온다.

  • if (PageUnevictable(page)) lru = LRU_UNEVICTABLE;
    • 페이지가 unevictable 플래그를 가졌으면 LRU_UNEVICTABLE(4)을 리턴한다.
  • else { lru = page_lru_base_type(page);
    • 그렇지 않으면 페이지가 화일을 캐시한 타입인 경우 LRU_INACTIVE_FILE(2)을 그렇지 않은 경우 LRU_INACTIVE_ANON(0)을 알아온다.
  • if (PageActive(page)) { __ClearPageActive(page); lru += LRU_ACTIVE; }
  • 페이지가 active 상태인 경우 clear하고 lru에 LRU_ACTIVE(1)를 추가한다.
    • LRU_INACTIVE_FILE(2) -> LRU_ACTIVE_FILE(3)
    • LRU_INACTIVE_ANON(0) -> LRU_ACTIVE_ANON(1)

 

add_page_to_lru_list()

include/linux/mm_inline.h

static __always_inline void add_page_to_lru_list(struct page *page,
                                struct lruvec *lruvec, enum lru_list lru)
{       
        int nr_pages = hpage_nr_pages(page);
        mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
        list_add(&page->lru, &lruvec->lists[lru]);
        __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages);
}
  • int nr_pages = hpage_nr_pages(page);
    • 페이지가 huge 페이지인 경우 작은 페이지 수를 알아온다. 아닌 경우는 1이다.
      • huge 페이지가 2MB인 경우 -> 512개
  • mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
    • 메모리 cgroup의 lru_size[lru]에 페이지 수를 추가한다.
  • list_add(&page->lru, &lruvec->lists[lru]);
    • lru의 타입별 리스트에 페이지를 선두에 추가한다.
      • 선두에 추가한다는 의미는 사용빈도가 높은 hot page를 의미한다.
  • __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages);
    • zone의 vm_stat[]과 전역 vm_stat[]에 nr_pages를 추가한다.

 

mem_cgroup_update_lru_size()

mm/memcontrol.c

/**
 * 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())
                return;         

        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);
}

메모리 cgroup의 lru_size[lru]에 페이지 수를 추가한다.

 

update_page_reclaim_stat()

mm/swap.c

static void update_page_reclaim_stat(struct lruvec *lruvec,
                                     int file, int rotated)
{
        struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat;

        reclaim_stat->recent_scanned[file]++;
        if (rotated)
                reclaim_stat->recent_rotated[file]++;
}

reclaim 관련 scanned[]와 rocated[] 항목을 증가시킨다. 두 항목은 각각 2개의 배열을 사용하는데 각각의 배열은 다음과 같다.

  • [0]: anon LRU stat
  • [1]: file LRU stat

 

  •  reclaim_stat->recent_scanned[file]++;
    • lru의  reclaim_stat 에서 recent_scanned[]를 증가시킨다.
  • if (rotated) reclaim_stat->recent_rotated[file]++;
    • 인수 rotated가 설정된 경우 지정된 lru의 reclaim_stat 에서 recent_rotated[]를 증가시킨다.

 

pagevec_move_tail()

mm/swap.c

/*
 * pagevec_move_tail() must be called with IRQ disabled.
 * Otherwise this may cause nasty races.
 */
static void pagevec_move_tail(struct pagevec *pvec)
{
        int pgmoved = 0;

        pagevec_lru_move_fn(pvec, pagevec_move_tail_fn, &pgmoved);
        __count_vm_events(PGROTATED, pgmoved);
}

pagevec에 등록된 페이지들을 해당 페이지의 memory control group의 lru의 타입별 리스트의 후미에 추가하고 pagevec를 비우고 초기화한다. 추가한 페이지들의 수를 vm_events 관련 pgmoved 항목에 더한다.

 

pagevec_move_tail_fn()

mm/swap.c

static void pagevec_move_tail_fn(struct page *page, struct lruvec *lruvec,
                                 void *arg)
{
        int *pgmoved = arg;

        if (PageLRU(page) && !PageActive(page) && !PageUnevictable(page)) {
                enum lru_list lru = page_lru_base_type(page);
                list_move_tail(&page->lru, &lruvec->lists[lru]);
                (*pgmoved)++;
        }
}

페이지가 LRU_INACTIVATE_ANON 및 LRU_INACTIVATE_FILE 인 경우 lru의 타입별 리스트의 후미(cold)에 페이지를 추가한다.

  • if (PageLRU(page) && !PageActive(page) && !PageUnevictable(page)) {
    • 페이지가 LRU 플래그 설정되어 있고 inactive면서 unevitable 플래그 상태이면
  • enum lru_list lru = page_lru_base_type(page);
    • 페이지에서 lru 타입을 알아온다.
  • list_move_tail(&page->lru, &lruvec->lists[lru]);
    • lru의 타입별 리스트의 후미에 페이지를 추가한다.
  • (*pgmoved)++;
    • 인수 pgmoved 카운터를 증가시킨다.

 

lru_deactivate_fn()

mm/swap.c

/*
 * If the page can not be invalidated, it is moved to the
 * inactive list to speed up its reclaim.  It is moved to the
 * head of the list, rather than the tail, to give the flusher
 * threads some time to write it out, as this is much more
 * effective than the single-page writeout from reclaim.
 *
 * If the page isn't page_mapped and dirty/writeback, the page
 * could reclaim asap using PG_reclaim.
 *
 * 1. active, mapped page -> none
 * 2. active, dirty/writeback page -> inactive, head, PG_reclaim
 * 3. inactive, mapped page -> none
 * 4. inactive, dirty/writeback page -> inactive, head, PG_reclaim
 * 5. inactive, clean -> inactive, tail
 * 6. Others -> none
 *
 * In 4, why it moves inactive's head, the VM expects the page would
 * be write it out by flusher threads as this is much more effective
 * than the single-page writeout from reclaim.
 */
static void lru_deactivate_fn(struct page *page, struct lruvec *lruvec,
                              void *arg)
{
        int lru, file;
        bool active;
        
        if (!PageLRU(page))
                return;
        
        if (PageUnevictable(page))
                return;
        
        /* Some processes are using the page */
        if (page_mapped(page))
                return;

        active = PageActive(page);
        file = page_is_file_cache(page);
        lru = page_lru_base_type(page);
        
        del_page_from_lru_list(page, lruvec, lru + active);
        ClearPageActive(page);
        ClearPageReferenced(page); 
        add_page_to_lru_list(page, lruvec, lru);

        if (PageWriteback(page) || PageDirty(page)) {
                /*
                 * PG_reclaim could be raced with end_page_writeback
                 * It can make readahead confusing.  But race window
                 * is _really_ small and  it's non-critical problem.
                 */
                SetPageReclaim(page);
        } else {
                /*
                 * The page's writeback ends up during pagevec
                 * We moves tha page into tail of inactive.
                 */
                list_move_tail(&page->lru, &lruvec->lists[lru]);
                __count_vm_event(PGROTATED);
        }

        if (active)
                __count_vm_event(PGDEACTIVATE);
        update_page_reclaim_stat(lruvec, file, 0);
}

페이지가 LRU 타입이면서 unevictable이 아니고 mapped file이 아닌 경우 lru의 타입별 리스트에서 페이지를 삭제한 후 lru의 기본 타입의 선두에 페이지를 추가한다. 페이지 플래그는 active 및 referenced 플래그를 삭제한다. 페이지에 기록 속성이 있는 경우 reclaim 플래그를 설정하고 그렇지 않은 경우 리스트의 후미로 이동시킨다.

  • if (!PageLRU(page)) return;
    • 페이지에 LRU 플래그가 설정되어 있지 않은 경우 더 이상 진행하지 않고 빠져나간다.
  • if (PageUnevictable(page)) return;
    • 페이지에 Unevitable 플래그가 설정되어 있는 경우 더 이상 진행하지 않고 빠져나간다.
  • if (page_mapped(page)) return;
    • 페이지가 프로세스에서 사용 중인 경우 더 이상 진행하지 않고 빠져나간다.
  • active = PageActive(page);
    • 페이지가 active 플래그 상태를 가지고 있는지 여부를 알아온다.
  • file = page_is_file_cache(page);
    • 페이지가 file로 부터 캐시되어 있는지 여부를 알아온다.
  • lru = page_lru_base_type(page);
    • 페이지로부터 lru 타입을 알아온다.
  • del_page_from_lru_list(page, lruvec, lru + active);
    • lru + active 배열의 lru 리스트에서 페이지를 찾아 삭제한다.
  • ClearPageActive(page);
    • 페이지에서 Active 플래그를 삭제한다.
  • ClearPageReferenced(page);
    • 페이지에서 Referenced 플래그를 삭제한다.
  • add_page_to_lru_list(page, lruvec, lru);
    • lru 타입 배열의 lru 리스트에 페이지를 추가한다.
  • if (PageWriteback(page) || PageDirty(page)) { SetPageReclaim(page);
    • 페이지에 Writeback 또는 Dirty가 설정된 경우Reclaim 플래그를 설정해 놓는다.
  • } else { list_move_tail(&page->lru, &lruvec->lists[lru]); __count_vm_event(PGROTATED); }
    • 아니면 lru 타입 배열의 lru 리스트의 후미에 페이지를 추가한다.
      • 후미에 추가하는 경우 cold 페이지로 최빈도로 사용됨을 나타낸다.
  • if (active) __count_vm_event(PGDEACTIVATE);
    • active인 경우 PGDEACTIVATE 항목의 vm_event 를 증가시킨다.
  • update_page_reclaim_stat(lruvec, file, 0);
    • reclaim 관련 scanned[]와 rocated[] 항목을 증가시킨다

 

activate_page_drain()

mm/swap.c

static void activate_page_drain(int cpu)
{
        struct pagevec *pvec = &per_cpu(activate_page_pvecs, cpu);

        if (pagevec_count(pvec))
                pagevec_lru_move_fn(pvec, __activate_page, NULL);
}

activate_page_pvecs 라는 cpu 캐시 리스트에 등록된 페이지들을 해당 페이지의 memory control group의 lru의 타입별 리스트에서 삭제했다가 lru의 타입 + active를 하여 다시 선두(hot)에 추가하고 active 플래그를 설정하며 vm_events 관련 PGACTIVATE 항목을 증가시키고 reclaim 관련 통계도 증가시킨다. 그런 후 pagevec를 비우고 초기화한다. .

 

__activate_page()

mm/swap.c

static void __activate_page(struct page *page, struct lruvec *lruvec,
                            void *arg)
{
        if (PageLRU(page) && !PageActive(page) && !PageUnevictable(page)) {
                int file = page_is_file_cache(page);
                int lru = page_lru_base_type(page);

                del_page_from_lru_list(page, lruvec, lru);
                SetPageActive(page);
                lru += LRU_ACTIVE;
                add_page_to_lru_list(page, lruvec, lru);
                trace_mm_lru_activate(page);

                __count_vm_event(PGACTIVATE);
                update_page_reclaim_stat(lruvec, file, 1);
        }
}

페이지를 lruvec->lists[basic type]에서 삭제한 후 active 플래그를 설정하고 lruvec->lists[lru+active]의 선두(hot)에 추가한다.

  • if (PageLRU(page) && !PageActive(page) && !PageUnevictable(page)) {
    • 페이지가 LRU 설정되어 있고, inactive 이면서 unevictable 플래그 설정이 없는 경우
      • LRU_INACTIVE_ANON 또는 LRU_INACTIVE_FILE
  • int file = page_is_file_cache(page);
    • 페이지가 file로 부터 캐시되어 있는지 여부를 알아온다.
  • int lru = page_lru_base_type(page);
    • 페이지로부터 lru 타입을 알아온다.
  • del_page_from_lru_list(page, lruvec, lru);
    • lru 타입 배열의 lru 리스트에서 페이지를 찾아 삭제한다.
  • SetPageActive(page);
    • 페이지를 active 설정한다.
  • lru += LRU_ACTIVE; add_page_to_lru_list(page, lruvec, lru);
    • lru + active 타입 배열의 lru 리스트의 선두에 페이지를 추가한다.
  • __count_vm_event(PGACTIVATE);
    • vm_event의 PGACTIVATE 항목의 카운터를 증가시킨다.
  • update_page_reclaim_stat(lruvec, file, 1);
    • reclaim 관련 scanned[]와 rocated[] 항목을 증가시킨다

 

drain_pages()

mm/page_alloc.c

/*
 * Drain pcplists of all zones on the indicated processor.
 *
 * The processor must either be the current processor and the
 * thread pinned to the current processor or a processor that
 * is not online.
 */
static void drain_pages(unsigned int cpu)
{
        struct zone *zone;

        for_each_populated_zone(zone) {
                drain_pages_zone(cpu, zone);
        }
}

활성화된 zone 모두에 대해 Per-Cpu Page Fram Cache를 비운다.

 

drain_pages_zone()

mm/page_alloc.c

/*
 * Drain pcplists of the indicated processor and zone.
 *
 * The processor must either be the current processor and the
 * thread pinned to the current processor or a processor that
 * is not online.
 */
static void drain_pages_zone(unsigned int cpu, struct zone *zone)
{
        unsigned long flags;
        struct per_cpu_pageset *pset;
        struct per_cpu_pages *pcp;

        local_irq_save(flags);
        pset = per_cpu_ptr(zone->pageset, cpu);

        pcp = &pset->pcp;
        if (pcp->count) {
                free_pcppages_bulk(zone, pcp->count, pcp);
                pcp->count = 0;
        }
        local_irq_restore(flags);
}

요청 zone에 대한 Per-Cpu Page Fram Cache에 등록된 페이지들 모두 buddy 시스템으로 이주시킨다.

 

vm_events_fold_cpu()

mm/vmstat.c

/*
 * Fold the foreign cpu events into our own.
 *
 * This is adding to the events on one processor
 * but keeps the global counts constant. 
 */
void vm_events_fold_cpu(int cpu)
{
        struct vm_event_state *fold_state = &per_cpu(vm_event_states, cpu);
        int i;

        for (i = 0; i < NR_VM_EVENT_ITEMS; i++) {
                count_vm_events(i, fold_state->event[i]);
                fold_state->event[i] = 0;
        }
}

fold될 cpu에 대한 event[] 카운터들을 현재의 cpu event[] 카운터에 더한 후 fold될 cpu에 대한 event[]는 모두 clear 한다.

 

cpu_vm_stats_fold()

mm/vmstat.c

/*
 * Fold the data for an offline cpu into the global array.
 * There cannot be any access by the offline cpu and therefore
 * synchronization is simplified.
 */
void cpu_vm_stats_fold(int cpu)
{
        struct zone *zone;
        int i;
        int global_diff[NR_VM_ZONE_STAT_ITEMS] = { 0, };

        for_each_populated_zone(zone) {
                struct per_cpu_pageset *p;

                p = per_cpu_ptr(zone->pageset, cpu);
        
                for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
                        if (p->vm_stat_diff[i]) {
                                int v;

                                v = p->vm_stat_diff[i];
                                p->vm_stat_diff[i] = 0;
                                atomic_long_add(v, &zone->vm_stat[i]);
                                global_diff[i] += v;
                        }
        }
                        
        fold_diff(global_diff);
}

fold될 cpu에 대한 vm_stat_diff[] 카운터들을 zone->vm_stat[] 카운터에 더하고 전역 vm_stat[]에도 도한 후 fold될 cpu에 대한 카운터는 모두 clear 한다.

 

fold_diff()

mm/vmstat.c

/*
 * Fold a differential into the global counters.
 * Returns the number of counters updated.
 */
static int fold_diff(int *diff)
{
        int i;
        int changes = 0;

        for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
                if (diff[i]) {
                        atomic_long_add(diff[i], &vm_stat[i]);
                        changes++;
        }
        return changes;
}

인수로 전달 받은 vm_stat 값을 전역 vm_stat[]에 더하고 변경 된 항목 수가 리턴된다.

 

참고

답글 남기기

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