Re: [PATCH v1 4/6] mm: Introduce "aerated" pages

From: Dave Hansen
Date: Tue Jun 25 2019 - 15:45:47 EST


> +static inline void set_page_aerated(struct page *page,
> + struct zone *zone,
> + unsigned int order,
> + int migratetype)
> +{
> +#ifdef CONFIG_AERATION
> + /* update areated page accounting */
> + zone->free_area[order].nr_free_aerated++;
> +
> + /* record migratetype and flag page as aerated */
> + set_pcppage_migratetype(page, migratetype);
> + __SetPageAerated(page);
> +#endif
> +}

Please don't refer to code before you introduce it, even if you #ifdef
it. I went looking back in the series for the PageAerated() definition,
but didn't think to look forward.

Also, it doesn't make any sense to me that you would need to set the
migratetype here. Isn't it set earlier in the allocator? Also, when
can this function be called? There's obviously some locking in place
because of the __Set, but what are they?

> +static inline void clear_page_aerated(struct page *page,
> + struct zone *zone,
> + struct free_area *area)
> +{
> +#ifdef CONFIG_AERATION
> + if (likely(!PageAerated(page)))
> + return;

Logically, why would you ever clear_page_aerated() on a page that's not
aerated? Comments needed.

BTW, I already hate typing aerated. :)

> + __ClearPageAerated(page);
> + area->nr_free_aerated--;
> +#endif
> +}

More non-atomic flag clears. Still no comments.


