The locking protocol for page queue operations says that m->queue may
only transition between PQ_NONE and a page queue index, and that the
queue lock for from-value of m->queue must be held when performing the
update.
The page daemon is allowed to break this rule in the PQ_INACTIVE scan,
as an optimization. For each page, it first physically removes the page
from the queue while the inactive queue lock is held. Then, it acquires
the page lock, and verifies that m->queue == PQ_INACTIVE and that none
of the queue state flags are set. (For example, if PGA_DEQUEUE is set,
the page is logically dequeued and may not be freed.) Immediately
before freeing the page, the page daemon sets m->queue = PQ_NONE.
Currently, when performing per-CPU per-pagequeue batch operations, the loop
looks like this:
foreach m in batch queue: if m->queue == queue: aflags = m->aflags; <process m based on aflags>
The problem is that the page daemon may update m->queue after the
initial check of m->queue. We thus need to be more careful about the
ordering of the accesses of m->queue and m->aflags.
In pratice, this manifests as an assertion failure: the check
pq == vm_page_pagequeue(m) at the beginning of
vm_pqbatch_process_page() is invalid because the page daemon is allowed
to update m->queue. I think it would be very difficult to observe any
effects of this race in a non-INVARIANTS kernel.