Re: fs/proc/task_mmu: Implement IOCTL for efficient page table scanning

From: Michał Mirosław
Date: Fri Jul 21 2023 - 07:24:10 EST


On Fri, Jul 21, 2023 at 03:48:22PM +0500, Muhammad Usama Anjum wrote:
> On 7/21/23 12:28 AM, Michał Mirosław wrote:
> > This is a massaged version of patch by Muhammad Usama Anjum [1]
> > to illustrate my review comments and hopefully push the implementation
> > efforts closer to conclusion. The changes are:
> Thank you so much for this effort. I also want to reach conclusion. I'll
> agree with all the changes which don't affect me. But some requirements
> aren't being fulfilled with this current design.
>
> >
> > 1. the API:
[...]
> > b. rename match "flags" to 'page categories' everywhere - this makes
> > it easier to differentiate the ioctl()s categorisation of pages
> > from struct page flags;
> I've no problem with it.
>
> #define PAGE_IS_WPASYNC (1 << 0)
> #define PAGE_IS_WRITTEN (1 << 1)
> You have another new flag PAGE_IS_WPASYNC. But there is no application of
> PAGE_IS_WPASYNC. We must not add a flag which don't have any user.

Please see below.

> > c. change {required + excluded} to {inverted + required}. This was
> > rejected before, but I'd like to illustrate the difference.
> > Old interface can be translated to the new by:
> > categories_inverted = excluded_mask
> > categories_mask = required_mask | excluded_mask
> > categories_anyof_mask = anyof_mask
> > The new way allows filtering by: A & (B | !C)
> > categories_inverted = C
> > categories_mask = A
> > categories_anyof_mask = B | C
> I'm still unable to get the idea of inverted masks. IIRC Andei had also not
> supported/accepted this masking scheme. But I'll be okay with it if he
> supports this masking.

Please note that the masks are not inverted -- the values are. Masks
select which categories you want to filter on, and category_inverted
invert the meaning of a match (match 0 instead of 1).

> > d. change the ioctl to be a SCAN with optional WP. Addressing the
> > original use-case, GetWriteWatch() can be implemented as:
> As I've mentioned several times previously (without the name of
> ResetWriteWatch()) that we need exclusive WP without GET. This could be
> implemented with UFFDIO_WRITEPROTECT. But when we use UFFDIO_WRITEPROTECT,
> we hit some special case and performance is very slow. So with UFFD WP
> expert, Peter Xu we have decided to put exclusive WP in this IOCTL for
> implementation of ResetWriteWatch().
>
> A lot of simplification of the patch is made possible because of not
> keeping exclusive WP. (You have also written some quality code, more better.)
> >
> > memset(&args, 0, sizeof(args));
> > args.start = lpBaseAddress;
> > args.end = lpBaseAddress + dwRegionSize;
> > args.max_pages = *lpdwCount;
> > *lpdwGranularity = PAGE_SIZE;
> > args.flags = PM_SCAN_CHECK_WPASYNC;
> > if (dwFlags & WRITE_WATCH_FLAG_RESET)
> > args.flags |= PM_SCAN_WP_MATCHED;
> > args.categories_mask = PAGE_IS_WRITTEN;
> > args.return_mask = PAGE_IS_WRITTEN;

For ResetWriteWatch() you would:

args.flags = PM_SCAN_WP_MATCHING;
args.categories_mask = PAGE_IS_WPASYNC | PAGE_IS_WRITTEN;
args.return_mask = 0;

Or (if you want to error out if the range doesn't have WP enabled):

args.flags = PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC;
args.categories_mask = PAGE_IS_WRITTEN;
args.return_mask = 0;

(PM_SCAN_CHECK_WPASYNC is effectively adding PAGE_IS_WPASYNC to the
required categories.)

[...]

> > 2. the implementation:
> > a. gather the page-categorising and write-protecting code in one place;
> Agreed.
>
> > b. optimization: add whole-vma skipping for WP usecase;
> I don't know who can benefit from it. Do you have any user in mind? When
> the user come of this optimization, this can be added later.

This is for users of WP that want to ignore WP for non-registered ranges
instead of erroring out on them. (I anticipate CRIU to use this.)

> > c. extracted output limiting code to pagemap_scan_output();
> If user passes half THP, current code wouldn't split huge page and WP the
> whole THP. We would loose the dirty state of other half huge page. This is
> bug. consoliding the output limiting code looks optimal, but we'll need to
> same limiting code to detect if full THP hasn't been passed in case of THP
> and HugeTLB.

For THP indeed - the code should check `end != start + HPAGE_SIZE`
instead of `ret == -ENOSPC`.

For HugeTLB there is a check that returns EINVAL when trying to WP
a partial page. I think I didn't change that part.

> > d. extracted range coalescing to pagemap_scan_push_range();
> My old pagemap_scan_output has become pagemap_scan_push_range().

Indeed. I did first push the max_pages check in, hence the 'extracting'
later.

> > e. extracted THP entry handling to pagemap_scan_thp_entry();
> Good. But I didn't found value in seperating it just like other historic
> pagemap code.

This is to avoid having to much indentation and long functions that do
many things at once.

> > f. added a shortcut for non-WP hugetlb scan; avoids conditional
> > locking;
> Yeah, some if conditions have been removed. But overall did couple of calls
> to is_interesting and scan_output functions instead of just one.

Yes, there are now two pairs instead of one. I see that I haven't pushed
the is_interesting calls into scan_output. This is now trivial:
if (!interesting...) {
*end = start;
return 0;
}
and could save some typing (but would need a different name for
scan_output as it would do filter & output), but I'm not sure about
readability.

Best Regards
Michał Mirosław