> @@ -787,10 +790,10 @@ static inline void add_to_free_area(struct page *page, struct zone *zone,
> static inline void add_to_free_area_tail(struct page *page, struct zone *zone,
> unsigned int order, int migratetype)
> {
> - struct free_area *area = &zone->free_area[order];
> + struct list_head *tail = aerator_get_tail(zone, order, migratetype);

There is no logical change in this patch from this line. That's
unfortunate because I can't see the change in logic that's presumably
coming. You'll presumably change aerator_get_tail(), but then I'll have
to remember that this line is here and come back to it from a later patch.

If it *doesn't* change behavior, it has no business being calle
aerator_...().

This series seems rather suboptimal for reviewing.

> - list_add_tail(&page->lru, &area->free_list[migratetype]);
> - area->nr_free++;
> + list_add_tail(&page->lru, tail);
> + zone->free_area[order].nr_free++;
> }
>
> /* Used for pages which are on another list */
> @@ -799,6 +802,8 @@ static inline void move_to_free_area(struct page *page, struct zone *zone,
> {
> struct free_area *area = &zone->free_area[order];
>
> + clear_page_aerated(page, zone, area);
> +
> list_move(&page->lru, &area->free_list[migratetype]);
> }

It's not immediately clear to me why moving a page should clear
aeration. A comment would help make it clear.

> @@ -868,10 +869,11 @@ static inline struct capture_control *task_capc(struct zone *zone)
> static inline void __free_one_page(struct page *page,
> unsigned long pfn,
> struct zone *zone, unsigned int order,
> - int migratetype)
> + int migratetype, bool aerated)
> {
> struct capture_control *capc = task_capc(zone);
> unsigned long uninitialized_var(buddy_pfn);
> + bool fully_aerated = aerated;
> unsigned long combined_pfn;
> unsigned int max_order;
> struct page *buddy;
> @@ -902,6 +904,11 @@ static inline void __free_one_page(struct page *page,
> goto done_merging;
> if (!page_is_buddy(page, buddy, order))
> goto done_merging;
> +
> + /* assume buddy is not aerated */
> + if (aerated)
> + fully_aerated = false;

So, "full" vs. "partial" is with respect to high-order pages? Why not
just check the page flag on the buddy?

> /*
> * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
> * merge with it and move up one order.
> @@ -943,11 +950,17 @@ static inline void __free_one_page(struct page *page,
> done_merging:
> set_page_order(page, order);
>
> - if (buddy_merge_likely(pfn, buddy_pfn, page, order) ||
> + if (aerated ||
> + buddy_merge_likely(pfn, buddy_pfn, page, order) ||
> is_shuffle_tail_page(order))
> add_to_free_area_tail(page, zone, order, migratetype);
> else
> add_to_free_area(page, zone, order, migratetype);

Aerated pages always go to the tail? Ahh, so they don't get consumed
quickly and have to be undone? Comments, please.

> + if (fully_aerated)
> + set_page_aerated(page, zone, order, migratetype);
> + else
> + aerator_notify_free(zone, order);
> }

What is this notifying for? It's not like this is some opaque
registration interface. What does this *do*?

> @@ -2127,6 +2140,77 @@ struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
> return NULL;
> }
>
> +#ifdef CONFIG_AERATION
> +/**
> + * get_aeration_page - Provide a "raw" page for aeration by the aerator
> + * @zone: Zone to draw pages from
> + * @order: Order to draw pages from
> + * @migratetype: Migratetype to draw pages from

FWIW, kerneldoc is a waste of bytes here. Please use it sparingly.

> + * This function will obtain a page from above the boundary. As a result
> + * we can guarantee the page has not been aerated.

This is the first mention of a boundary. That's not good since I have
no idea at this point what the boundary is for or between.


> + * The page will have the migrate type and order stored in the page
> + * metadata.
> + *
> + * Return: page pointer if raw page found, otherwise NULL
> + */
> +struct page *get_aeration_page(struct zone *zone, unsigned int order,
> + int migratetype)
> +{
> + struct free_area *area = &(zone->free_area[order]);
> + struct list_head *list = &area->free_list[migratetype];
> + struct page *page;
> +
> + /* Find a page of the appropriate size in the preferred list */

I don't get the size comment. Hasn't this already been given an order?

> + page = list_last_entry(aerator_get_tail(zone, order, migratetype),
> + struct page, lru);
> + list_for_each_entry_from_reverse(page, list, lru) {
> + if (PageAerated(page)) {
> + page = list_first_entry(list, struct page, lru);
> + if (PageAerated(page))
> + break;
> + }

This confuses me. It looks for a page, then goes to the next page and
checks again? Why check twice? Why is a function looking for an
aerated page that finds *two* pages returning NULL?

I'm stumped.

> + del_page_from_free_area(page, zone, order);
> +
> + /* record migratetype and order within page */
> + set_pcppage_migratetype(page, migratetype);
> + set_page_private(page, order);
> + __mod_zone_freepage_state(zone, -(1 << order), migratetype);
> +
> + return page;
> + }
> +
> + return NULL;
> +}

Oh, so this is trying to find a page _for_ aerating.
"get_aeration_page()" does not convey that. Can that improved?
get_page_for_aeration()?

Rather than talk about boundaries, wouldn't a better description have been:

Similar to allocation, this function removes a page from the
free lists. However, it only removes unaerated pages.

> +/**
> + * put_aeration_page - Return a now-aerated "raw" page back where we got it
> + * @zone: Zone to return pages to
> + * @page: Previously "raw" page that can now be returned after aeration
> + *
> + * This function will pull the migratetype and order information out
> + * of the page and attempt to return it where it found it.
> + */
> +void put_aeration_page(struct zone *zone, struct page *page)
> +{
> + unsigned int order, mt;
> + unsigned long pfn;
> +
> + mt = get_pcppage_migratetype(page);
> + pfn = page_to_pfn(page);
> +
> + if (unlikely(has_isolate_pageblock(zone) || is_migrate_isolate(mt)))
> + mt = get_pfnblock_migratetype(page, pfn);
> +
> + order = page_private(page);
> + set_page_private(page, 0);
> +
> + __free_one_page(page, pfn, zone, order, mt, true);
> +}
> +#endif /* CONFIG_AERATION */

Yikes. This seems to have glossed over some pretty big aspects here.
Pages which are being aerated are not free. Pages which are freed are
diverted to be aerated before becoming free. Right? That sounds like
two really important things to add to a changelog.

> /*
> * This array describes the order lists are fallen back to when
> * the free lists for the desirable migrate type are depleted
> @@ -5929,9 +6013,12 @@ void __ref memmap_init_zone_device(struct zone *zone,
> static void __meminit zone_init_free_lists(struct zone *zone)
> {
> unsigned int order, t;
> - for_each_migratetype_order(order, t) {
> + for_each_migratetype_order(order, t)
> INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
> +
> + for (order = MAX_ORDER; order--; ) {
> zone->free_area[order].nr_free = 0;
> + zone->free_area[order].nr_free_aerated = 0;
> }
> }
>
>