Program Listing for File xlator.h
↰ Return to documentation for file (include/embers/helpers/xlator.h
)
/* Copyright © 2020 Advanced Micro Devices, Inc. All rights reserved */
#ifndef XLATOR_H
#define XLATOR_H
#include <iostream>
#include <fstream>
#include <memory>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <shared_mutex>
#include <unordered_map>
namespace embers
{
class Xlator
{
struct Translation {
uintptr_t pa;
bool ok;
};
private:
const uintptr_t pagesize_;
const pid_t pid_;
const uid_t euid_;
std::unordered_map<uintptr_t, uintptr_t> xlate_cache_;
mutable std::shared_mutex mutex_; // RW-lock to protect the xlate_cache_
// The layout of each entry in a process' virtual pages as defined in
// `/proc/[pid]/pagemap` - see `man 5 proc` or consult the online manpages:
// https://man7.org/linux/man-pages/man5/proc.5.html. We only care about if
// a particular entry is present and its page frame number (pfn). More info
// is available in the entry.
union pagemap_entry {
uint64_t val;
struct {
uint64_t pfn : 55; // 54:0
uint64_t other_bits : 8; // 55:62
uint64_t present : 1; // 63
};
};
pagemap_entry get_pagemap_entry(std::ifstream &pagemap_file, const uintptr_t va) const
{
pagemap_entry pm_ent;
pm_ent.present = false;
pm_ent.pfn = 0xdead;
// Calculate the vpn (virtual page number) from va (virtual address)
uint64_t vpn = va / pagesize_;
// Calculate the offset into pagemap file using vpn to find our entry
std::streampos offset = vpn * sizeof(pm_ent);
// Lookup and return pagemap_entry
pagemap_file.seekg(offset);
if (pagemap_file.tellg() != offset) {
std::cerr << "WARN: pos after seek: " << std::to_string(pagemap_file.tellg()) << "\n";
}
pagemap_file.read(reinterpret_cast<char *>(&pm_ent), sizeof(pm_ent));
// Ensure entry is marked "Present in RAM"
if (!pm_ent.present) {
std::cerr << "WARN: page not in ram!" << "\n";
return pm_ent;
}
return pm_ent;
}
Translation translate(uintptr_t va)
{
std::ifstream pagemap_file;
pagemap_file.rdbuf()->pubsetbuf(0, 0); // Prevents reading more bytes than requested
pagemap_file.open(std::string("/proc/") + std::to_string(pid_) + std::string("/pagemap"));
const auto ent = get_pagemap_entry(pagemap_file, va);
if (!ent.present) {
return Translation{.pa = 0, .ok = false};
}
// Calculate the `pa` (physical address) using the pagemap entry's
// pfn, our system's `pagesize_`, and the va (for the byte index into the
// page in the resultant physical address).
uint64_t pa = (ent.pfn * pagesize_) | (va % pagesize_);
{
std::unique_lock lock(mutex_); // Wr-lock
// Update translation cache
xlate_cache_[va & ~(pagesize_ - 1)] = pa & ~(pagesize_ - 1);
}
return Translation{.pa = pa, .ok = true};
}
public:
Xlator() : pagesize_(sysconf(_SC_PAGE_SIZE)), pid_(getpid()), euid_(geteuid()) {}
~Xlator() {}
void ClearCache()
{
std::unique_lock lock(mutex_); // Wr-lock
xlate_cache_.clear();
}
// Translation via the pagemap requires 'root' permissions
bool CanTranslate() { return euid_ == 0; }
// @note
// IMPORTANT: Page mappings can change arbitrarily at runtime due to swap
// and kernel mm mechanics (copy-on-write, etc). To ensure the
// most 'stable' (but no guarantees!) page mapping:
//
// * Prefer using pinned pages
// * For READ-ONLY pages: read an address from the page first
// * For WRITE-ONLY pages: write an address in the page first
//
// Caller can load the VA content into a volatile variable
// prior to calling VAtoPA to help ensure the page mapping
// will exist so that a valid PA can be returned.
//
Translation VAtoPA(const uintptr_t va, const bool bypass_xlate_cache = false)
{
if (!CanTranslate()) {
return Translation{.pa = 0, .ok = false};
}
if (bypass_xlate_cache) {
return translate(va);
}
{
std::shared_lock lock(mutex_); // Rd-lock
const auto it = xlate_cache_.find(va & ~(pagesize_ - 1));
if (it != xlate_cache_.end()) {
return Translation{.pa = it->second | (va % pagesize_), .ok = true};
}
}
return translate(va);
}
};
} // namespace embers
#endif // XLATOR_H