-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
kvm: share upper halves among all pagtables #3617
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here with What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
Hello! Would any arm man have a review on it please? @lubinszARM |
Hi @amscanne Hi @laijs , Thanks. |
@lubinszARM KPTI is unrelated to this pr, but it is very helpful for #2256 and an important preparation for #2256. And #2256 doesn't implement KPTI for arm. I'm not capable to develop on arm by now. |
updated |
If @amscanne approved this PR, I will help you to fix this issue. For your reference: goroutine 17 [running]: |
Got it. |
updated again. |
@lubinszARM Does the newest code work on arm64? Could you give it an Acked-by ? |
Segv issue was gone and the results of the kvm test are passed on Arm64. |
@amscanne I think the pr is ready to be merged. |
ping @amscanne |
There are a few issues here, and I'll try to explain. I think there's a clear path to resolve them. First, the internal allocator assumes unique ownership over the PTE entries. That means that it may reuse them when freed, since they go back into the pool. Second, the "used" map tracks PTEs that are in use (owned by those page tables) so that they aren't garbage-collected. As long as Unmap isn't called on the kernel range, then everything here should be fine... but I don't like to count on that. I'd prefer if there were a second structure and we maintained it properly. Could look something like this: type SharedPageTables struct { // impl is the set of PageTables. It holds ownership over all shared pages. // start is the end of the shared range. // end is the end of the shared range. // usedIn is the set of PageTables where this is mapped. // Map adds the given range to the SharedPageTables. func (p *SharedPageTables) Unmap(addr usermem.Addr, length uintptr) { func (p *SharedPageTables) Apply(other *PageTables) { struct PageTables struct { func (p *PageTables) Map(...) { func (p *PageTables) Unmap(...) { Then the machine.kernelPageTables would just become SharedPageTables. Anyhow, this is just a sketch of an idea to introduce a new type, track shared pages (and give them a clear owner), though I think there are other options here. Happy to discuss. I'm trying to merge the KPTI change right now (so that this can be rebase for merge as well). |
Updated commit pushed. Your concerns addressed. And your suggestions are basically applied except that SharedPageTables is forced purely read-only, so there is no sync.Mutex and usedIn needed and |
84d7fa3
to
17f3c05
Compare
Updated commit pushed again. And execRegion is removed to meet your requirement in the KPTI patch since mapUpperHalf() is called only once now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks great. I had some minor feedback and nits (and one enhancement), then I'm happy to get it merged.
@@ -41,13 +52,22 @@ type PageTables struct { | |||
archPageTables | |||
} | |||
|
|||
// New returns new PageTables. | |||
func New(a Allocator) *PageTables { | |||
// NewWithUpper returns new PageTables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussion elsewhere, you should document the API semantics of upperHalfPageTables here, and include any preconditions. E.g.
// NewWithUpper returns new PageTables.
//
// upperHalfPageTables are used for mapping the upper half of addresses, starting at upperLimit.
// These pageTables should not be touched (as invalidations may be incorrect) after they are
// passed as an upperHalfPageTables. Only when all dependent PageTables are gone may they
// be used. The intenteded use case is for kernel page tables, which are static and fixed.
//
// Precondition: upperLimit must be between canonical ranges.
// Precondition: ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it a bit further, it does make me nervous that someone could accidentally do something with kernelPageTables, and it would be really hard to identify the source of that bug. (Since e.g. page table memory might just get reused.)
I like the simplicity of just checking for things in Map/Unmap/etc. Can we just do something like this?
// MakeShared marks these PageTables as shared.
func (p *PageTables) MakeShared() {
p.allocator.Drain() // Won't be modified.
p.shared = true
}
// ...
// Precondition: p must not be shared.
func (p *PageTables) Map(...) {
if p.shared {
panic(...)
}
...
}
// ...
// Precondition: upperHalfPageTables must be shared.
func NewWithUpper(a Allocator, upperHalfPageTables *pageTables) *PageTables {
if upperHalfPageTables != nil {
if !upperHalfPageTables.shared {
panic(...)
}
}
...
}
That way everything is pretty simple (like the design you have here), and also still safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more good idea here: you can actually add an arch-hook for makeShared. E.g.
// MakeShared marks these PageTables as shared.
func (p *PageTables) MakeShared() {
p.shared = true
w := sharedWalker{
pageTables: p,
visitor: sharedVisitor{}
}
w.iterateRange(0, ^0)
p.allocator.Drain() // Won't be modified.
}
On amd64, this could go and mark all relevant PTEs as G, so they are shared across all address spaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea to add the shared field in the Pagetables to make it read only. But I don't think we need to add a new sharedVisitor, we can just use G bit in mapUpperHalf() (and add X86_CR4_PGE bit in CR4).
777feb3
to
3425485
Compare
The commit is updated as you requested. |
Fixes: google#509 Signed-off-by: Lai Jiangshan <jiangshan.ljs@antfin.com> Signed-off-by: Lai Jiangshan <laijs@linux.alibaba.com>
Fixes: #509