| // 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 |