The page table already knows the size of a page without having to directly use any ISA specific constants. Change-Id: I68b575e194697065620a2097d972076886766f74 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/34172 Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu> Maintainer: Gabe Black <gabe.black@gmail.com>
492 lines
17 KiB
C++
492 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2017-2020 Advanced Micro Devices, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met: redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer;
|
|
* redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution;
|
|
* neither the name of the copyright holders nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "sim/mem_state.hh"
|
|
|
|
#include <cassert>
|
|
|
|
#include "arch/generic/mmu.hh"
|
|
#include "debug/Vma.hh"
|
|
#include "mem/se_translating_port_proxy.hh"
|
|
#include "sim/process.hh"
|
|
#include "sim/syscall_debug_macros.hh"
|
|
#include "sim/system.hh"
|
|
#include "sim/vma.hh"
|
|
|
|
MemState::MemState(Process *owner, Addr brk_point, Addr stack_base,
|
|
Addr max_stack_size, Addr next_thread_stack_base,
|
|
Addr mmap_end)
|
|
: _ownerProcess(owner),
|
|
_pageBytes(owner->pTable->pageSize()), _brkPoint(brk_point),
|
|
_stackBase(stack_base), _stackSize(max_stack_size),
|
|
_maxStackSize(max_stack_size), _stackMin(stack_base - max_stack_size),
|
|
_nextThreadStackBase(next_thread_stack_base),
|
|
_mmapEnd(mmap_end), _endBrkPoint(brk_point)
|
|
{
|
|
}
|
|
|
|
MemState&
|
|
MemState::operator=(const MemState &in)
|
|
{
|
|
if (this == &in)
|
|
return *this;
|
|
|
|
_pageBytes = in._pageBytes;
|
|
_brkPoint = in._brkPoint;
|
|
_stackBase = in._stackBase;
|
|
_stackSize = in._stackSize;
|
|
_maxStackSize = in._maxStackSize;
|
|
_stackMin = in._stackMin;
|
|
_nextThreadStackBase = in._nextThreadStackBase;
|
|
_mmapEnd = in._mmapEnd;
|
|
_endBrkPoint = in._endBrkPoint;
|
|
_vmaList = in._vmaList; /* This assignment does a deep copy. */
|
|
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
MemState::resetOwner(Process *owner)
|
|
{
|
|
_ownerProcess = owner;
|
|
}
|
|
|
|
bool
|
|
MemState::isUnmapped(Addr start_addr, Addr length)
|
|
{
|
|
Addr end_addr = start_addr + length;
|
|
const AddrRange range(start_addr, end_addr);
|
|
for (const auto &vma : _vmaList) {
|
|
if (vma.intersects(range))
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* In case someone skips the VMA interface and just directly maps memory
|
|
* also consult the page tables to make sure that this memory isnt mapped.
|
|
*/
|
|
for (auto start = start_addr; start < end_addr;
|
|
start += _pageBytes) {
|
|
if (_ownerProcess->pTable->lookup(start) != nullptr) {
|
|
panic("Someone allocated physical memory at VA %p without "
|
|
"creating a VMA!\n", start);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MemState::updateBrkRegion(Addr old_brk, Addr new_brk)
|
|
{
|
|
/**
|
|
* To make this simple, avoid reducing the heap memory area if the
|
|
* new_brk point is less than the old_brk; this occurs when the heap is
|
|
* receding because the application has given back memory. The brk point
|
|
* is still tracked in the MemState class as an independent field so that
|
|
* it can be returned to the application; we just do not update the
|
|
* region unless we expand it out.
|
|
*/
|
|
if (new_brk < old_brk) {
|
|
_brkPoint = new_brk;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* The regions must be page aligned but the break point can be set on
|
|
* byte boundaries. Ensure that the restriction is maintained here by
|
|
* extending the request out to the end of the page. (The roundUp
|
|
* function will not round up an already aligned page.)
|
|
*/
|
|
auto page_aligned_brk = roundUp(new_brk, _pageBytes);
|
|
|
|
/**
|
|
* Create a new mapping for the heap region. We only create a mapping
|
|
* for the extra memory that is requested so we do not create a situation
|
|
* where there can be overlapping mappings in the regions.
|
|
*
|
|
* Since we do not track the type of the region and we also do not
|
|
* coalesce the regions together, we can create a fragmented set of
|
|
* heap regions. To resolve this, we keep the furthest point ever mapped
|
|
* by the _endBrkPoint field.
|
|
*/
|
|
if (page_aligned_brk > _endBrkPoint) {
|
|
auto length = page_aligned_brk - _endBrkPoint;
|
|
/**
|
|
* Check if existing mappings impede the expansion of brk expansion.
|
|
* If brk cannot expand, it must return the original, unmodified brk
|
|
* address and should not modify the mappings here.
|
|
*/
|
|
if (!isUnmapped(_endBrkPoint, length)) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Note that the heap regions are always contiguous but there is
|
|
* no mechanism right now to coalesce together memory that belongs
|
|
* to the same region with similar access permissions. This could be
|
|
* implemented if it actually becomes necessary; probably only
|
|
* necessary if the list becomes too long to walk.
|
|
*/
|
|
mapRegion(_endBrkPoint, length, "heap");
|
|
_endBrkPoint = page_aligned_brk;
|
|
}
|
|
|
|
_brkPoint = new_brk;
|
|
}
|
|
|
|
void
|
|
MemState::mapRegion(Addr start_addr, Addr length,
|
|
const std::string& region_name, int sim_fd, Addr offset)
|
|
{
|
|
DPRINTF(Vma, "memstate: creating vma (%s) [0x%x - 0x%x]\n",
|
|
region_name.c_str(), start_addr, start_addr + length);
|
|
|
|
/**
|
|
* Avoid creating a region that has preexisting mappings. This should
|
|
* not happen under normal circumstances so consider this to be a bug.
|
|
*/
|
|
assert(isUnmapped(start_addr, length));
|
|
|
|
/**
|
|
* Record the region in our list structure.
|
|
*/
|
|
_vmaList.emplace_back(AddrRange(start_addr, start_addr + length),
|
|
_pageBytes, region_name, sim_fd, offset);
|
|
}
|
|
|
|
void
|
|
MemState::unmapRegion(Addr start_addr, Addr length)
|
|
{
|
|
Addr end_addr = start_addr + length;
|
|
const AddrRange range(start_addr, end_addr);
|
|
|
|
auto vma = std::begin(_vmaList);
|
|
while (vma != std::end(_vmaList)) {
|
|
if (vma->isStrictSuperset(range)) {
|
|
DPRINTF(Vma, "memstate: split vma [0x%x - 0x%x] into "
|
|
"[0x%x - 0x%x] and [0x%x - 0x%x]\n",
|
|
vma->start(), vma->end(),
|
|
vma->start(), start_addr,
|
|
end_addr, vma->end());
|
|
/**
|
|
* Need to split into two smaller regions.
|
|
* Create a clone of the old VMA and slice it to the right.
|
|
*/
|
|
_vmaList.push_back(*vma);
|
|
_vmaList.back().sliceRegionRight(start_addr);
|
|
|
|
/**
|
|
* Slice old VMA to encapsulate the left region.
|
|
*/
|
|
vma->sliceRegionLeft(end_addr);
|
|
|
|
/**
|
|
* Region cannot be in any more VMA, because it is completely
|
|
* contained in this one!
|
|
*/
|
|
break;
|
|
} else if (vma->isSubset(range)) {
|
|
DPRINTF(Vma, "memstate: destroying vma [0x%x - 0x%x]\n",
|
|
vma->start(), vma->end());
|
|
/**
|
|
* Need to nuke the existing VMA.
|
|
*/
|
|
vma = _vmaList.erase(vma);
|
|
|
|
continue;
|
|
|
|
} else if (vma->intersects(range)) {
|
|
/**
|
|
* Trim up the existing VMA.
|
|
*/
|
|
if (vma->start() < start_addr) {
|
|
DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
|
|
"into [0x%x - 0x%x]\n",
|
|
vma->start(), vma->end(),
|
|
vma->start(), start_addr);
|
|
/**
|
|
* Overlaps from the right.
|
|
*/
|
|
vma->sliceRegionRight(start_addr);
|
|
} else {
|
|
DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
|
|
"into [0x%x - 0x%x]\n",
|
|
vma->start(), vma->end(),
|
|
end_addr, vma->end());
|
|
/**
|
|
* Overlaps from the left.
|
|
*/
|
|
vma->sliceRegionLeft(end_addr);
|
|
}
|
|
}
|
|
|
|
vma++;
|
|
}
|
|
|
|
/**
|
|
* TLBs need to be flushed to remove any stale mappings from regions
|
|
* which were unmapped. Currently the entire TLB is flushed. This results
|
|
* in functionally correct execution, but real systems do not flush all
|
|
* entries when a single mapping changes since it degrades performance.
|
|
* There is currently no general method across all TLB implementations
|
|
* that can flush just part of the address space.
|
|
*/
|
|
for (auto *tc: _ownerProcess->system->threads) {
|
|
tc->getMMUPtr()->flushAll();
|
|
}
|
|
|
|
do {
|
|
if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
|
|
_ownerProcess->pTable->unmap(start_addr, _pageBytes);
|
|
|
|
start_addr += _pageBytes;
|
|
|
|
/**
|
|
* The regions need to always be page-aligned otherwise the while
|
|
* condition will loop indefinitely. (The Addr type is currently
|
|
* defined to be uint64_t in src/base/types.hh; it can underflow
|
|
* since it is unsigned.)
|
|
*/
|
|
length -= _pageBytes;
|
|
} while (length > 0);
|
|
}
|
|
|
|
void
|
|
MemState::remapRegion(Addr start_addr, Addr new_start_addr, Addr length)
|
|
{
|
|
Addr end_addr = start_addr + length;
|
|
const AddrRange range(start_addr, end_addr);
|
|
|
|
auto vma = std::begin(_vmaList);
|
|
while (vma != std::end(_vmaList)) {
|
|
if (vma->isStrictSuperset(range)) {
|
|
/**
|
|
* Create clone of the old VMA and slice right.
|
|
*/
|
|
_vmaList.push_back(*vma);
|
|
_vmaList.back().sliceRegionRight(start_addr);
|
|
|
|
/**
|
|
* Create clone of the old VMA and slice it left.
|
|
*/
|
|
_vmaList.push_back(*vma);
|
|
_vmaList.back().sliceRegionLeft(end_addr);
|
|
|
|
/**
|
|
* Slice the old VMA left and right to adjust the file backing,
|
|
* then overwrite the virtual addresses!
|
|
*/
|
|
vma->sliceRegionLeft(start_addr);
|
|
vma->sliceRegionRight(end_addr);
|
|
vma->remap(new_start_addr);
|
|
|
|
/**
|
|
* The region cannot be in any more VMAs, because it is
|
|
* completely contained in this one!
|
|
*/
|
|
break;
|
|
} else if (vma->isSubset(range)) {
|
|
/**
|
|
* Just go ahead and remap it!
|
|
*/
|
|
vma->remap(vma->start() - start_addr + new_start_addr);
|
|
} else if (vma->intersects(range)) {
|
|
/**
|
|
* Create a clone of the old VMA.
|
|
*/
|
|
_vmaList.push_back(*vma);
|
|
|
|
if (vma->start() < start_addr) {
|
|
/**
|
|
* Overlaps from the right.
|
|
*/
|
|
_vmaList.back().sliceRegionRight(start_addr);
|
|
|
|
/**
|
|
* Remap the old region.
|
|
*/
|
|
vma->sliceRegionLeft(start_addr);
|
|
vma->remap(new_start_addr);
|
|
} else {
|
|
/**
|
|
* Overlaps from the left.
|
|
*/
|
|
_vmaList.back().sliceRegionLeft(end_addr);
|
|
|
|
/**
|
|
* Remap the old region.
|
|
*/
|
|
vma->sliceRegionRight(end_addr);
|
|
vma->remap(new_start_addr + vma->start() - start_addr);
|
|
}
|
|
}
|
|
|
|
vma++;
|
|
}
|
|
|
|
/**
|
|
* TLBs need to be flushed to remove any stale mappings from regions
|
|
* which were remapped. Currently the entire TLB is flushed. This results
|
|
* in functionally correct execution, but real systems do not flush all
|
|
* entries when a single mapping changes since it degrades performance.
|
|
* There is currently no general method across all TLB implementations
|
|
* that can flush just part of the address space.
|
|
*/
|
|
for (auto *tc: _ownerProcess->system->threads) {
|
|
tc->getMMUPtr()->flushAll();
|
|
}
|
|
|
|
do {
|
|
if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
|
|
_ownerProcess->pTable->remap(start_addr, _pageBytes,
|
|
new_start_addr);
|
|
|
|
start_addr += _pageBytes;
|
|
new_start_addr += _pageBytes;
|
|
|
|
/**
|
|
* The regions need to always be page-aligned otherwise the while
|
|
* condition will loop indefinitely. (The Addr type is currently
|
|
* defined to be uint64_t in src/base/types.hh; it can underflow
|
|
* since it is unsigned.)
|
|
*/
|
|
length -= _pageBytes;
|
|
} while (length > 0);
|
|
}
|
|
|
|
bool
|
|
MemState::fixupFault(Addr vaddr)
|
|
{
|
|
/**
|
|
* Check if we are accessing a mapped virtual address. If so then we
|
|
* just haven't allocated it a physical page yet and can do so here.
|
|
*/
|
|
for (const auto &vma : _vmaList) {
|
|
if (vma.contains(vaddr)) {
|
|
Addr vpage_start = roundDown(vaddr, _pageBytes);
|
|
_ownerProcess->allocateMem(vpage_start, _pageBytes);
|
|
|
|
/**
|
|
* We are assuming that fresh pages are zero-filled, so there is
|
|
* no need to zero them out when there is no backing file.
|
|
* This assumption will not hold true if/when physical pages
|
|
* are recycled.
|
|
*/
|
|
if (vma.hasHostBuf()) {
|
|
/**
|
|
* Write the memory for the host buffer contents for all
|
|
* ThreadContexts associated with this process.
|
|
*/
|
|
for (auto &cid : _ownerProcess->contextIds) {
|
|
auto *tc = _ownerProcess->system->threads[cid];
|
|
SETranslatingPortProxy
|
|
virt_mem(tc, SETranslatingPortProxy::Always);
|
|
vma.fillMemPages(vpage_start, _pageBytes, virt_mem);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the stack needs to be grown in the case where the ISAs
|
|
* process argsInit does not explicitly map the entire stack.
|
|
*
|
|
* Check if this is already on the stack and there's just no page there
|
|
* yet.
|
|
*/
|
|
if (vaddr >= _stackMin && vaddr < _stackBase) {
|
|
_ownerProcess->allocateMem(roundDown(vaddr, _pageBytes), _pageBytes);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* We've accessed the next page of the stack, so extend it to include
|
|
* this address.
|
|
*/
|
|
if (vaddr < _stackMin && vaddr >= _stackBase - _maxStackSize) {
|
|
while (vaddr < _stackMin) {
|
|
_stackMin -= _pageBytes;
|
|
if (_stackBase - _stackMin > _maxStackSize) {
|
|
fatal("Maximum stack size exceeded\n");
|
|
}
|
|
_ownerProcess->allocateMem(_stackMin, _pageBytes);
|
|
inform("Increasing stack size by one page.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Addr
|
|
MemState::extendMmap(Addr length)
|
|
{
|
|
Addr start = _mmapEnd;
|
|
|
|
if (_ownerProcess->mmapGrowsDown())
|
|
start = _mmapEnd - length;
|
|
|
|
// Look for a contiguous region of free virtual memory. We can't assume
|
|
// that the region beyond mmap_end is free because of fixed mappings from
|
|
// the user.
|
|
while (!isUnmapped(start, length)) {
|
|
DPRINTF(Vma, "memstate: cannot extend vma for mmap region at %p. "
|
|
"Virtual address range is already reserved! Skipping a page "
|
|
"and trying again!\n", start);
|
|
start = (_ownerProcess->mmapGrowsDown()) ? start - _pageBytes :
|
|
start + _pageBytes;
|
|
}
|
|
|
|
DPRINTF(Vma, "memstate: extending mmap region (old %p) (new %p)\n",
|
|
_mmapEnd,
|
|
_ownerProcess->mmapGrowsDown() ? start : start + length);
|
|
|
|
_mmapEnd = _ownerProcess->mmapGrowsDown() ? start : start + length;
|
|
|
|
return start;
|
|
}
|
|
|
|
std::string
|
|
MemState::printVmaList()
|
|
{
|
|
std::stringstream file_content;
|
|
|
|
for (auto vma : _vmaList) {
|
|
std::stringstream line;
|
|
line << std::hex << vma.start() << "-";
|
|
line << std::hex << vma.end() << " ";
|
|
line << "r-xp 00000000 00:00 0 ";
|
|
line << "[" << vma.getName() << "]" << std::endl;
|
|
file_content << line.str();
|
|
}
|
|
|
|
return file_content.str();
|
|
}
|