blob: 9cc9013cbd08df64fc0bd4058407d36af3357a5a [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "user-pager.h"
#include <limits.h>
#include <zircon/status.h>
#include <memory>
#include <blobfs/format.h>
#include <fbl/auto_call.h>
#include <fs/trace.h>
namespace blobfs {
zx_status_t UserPager::InitPager() {
TRACE_DURATION("blobfs", "UserPager::InitPager");
// Make sure blocks are page-aligned.
static_assert(kBlobfsBlockSize % PAGE_SIZE == 0);
// Make sure the pager transfer buffer is block-aligned.
static_assert(kTransferBufferSize % kBlobfsBlockSize == 0);
// Set up the pager transfer buffer.
zx_status_t status = zx::vmo::create(kTransferBufferSize, 0, &transfer_buffer_);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Cannot create transfer buffer: %s\n", zx_status_get_string(status));
return status;
}
status = AttachTransferVmo(transfer_buffer_);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Failed to attach transfer vmo: %s\n", zx_status_get_string(status));
return status;
}
// Create the pager.
status = zx::pager::create(0, &pager_);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Cannot initialize pager\n");
return status;
}
// Start the pager thread.
status = pager_loop_.StartThread("blobfs-pager-thread");
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Could not start pager thread\n");
return status;
}
return ZX_OK;
}
UserPager::ReadRange UserPager::ExtendReadRange(UserPagerInfo* info, uint64_t offset,
uint64_t length) {
// TODO(rashaeqbal): Make the cluster size dynamic once we have prefetched read efficiency
// metrics from the kernel - what percentage of prefetched pages are actually used.
//
// For now read read in at least 128KB (if the blob is larger than 128KB). 128KB is completely
// arbitrary. Tune this for optimal performance (until we can support dynamic prefetch sizing).
//
// TODO(rashaeqbal): Consider extending the range backwards as well. Will need some way to track
// populated ranges.
constexpr uint64_t kReadAheadClusterSize = (128 * (1 << 10));
size_t read_ahead_offset;
size_t read_ahead_length;
if (info->compression_algorithm == CompressionAlgorithm::ZSTD_SEEKABLE) {
// ZSTD Seekable frames are calibrated to have boundaries every |kReadAheadClusterSize|.
read_ahead_offset = fbl::round_down(offset, kReadAheadClusterSize);
read_ahead_length = fbl::round_up(offset + length, kReadAheadClusterSize) - read_ahead_offset;
} else {
read_ahead_offset = offset;
read_ahead_length = fbl::max(kReadAheadClusterSize, length);
}
read_ahead_length = fbl::min(read_ahead_length, info->data_length_bytes - read_ahead_offset);
// Align to the block size for verification. (In practice this means alignment to 8k).
zx_status_t status = info->verifier->Align(&read_ahead_offset, &read_ahead_length);
// This only happens if the info->verifier thinks that [offset,length) is out of range, which
// will only happen if |verifier| was initialized with a different length than the rest of |info|
// (which is a programming error).
ZX_DEBUG_ASSERT(status == ZX_OK);
ZX_DEBUG_ASSERT(read_ahead_offset % kBlobfsBlockSize == 0);
ZX_DEBUG_ASSERT(read_ahead_length % kBlobfsBlockSize == 0 ||
read_ahead_offset + read_ahead_length == info->data_length_bytes);
return {.offset = read_ahead_offset, .length = read_ahead_length};
}
zx_status_t UserPager::TransferPagesToVmo(uint64_t requested_offset, uint64_t requested_length,
const zx::vmo& vmo, UserPagerInfo* info) {
ZX_DEBUG_ASSERT(info);
size_t end;
if (add_overflow(requested_offset, requested_length, &end)) {
FS_TRACE_ERROR("blobfs: Transfer range would overflow (off=%lu, len=%lu)\n", requested_offset,
requested_length);
return ZX_ERR_OUT_OF_RANGE;
}
const auto [offset, length] = ExtendReadRange(info, requested_offset, requested_length);
TRACE_DURATION("blobfs", "UserPager::TransferPagesToVmo", "offset", offset, "length", length);
auto decommit = fbl::MakeAutoCall([this, length = length]() {
// Decommit pages in the transfer buffer that might have been populated. All blobs share the
// same transfer buffer - this prevents data leaks between different blobs.
transfer_buffer_.op_range(ZX_VMO_OP_DECOMMIT, 0, fbl::round_up(length, kBlobfsBlockSize),
nullptr, 0);
});
// Read from storage into the transfer buffer.
zx_status_t status = PopulateTransferVmo(offset, length, info);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Failed to populate transfer vmo: %s\n", zx_status_get_string(status));
return status;
}
// Verify the pages read in.
status = VerifyTransferVmo(offset, length, transfer_buffer_, info);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Failed to verify transfer vmo: %s\n", zx_status_get_string(status));
return status;
}
ZX_DEBUG_ASSERT(offset % PAGE_SIZE == 0);
// Move the pages from the transfer buffer to the destination VMO.
status = pager_.supply_pages(vmo, offset, fbl::round_up<uint64_t, uint64_t>(length, PAGE_SIZE),
transfer_buffer_, 0);
if (status != ZX_OK) {
FS_TRACE_ERROR("blobfs: Failed to supply pages to paged VMO: %s\n",
zx_status_get_string(status));
return status;
}
return ZX_OK;
}
} // namespace blobfs