* Copyright 2010-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "CachedDataReader.h"
#include <algorithm>
#include <DataIO.h>
#include <util/AutoLock.h>
#include <vm/VMCache.h>
#include <vm/vm_page.h>
#include "DebugSupport.h"
using BPackageKit::BHPKG::BBufferDataReader;
static inline bool
page_physical_number_less(const vm_page* a, const vm_page* b)
{
return a->physical_page_number < b->physical_page_number;
}
struct CachedDataReader::PagesDataOutput : public BDataIO {
PagesDataOutput(vm_page** pages, size_t pageCount)
:
fPages(pages),
fPageCount(pageCount),
fInPageOffset(0)
{
}
virtual ssize_t Write(const void* buffer, size_t size)
{
size_t bytesRemaining = size;
while (bytesRemaining > 0) {
if (fPageCount == 0)
return B_BAD_VALUE;
size_t toCopy = std::min(bytesRemaining,
B_PAGE_SIZE - fInPageOffset);
status_t error = vm_memcpy_to_physical(
fPages[0]->physical_page_number * B_PAGE_SIZE + fInPageOffset,
buffer, toCopy, false);
if (error != B_OK)
return error;
fInPageOffset += toCopy;
if (fInPageOffset == B_PAGE_SIZE) {
fInPageOffset = 0;
fPages++;
fPageCount--;
}
buffer = (const char*)buffer + toCopy;
bytesRemaining -= toCopy;
}
return size;
}
private:
vm_page** fPages;
size_t fPageCount;
size_t fInPageOffset;
};
CachedDataReader::CachedDataReader()
:
fReader(NULL),
fCache(NULL),
fCacheLineLockers()
{
mutex_init(&fLock, "packagefs cached reader");
}
CachedDataReader::~CachedDataReader()
{
if (fCache != NULL) {
fCache->Lock();
fCache->ReleaseRefAndUnlock();
}
mutex_destroy(&fLock);
}
status_t
CachedDataReader::Init(BAbstractBufferedDataReader* reader, off_t size)
{
fReader = reader;
status_t error = fCacheLineLockers.Init();
if (error != B_OK)
RETURN_ERROR(error);
error = VMCacheFactory::CreateNullCache(VM_PRIORITY_SYSTEM,
fCache);
if (error != B_OK)
RETURN_ERROR(error);
fCache->virtual_end = size;
return B_OK;
}
status_t
CachedDataReader::ReadDataToOutput(off_t offset, size_t size,
BDataIO* output)
{
if (offset > fCache->virtual_end
|| (off_t)size > fCache->virtual_end - offset) {
return B_BAD_VALUE;
}
if (size == 0)
return B_OK;
while (size > 0) {
off_t lineOffset = (offset / kCacheLineSize) * kCacheLineSize;
off_t cacheLineEnd = std::min(lineOffset + (off_t)kCacheLineSize,
fCache->virtual_end);
size_t requestLineLength
= std::min(cacheLineEnd - offset, (off_t)size);
status_t error = _ReadCacheLine(lineOffset, cacheLineEnd - lineOffset,
offset, requestLineLength, output);
if (error != B_OK)
return error;
offset = cacheLineEnd;
size -= requestLineLength;
}
return B_OK;
}
status_t
CachedDataReader::_ReadCacheLine(off_t lineOffset, size_t lineSize,
off_t requestOffset, size_t requestLength, BDataIO* output)
{
PRINT("CachedDataReader::_ReadCacheLine(%" B_PRIdOFF ", %zu, %" B_PRIdOFF
", %zu, %p\n", lineOffset, lineSize, requestOffset, requestLength,
output);
CacheLineLocker cacheLineLocker(this, lineOffset);
page_num_t firstPageOffset = lineOffset / B_PAGE_SIZE;
page_num_t linePageCount = (lineSize + B_PAGE_SIZE - 1) / B_PAGE_SIZE;
vm_page* pages[kPagesPerCacheLine] = {};
AutoLocker<VMCache> cacheLocker(fCache);
page_num_t firstMissing = 0;
page_num_t lastMissing = 0;
page_num_t missingPages = 0;
page_num_t pageOffset = firstPageOffset;
VMCachePagesTree::Iterator it = fCache->pages.GetIterator(pageOffset, true,
true);
while (pageOffset < firstPageOffset + linePageCount) {
vm_page* page = it.Next();
page_num_t currentPageOffset;
if (page == NULL
|| page->cache_offset >= firstPageOffset + linePageCount) {
page = NULL;
currentPageOffset = firstPageOffset + linePageCount;
} else
currentPageOffset = page->cache_offset;
if (pageOffset < currentPageOffset) {
if (missingPages == 0)
firstMissing = pageOffset;
lastMissing = currentPageOffset - 1;
missingPages += currentPageOffset - pageOffset;
for (; pageOffset < currentPageOffset; pageOffset++)
pages[pageOffset - firstPageOffset] = NULL;
}
if (page != NULL) {
pages[pageOffset++ - firstPageOffset] = page;
DEBUG_PAGE_ACCESS_START(page);
vm_page_set_state(page, PAGE_STATE_UNUSED);
DEBUG_PAGE_ACCESS_END(page);
}
}
cacheLocker.Unlock();
if (missingPages > 0) {
vm_page_reservation reservation;
if (!vm_page_try_reserve_pages(&reservation, missingPages,
VM_PRIORITY_USER)) {
_DiscardPages(pages, firstMissing - firstPageOffset, missingPages);
return fReader->ReadDataToOutput(requestOffset, requestLength,
output);
}
for (pageOffset = firstMissing; pageOffset <= lastMissing;
pageOffset++) {
page_num_t index = pageOffset - firstPageOffset;
if (pages[index] == NULL) {
pages[index] = vm_page_allocate_page(&reservation,
PAGE_STATE_UNUSED);
DEBUG_PAGE_ACCESS_END(pages[index]);
} else {
cacheLocker.Lock();
fCache->RemovePage(pages[index]);
cacheLocker.Unlock();
}
}
missingPages = lastMissing - firstMissing + 1;
cacheLocker.Lock();
for (pageOffset = firstMissing; pageOffset <= lastMissing;
pageOffset++) {
page_num_t index = pageOffset - firstPageOffset;
fCache->InsertPage(pages[index], (off_t)pageOffset * B_PAGE_SIZE);
}
cacheLocker.Unlock();
status_t error = _ReadIntoPages(pages, firstMissing - firstPageOffset,
missingPages);
if (error != B_OK) {
ERROR("CachedDataReader::_ReadCacheLine(): Failed to read into "
"cache (offset: %" B_PRIdOFF ", length: %" B_PRIuSIZE "), "
"trying uncached read (offset: %" B_PRIdOFF ", length: %"
B_PRIuSIZE ")\n", (off_t)firstMissing * B_PAGE_SIZE,
(size_t)missingPages * B_PAGE_SIZE, requestOffset,
requestLength);
_DiscardPages(pages, firstMissing - firstPageOffset, missingPages);
return fReader->ReadDataToOutput(requestOffset, requestLength,
output);
}
}
status_t error = _WritePages(pages, requestOffset - lineOffset,
requestLength, output);
_CachePages(pages, 0, linePageCount);
return error;
}
\c NULL entries in the range are OK. All non \c NULL entries must refer
to pages with \c PAGE_STATE_UNUSED. The pages may belong to \c fCache or
may not have a cache.
\c fCache must not be locked.
*/
void
CachedDataReader::_DiscardPages(vm_page** pages, size_t firstPage,
size_t pageCount)
{
PRINT("%p->CachedDataReader::_DiscardPages(%" B_PRIuSIZE ", %" B_PRIuSIZE
")\n", this, firstPage, pageCount);
AutoLocker<VMCache> cacheLocker(fCache);
for (size_t i = firstPage; i < firstPage + pageCount; i++) {
vm_page* page = pages[i];
if (page == NULL)
continue;
DEBUG_PAGE_ACCESS_START(page);
ASSERT_PRINT(page->State() == PAGE_STATE_UNUSED,
"page: %p @! page -m %p", page, page);
if (page->Cache() != NULL)
fCache->RemovePage(page);
vm_page_free(NULL, page);
}
}
There must not be any \c NULL entries in the given array range. All pages
must belong to \c cache and have state \c PAGE_STATE_UNUSED.
\c fCache must not be locked.
*/
void
CachedDataReader::_CachePages(vm_page** pages, size_t firstPage,
size_t pageCount)
{
PRINT("%p->CachedDataReader::_CachePages(%" B_PRIuSIZE ", %" B_PRIuSIZE
")\n", this, firstPage, pageCount);
AutoLocker<VMCache> cacheLocker(fCache);
for (size_t i = firstPage; i < firstPage + pageCount; i++) {
vm_page* page = pages[i];
ASSERT(page != NULL);
ASSERT_PRINT(page->State() == PAGE_STATE_UNUSED
&& page->Cache() == fCache,
"page: %p @! page -m %p", page, page);
DEBUG_PAGE_ACCESS_START(page);
vm_page_set_state(page, PAGE_STATE_CACHED);
DEBUG_PAGE_ACCESS_END(page);
}
}
\param pages The pages array.
\param pagesRelativeOffset The offset relative to \a pages[0] where to
start writing from.
\param requestLength The number of bytes to write.
\param output The output to which the data shall be written.
\return \c B_OK, if writing went fine, another error code otherwise.
*/
status_t
CachedDataReader::_WritePages(vm_page** pages, size_t pagesRelativeOffset,
size_t requestLength, BDataIO* output)
{
PRINT("%p->CachedDataReader::_WritePages(%" B_PRIuSIZE ", %" B_PRIuSIZE
", %p)\n", this, pagesRelativeOffset, requestLength, output);
size_t firstPage = pagesRelativeOffset / B_PAGE_SIZE;
size_t endPage = (pagesRelativeOffset + requestLength + B_PAGE_SIZE - 1)
/ B_PAGE_SIZE;
size_t inPageOffset = pagesRelativeOffset % B_PAGE_SIZE;
for (size_t i = firstPage; i < endPage; i++) {
void* handle;
addr_t address;
status_t error = vm_get_physical_page(
pages[i]->physical_page_number * B_PAGE_SIZE, &address,
&handle);
if (error != B_OK)
return error;
size_t toCopy = std::min(B_PAGE_SIZE - inPageOffset, requestLength);
error = output->WriteExactly((uint8*)(address + inPageOffset), toCopy);
vm_put_physical_page(address, handle);
if (error != B_OK)
return error;
inPageOffset = 0;
requestLength -= toCopy;
}
return B_OK;
}
status_t
CachedDataReader::_ReadIntoPages(vm_page** pages, size_t firstPage,
size_t pageCount)
{
PagesDataOutput output(pages + firstPage, pageCount);
off_t firstPageOffset = (off_t)pages[firstPage]->cache_offset
* B_PAGE_SIZE;
generic_size_t requestLength = std::min(
firstPageOffset + (off_t)pageCount * B_PAGE_SIZE,
fCache->virtual_end)
- firstPageOffset;
return fReader->ReadDataToOutput(firstPageOffset, requestLength, &output);
}
void
CachedDataReader::_LockCacheLine(CacheLineLocker* lineLocker)
{
MutexLocker locker(fLock);
CacheLineLocker* otherLineLocker
= fCacheLineLockers.Lookup(lineLocker->Offset());
if (otherLineLocker == NULL) {
fCacheLineLockers.Insert(lineLocker);
return;
}
otherLineLocker->Queue().Add(lineLocker);
lineLocker->Wait(fLock);
}
void
CachedDataReader::_UnlockCacheLine(CacheLineLocker* lineLocker)
{
MutexLocker locker(fLock);
fCacheLineLockers.Remove(lineLocker);
if (CacheLineLocker* nextLineLocker = lineLocker->Queue().RemoveHead()) {
nextLineLocker->Queue().TakeFrom(&lineLocker->Queue());
fCacheLineLockers.Insert(nextLineLocker);
nextLineLocker->WakeUp();
}
}