Make page tables split-safe.

In order to minimize the likelihood of exit during page table
modifications, make the full set of page table functions split-safe.
This is not strictly necessary (and you may still incur splits due to
allocations from the allocator pool) but should make retries a very rare
occurance.

PiperOrigin-RevId: 200146688
Change-Id: I8fa36aa16b807beda2f0b057be60038258e8d597
This commit is contained in:
Adin Scannell
2018-06-11 18:15:14 -07:00
committed by Shentubot
parent 09b0a9c320
commit 1397a413b4
5 changed files with 584 additions and 348 deletions
@@ -1,6 +1,73 @@
package(licenses = ["notice"]) # Apache 2.0
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
go_template(
name = "generic_walker",
srcs = [
"walker_amd64.go",
],
opt_types = [
"Visitor",
],
visibility = [":__pkg__"],
)
go_template_instance(
name = "walker_map",
out = "walker_map.go",
package = "pagetables",
prefix = "map",
template = ":generic_walker",
types = {
"Visitor": "mapVisitor",
},
)
go_template_instance(
name = "walker_unmap",
out = "walker_unmap.go",
package = "pagetables",
prefix = "unmap",
template = ":generic_walker",
types = {
"Visitor": "unmapVisitor",
},
)
go_template_instance(
name = "walker_lookup",
out = "walker_lookup.go",
package = "pagetables",
prefix = "lookup",
template = ":generic_walker",
types = {
"Visitor": "lookupVisitor",
},
)
go_template_instance(
name = "walker_empty",
out = "walker_empty.go",
package = "pagetables",
prefix = "empty",
template = ":generic_walker",
types = {
"Visitor": "emptyVisitor",
},
)
go_template_instance(
name = "walker_check",
out = "walker_check.go",
package = "pagetables",
prefix = "check",
template = ":generic_walker",
types = {
"Visitor": "checkVisitor",
},
)
go_library(
name = "pagetables",
@@ -11,6 +78,10 @@ go_library(
"pagetables_amd64.go",
"pagetables_x86.go",
"pcids_x86.go",
"walker_empty.go",
"walker_lookup.go",
"walker_map.go",
"walker_unmap.go",
],
importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/platform/ring0/pagetables",
visibility = [
@@ -26,6 +97,7 @@ go_test(
srcs = [
"pagetables_amd64_test.go",
"pagetables_test.go",
"walker_check.go",
],
embed = [":pagetables"],
deps = ["//pkg/sentry/usermem"],
@@ -13,6 +13,11 @@
// limitations under the License.
// Package pagetables provides a generic implementation of pagetables.
//
// The core functions must be safe to call from a nosplit context. Furthermore,
// this pagetables implementation goes to lengths to ensure that all functions
// are free from runtime allocation. Calls to NewPTEs/FreePTEs may be made
// during walks, but these can be cached elsewhere if required.
package pagetables
import (
@@ -38,64 +43,179 @@ type PageTables struct {
// New returns new PageTables.
func New(a Allocator) *PageTables {
p := &PageTables{Allocator: a}
p.root = p.Allocator.NewPTEs()
p.rootPhysical = p.Allocator.PhysicalFor(p.root)
p := new(PageTables)
p.Init(a)
return p
}
// Init initializes a set of PageTables.
//
//go:nosplit
func (p *PageTables) Init(allocator Allocator) {
p.Allocator = allocator
p.root = p.Allocator.NewPTEs()
p.rootPhysical = p.Allocator.PhysicalFor(p.root)
}
// mapVisitor is used for map.
type mapVisitor struct {
target uintptr // Input.
physical uintptr // Input.
opts MapOpts // Input.
prev bool // Output.
}
// visit is used for map.
//
//go:nosplit
func (v *mapVisitor) visit(start uintptr, pte *PTE, align uintptr) {
p := v.physical + (start - uintptr(v.target))
if pte.Valid() && (pte.Address() != p || pte.Opts() != v.opts) {
v.prev = true
}
if p&align != 0 {
// We will install entries at a smaller granulaity if we don't
// install a valid entry here, however we must zap any existing
// entry to ensure this happens.
pte.Clear()
return
}
pte.Set(p, v.opts)
}
//go:nosplit
func (*mapVisitor) requiresAlloc() bool { return true }
//go:nosplit
func (*mapVisitor) requiresSplit() bool { return true }
// Map installs a mapping with the given physical address.
//
// True is returned iff there was a previous mapping in the range.
//
// Precondition: addr & length must be aligned, their sum must not overflow.
// Precondition: addr & length must be page-aligned, their sum must not overflow.
//
//go:nosplit
func (p *PageTables) Map(addr usermem.Addr, length uintptr, opts MapOpts, physical uintptr) bool {
if !opts.AccessType.Any() {
return p.Unmap(addr, length)
}
prev := false
end, ok := addr.AddLength(uint64(length))
if !ok {
panic("pagetables.Map: overflow")
w := mapWalker{
pageTables: p,
visitor: mapVisitor{
target: uintptr(addr),
physical: physical,
opts: opts,
},
}
p.iterateRange(uintptr(addr), uintptr(end), true, func(s, e uintptr, pte *PTE, align uintptr) {
p := physical + (s - uintptr(addr))
prev = prev || (pte.Valid() && (p != pte.Address() || opts != pte.Opts()))
if p&align != 0 {
// We will install entries at a smaller granulaity if
// we don't install a valid entry here, however we must
// zap any existing entry to ensure this happens.
pte.Clear()
return
}
pte.Set(p, opts)
})
return prev
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.prev
}
// unmapVisitor is used for unmap.
type unmapVisitor struct {
count int
}
//go:nosplit
func (*unmapVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*unmapVisitor) requiresSplit() bool { return true }
// visit unmaps the given entry.
//
//go:nosplit
func (v *unmapVisitor) visit(start uintptr, pte *PTE, align uintptr) {
pte.Clear()
v.count++
}
// Unmap unmaps the given range.
//
// True is returned iff there was a previous mapping in the range.
//
// Precondition: addr & length must be page-aligned.
//
//go:nosplit
func (p *PageTables) Unmap(addr usermem.Addr, length uintptr) bool {
count := 0
p.iterateRange(uintptr(addr), uintptr(addr)+length, false, func(s, e uintptr, pte *PTE, align uintptr) {
pte.Clear()
count++
})
return count > 0
w := unmapWalker{
pageTables: p,
visitor: unmapVisitor{
count: 0,
},
}
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.count > 0
}
// emptyVisitor is used for emptiness checks.
type emptyVisitor struct {
count int
}
//go:nosplit
func (*emptyVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*emptyVisitor) requiresSplit() bool { return false }
// visit unmaps the given entry.
//
//go:nosplit
func (v *emptyVisitor) visit(start uintptr, pte *PTE, align uintptr) {
v.count++
}
// IsEmpty checks if the given range is empty.
//
// Precondition: addr & length must be page-aligned.
//
//go:nosplit
func (p *PageTables) IsEmpty(addr usermem.Addr, length uintptr) bool {
w := emptyWalker{
pageTables: p,
}
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.count == 0
}
// lookupVisitor is used for lookup.
type lookupVisitor struct {
target uintptr // Input.
physical uintptr // Output.
opts MapOpts // Output.
}
// visit matches the given address.
//
//go:nosplit
func (v *lookupVisitor) visit(start uintptr, pte *PTE, align uintptr) {
if !pte.Valid() {
return
}
v.physical = pte.Address() + (start - uintptr(v.target))
v.opts = pte.Opts()
}
//go:nosplit
func (*lookupVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*lookupVisitor) requiresSplit() bool { return false }
// Lookup returns the physical address for the given virtual address.
//
//go:nosplit
func (p *PageTables) Lookup(addr usermem.Addr) (physical uintptr, opts MapOpts) {
mask := uintptr(usermem.PageSize - 1)
off := uintptr(addr) & mask
addr = addr &^ usermem.Addr(mask)
p.iterateRange(uintptr(addr), uintptr(addr+usermem.PageSize), false, func(s, e uintptr, pte *PTE, align uintptr) {
if !pte.Valid() {
return
}
physical = pte.Address() + (s - uintptr(addr)) + off
opts = pte.Opts()
})
return
offset := uintptr(addr) & mask
w := lookupWalker{
pageTables: p,
visitor: lookupVisitor{
target: uintptr(addr &^ usermem.Addr(mask)),
},
}
w.iterateRange(uintptr(addr), uintptr(addr)+1)
return w.visitor.physical + offset, w.visitor.opts
}
@@ -12,14 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build amd64
package pagetables
import (
"fmt"
)
// Address constraints.
//
// The lowerTop and upperBottom currently apply to four-level pagetables;
@@ -49,273 +43,3 @@ const (
// PTEs is a collection of entries.
type PTEs [entriesPerPage]PTE
// next returns the next address quantized by the given size.
func next(start uint64, size uint64) uint64 {
start &= ^(size - 1)
start += size
return start
}
// iterateRange iterates over all appropriate levels of page tables for the given range.
//
// If alloc is set, then Set _must_ be called on all given PTEs. The exception
// is super pages. If a valid super page cannot be installed, then the walk
// will continue to individual entries.
//
// This algorithm will attempt to maximize the use of super pages whenever
// possible. Whether a super page is provided will be clear through the range
// provided in the callback.
//
// Note that if alloc set, then no gaps will be present. However, if alloc is
// not set, then the iteration will likely be full of gaps.
//
// Note that this function should generally be avoided in favor of Map, Unmap,
// etc. when not necessary.
//
// Precondition: startAddr and endAddr must be page-aligned.
//
// Precondition: startStart must be less than endAddr.
//
// Precondition: If alloc is set, then startAddr and endAddr should not span
// non-canonical ranges. If they do, a panic will result.
func (p *PageTables) iterateRange(startAddr, endAddr uintptr, alloc bool, fn func(s, e uintptr, pte *PTE, align uintptr)) {
start := uint64(startAddr)
end := uint64(endAddr)
if start%pteSize != 0 {
panic(fmt.Sprintf("unaligned start: %v", start))
}
if start > end {
panic(fmt.Sprintf("start > end (%v > %v))", start, end))
}
// Deal with cases where we traverse the "gap".
//
// These are all explicitly disallowed if alloc is set, and we must
// traverse an entry for each address explicitly.
switch {
case start < lowerTop && end > lowerTop && end < upperBottom:
if alloc {
panic(fmt.Sprintf("alloc [%x, %x) spans non-canonical range", start, end))
}
p.iterateRange(startAddr, lowerTop, false, fn)
return
case start < lowerTop && end > lowerTop:
if alloc {
panic(fmt.Sprintf("alloc [%x, %x) spans non-canonical range", start, end))
}
p.iterateRange(startAddr, lowerTop, false, fn)
p.iterateRange(upperBottom, endAddr, false, fn)
return
case start > lowerTop && end < upperBottom:
if alloc {
panic(fmt.Sprintf("alloc [%x, %x) spans non-canonical range", start, end))
}
return
case start > lowerTop && start < upperBottom && end > upperBottom:
if alloc {
panic(fmt.Sprintf("alloc [%x, %x) spans non-canonical range", start, end))
}
p.iterateRange(upperBottom, endAddr, false, fn)
return
}
for pgdIndex := int((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
var (
pgdEntry = &p.root[pgdIndex]
pudEntries *PTEs
)
if !pgdEntry.Valid() {
if !alloc {
// Skip over this entry.
start = next(start, pgdSize)
continue
}
// Allocate a new pgd.
pudEntries = p.Allocator.NewPTEs()
pgdEntry.setPageTable(p, pudEntries)
} else {
pudEntries = p.Allocator.LookupPTEs(pgdEntry.Address())
}
// Map the next level.
clearPUDEntries := 0
for pudIndex := int((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
var (
pudEntry = &pudEntries[pudIndex]
pmdEntries *PTEs
)
if !pudEntry.Valid() {
if !alloc {
// Skip over this entry.
clearPUDEntries++
start = next(start, pudSize)
continue
}
// This level has 1-GB super pages. Is this
// entire region contained in a single PUD
// entry? If so, we can skip allocating a new
// page for the pmd.
if start&(pudSize-1) == 0 && end-start >= pudSize {
pudEntry.SetSuper()
fn(uintptr(start), uintptr(start+pudSize), pudEntry, pudSize-1)
if pudEntry.Valid() {
start = next(start, pudSize)
continue
}
}
// Allocate a new pud.
pmdEntries = p.Allocator.NewPTEs()
pudEntry.setPageTable(p, pmdEntries)
} else if pudEntry.IsSuper() {
// Does this page need to be split?
if start&(pudSize-1) != 0 || end < next(start, pudSize) {
currentAddr := uint64(pudEntry.Address())
// Install the relevant entries.
pmdEntries = p.Allocator.NewPTEs()
for index := 0; index < entriesPerPage; index++ {
pmdEntry := &pmdEntries[index]
pmdEntry.SetSuper()
pmdEntry.Set(uintptr(currentAddr), pudEntry.Opts())
currentAddr += pmdSize
}
// Reset to point to the new page.
pudEntry.setPageTable(p, pmdEntries)
} else {
// A super page to be checked directly.
fn(uintptr(start), uintptr(start+pudSize), pudEntry, pudSize-1)
// Might have been cleared.
if !pudEntry.Valid() {
clearPUDEntries++
}
// Note that the super page was changed.
start = next(start, pudSize)
continue
}
} else {
pmdEntries = p.Allocator.LookupPTEs(pudEntry.Address())
}
// Map the next level, since this is valid.
clearPMDEntries := 0
for pmdIndex := int((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
var (
pmdEntry = &pmdEntries[pmdIndex]
pteEntries *PTEs
)
if !pmdEntry.Valid() {
if !alloc {
// Skip over this entry.
clearPMDEntries++
start = next(start, pmdSize)
continue
}
// This level has 2-MB huge pages. If this
// region is contained in a single PMD entry?
// As above, we can skip allocating a new page.
if start&(pmdSize-1) == 0 && end-start >= pmdSize {
pmdEntry.SetSuper()
fn(uintptr(start), uintptr(start+pmdSize), pmdEntry, pmdSize-1)
if pmdEntry.Valid() {
start = next(start, pmdSize)
continue
}
}
// Allocate a new pmd.
pteEntries = p.Allocator.NewPTEs()
pmdEntry.setPageTable(p, pteEntries)
} else if pmdEntry.IsSuper() {
// Does this page need to be split?
if start&(pmdSize-1) != 0 || end < next(start, pmdSize) {
currentAddr := uint64(pmdEntry.Address())
// Install the relevant entries.
pteEntries = p.Allocator.NewPTEs()
for index := 0; index < entriesPerPage; index++ {
pteEntry := &pteEntries[index]
pteEntry.Set(uintptr(currentAddr), pmdEntry.Opts())
currentAddr += pteSize
}
// Reset to point to the new page.
pmdEntry.setPageTable(p, pteEntries)
} else {
// A huge page to be checked directly.
fn(uintptr(start), uintptr(start+pmdSize), pmdEntry, pmdSize-1)
// Might have been cleared.
if !pmdEntry.Valid() {
clearPMDEntries++
}
// Note that the huge page was changed.
start = next(start, pmdSize)
continue
}
} else {
pteEntries = p.Allocator.LookupPTEs(pmdEntry.Address())
}
// Map the next level, since this is valid.
clearPTEEntries := 0
for pteIndex := int((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
var (
pteEntry = &pteEntries[pteIndex]
)
if !pteEntry.Valid() && !alloc {
clearPTEEntries++
start += pteSize
continue
}
// At this point, we are guaranteed that start%pteSize == 0.
fn(uintptr(start), uintptr(start+pteSize), pteEntry, pteSize-1)
if !pteEntry.Valid() {
if alloc {
panic("PTE not set after iteration with alloc=true!")
}
clearPTEEntries++
}
// Note that the pte was changed.
start += pteSize
continue
}
// Check if we no longer need this page.
if clearPTEEntries == entriesPerPage {
pmdEntry.Clear()
p.Allocator.FreePTEs(pteEntries)
clearPMDEntries++
}
}
// Check if we no longer need this page.
if clearPMDEntries == entriesPerPage {
pudEntry.Clear()
p.Allocator.FreePTEs(pmdEntries)
clearPUDEntries++
}
}
// Check if we no longer need this page.
if clearPUDEntries == entriesPerPage {
pgdEntry.Clear()
p.Allocator.FreePTEs(pudEntries)
}
}
}
@@ -27,48 +27,61 @@ type mapping struct {
opts MapOpts
}
func checkMappings(t *testing.T, pt *PageTables, m []mapping) {
var (
current int
found []mapping
failed string
)
type checkVisitor struct {
expected []mapping // Input.
current int // Temporary.
found []mapping // Output.
failed string // Output.
}
// Iterate over all the mappings.
pt.iterateRange(0, ^uintptr(0), false, func(s, e uintptr, pte *PTE, align uintptr) {
found = append(found, mapping{
start: s,
length: e - s,
addr: pte.Address(),
opts: pte.Opts(),
})
if failed != "" {
// Don't keep looking for errors.
return
}
if current >= len(m) {
failed = "more mappings than expected"
} else if m[current].start != s {
failed = "start didn't match expected"
} else if m[current].length != (e - s) {
failed = "end didn't match expected"
} else if m[current].addr != pte.Address() {
failed = "address didn't match expected"
} else if m[current].opts != pte.Opts() {
failed = "opts didn't match"
}
current++
func (v *checkVisitor) visit(start uintptr, pte *PTE, align uintptr) {
v.found = append(v.found, mapping{
start: start,
length: align + 1,
addr: pte.Address(),
opts: pte.Opts(),
})
if v.failed != "" {
// Don't keep looking for errors.
return
}
if v.current >= len(v.expected) {
v.failed = "more mappings than expected"
} else if v.expected[v.current].start != start {
v.failed = "start didn't match expected"
} else if v.expected[v.current].length != (align + 1) {
v.failed = "end didn't match expected"
} else if v.expected[v.current].addr != pte.Address() {
v.failed = "address didn't match expected"
} else if v.expected[v.current].opts != pte.Opts() {
v.failed = "opts didn't match"
}
v.current++
}
func (*checkVisitor) requiresAlloc() bool { return false }
func (*checkVisitor) requiresSplit() bool { return false }
func checkMappings(t *testing.T, pt *PageTables, m []mapping) {
// Iterate over all the mappings.
w := checkWalker{
pageTables: pt,
visitor: checkVisitor{
expected: m,
},
}
w.iterateRange(0, ^uintptr(0))
// Were we expected additional mappings?
if failed == "" && current != len(m) {
failed = "insufficient mappings found"
if w.visitor.failed == "" && w.visitor.current != len(w.visitor.expected) {
w.visitor.failed = "insufficient mappings found"
}
// Emit a meaningful error message on failure.
if failed != "" {
t.Errorf("%s; got %#v, wanted %#v", failed, found, m)
if w.visitor.failed != "" {
t.Errorf("%s; got %#v, wanted %#v", w.visitor.failed, w.visitor.found, w.visitor.expected)
}
}
@@ -0,0 +1,307 @@
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build amd64
package pagetables
// Visitor is a generic type.
type Visitor interface {
// visit is called on each PTE.
visit(start uintptr, pte *PTE, align uintptr)
// requiresAlloc indicates that new entries should be allocated within
// the walked range.
requiresAlloc() bool
// requiresSplit indicates that entries in the given range should be
// split if they are huge or jumbo pages.
requiresSplit() bool
}
// Walker walks page tables.
type Walker struct {
// pageTables are the tables to walk.
pageTables *PageTables
// Visitor is the set of arguments.
visitor Visitor
}
// iterateRange iterates over all appropriate levels of page tables for the given range.
//
// If requiresAlloc is true, then Set _must_ be called on all given PTEs. The
// exception is super pages. If a valid super page (huge or jumbo) cannot be
// installed, then the walk will continue to individual entries.
//
// This algorithm will attempt to maximize the use of super pages whenever
// possible. Whether a super page is provided will be clear through the range
// provided in the callback.
//
// Note that if requiresAlloc is true, then no gaps will be present. However,
// if alloc is not set, then the iteration will likely be full of gaps.
//
// Note that this function should generally be avoided in favor of Map, Unmap,
// etc. when not necessary.
//
// Precondition: start must be page-aligned.
//
// Precondition: start must be less than end.
//
// Precondition: If requiresAlloc is true, then start and end should not span
// non-canonical ranges. If they do, a panic will result.
//
//go:nosplit
func (w *Walker) iterateRange(start, end uintptr) {
if start%pteSize != 0 {
panic("unaligned start")
}
if end < start {
panic("start > end")
}
if start < lowerTop {
if end <= lowerTop {
w.iterateRangeCanonical(start, end)
} else if end > lowerTop && end <= upperBottom {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(start, lowerTop)
} else {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(start, lowerTop)
w.iterateRangeCanonical(upperBottom, end)
}
} else if start < upperBottom {
if end <= upperBottom {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
} else {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(upperBottom, end)
}
} else {
w.iterateRangeCanonical(start, end)
}
}
// next returns the next address quantized by the given size.
//
//go:nosplit
func next(start uintptr, size uintptr) uintptr {
start &= ^(size - 1)
start += size
return start
}
// iterateRangeCanonical walks a canonical range.
//
//go:nosplit
func (w *Walker) iterateRangeCanonical(start, end uintptr) {
for pgdIndex := uint16((start & pgdMask) >> pgdShift); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
var (
pgdEntry = &w.pageTables.root[pgdIndex]
pudEntries *PTEs
)
if !pgdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
start = next(start, pgdSize)
continue
}
// Allocate a new pgd.
pudEntries = w.pageTables.Allocator.NewPTEs()
pgdEntry.setPageTable(w.pageTables, pudEntries)
} else {
pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
}
// Map the next level.
clearPUDEntries := uint16(0)
for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
var (
pudEntry = &pudEntries[pudIndex]
pmdEntries *PTEs
)
if !pudEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPUDEntries++
start = next(start, pudSize)
continue
}
// This level has 1-GB super pages. Is this
// entire region at least as large as a single
// PUD entry? If so, we can skip allocating a
// new page for the pmd.
if start&(pudSize-1) == 0 && end-start >= pudSize {
pudEntry.SetSuper()
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
if pudEntry.Valid() {
start = next(start, pudSize)
continue
}
}
// Allocate a new pud.
pmdEntries = w.pageTables.Allocator.NewPTEs()
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else if pudEntry.IsSuper() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < next(start, pudSize)) {
// Install the relevant entries.
pmdEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pmdEntries[index].SetSuper()
pmdEntries[index].Set(
pudEntry.Address()+(pmdSize*uintptr(index)),
pudEntry.Opts())
}
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else {
// A super page to be checked directly.
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
// Might have been cleared.
if !pudEntry.Valid() {
clearPUDEntries++
}
// Note that the super page was changed.
start = next(start, pudSize)
continue
}
} else {
pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
}
// Map the next level, since this is valid.
clearPMDEntries := uint16(0)
for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
var (
pmdEntry = &pmdEntries[pmdIndex]
pteEntries *PTEs
)
if !pmdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPMDEntries++
start = next(start, pmdSize)
continue
}
// This level has 2-MB huge pages. If this
// region is contined in a single PMD entry?
// As above, we can skip allocating a new page.
if start&(pmdSize-1) == 0 && end-start >= pmdSize {
pmdEntry.SetSuper()
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
if pmdEntry.Valid() {
start = next(start, pmdSize)
continue
}
}
// Allocate a new pmd.
pteEntries = w.pageTables.Allocator.NewPTEs()
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else if pmdEntry.IsSuper() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < next(start, pmdSize)) {
// Install the relevant entries.
pteEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pteEntries[index].Set(
pmdEntry.Address()+(pteSize*uintptr(index)),
pmdEntry.Opts())
}
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else {
// A huge page to be checked directly.
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
// Might have been cleared.
if !pmdEntry.Valid() {
clearPMDEntries++
}
// Note that the huge page was changed.
start = next(start, pmdSize)
continue
}
} else {
pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
}
// Map the next level, since this is valid.
clearPTEEntries := uint16(0)
for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
var (
pteEntry = &pteEntries[pteIndex]
)
if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
clearPTEEntries++
start += pteSize
continue
}
// At this point, we are guaranteed that start%pteSize == 0.
w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
if !pteEntry.Valid() {
if w.visitor.requiresAlloc() {
panic("PTE not set after iteration with requiresAlloc!")
}
clearPTEEntries++
}
// Note that the pte was changed.
start += pteSize
continue
}
// Check if we no longer need this page.
if clearPTEEntries == entriesPerPage {
pmdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pteEntries)
clearPMDEntries++
}
}
// Check if we no longer need this page.
if clearPMDEntries == entriesPerPage {
pudEntry.Clear()
w.pageTables.Allocator.FreePTEs(pmdEntries)
clearPUDEntries++
}
}
// Check if we no longer need this page.
if clearPUDEntries == entriesPerPage {
pgdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pudEntries)
}
}
}