blob: d1d5bf25d887a00f307249d5836ce4610cc5c671 [file] [log] [blame] [edit]
// Copyright 2017 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 <dlfcn.h>
#include <benchmark/benchmark.h>
#include <launchpad/launchpad.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
namespace {
constexpr char pname[] = "bench-process";
constexpr char tname[] = "bench-thread";
// The function is the entry point for the child process. It is copied into the child process via
// zx_vmo_write() so it must have no dependencies (other than zx_thread_exit()).
void call_exit(zx_handle_t unused, uintptr_t thread_exit_addr) {
__typeof(zx_thread_exit)* thread_exit = (__typeof(zx_thread_exit)*)thread_exit_addr;
(*thread_exit)();
}
// Computes the stack pointer. Modeled after zircon/stack.h.
uintptr_t compute_stack_pointer(uintptr_t stack_base, size_t stack_size) {
uintptr_t sp = stack_base + stack_size;
sp &= -16;
#ifdef __x86_64__
sp -= 8;
#elif defined(__arm__) || defined(__aarch64__)
#else
#error unknown machine
#endif
return sp;
}
class Process : public benchmark::Fixture {
private:
void SetUp(benchmark::State& state) override;
protected:
// Initializes a minimal process that when started simply calls zx_thread_exit.
//
// Should be called once per benchmark iteration after zx_process_create(), but before
// zx_process_start().
bool InitChildProcess(benchmark::State& state);
// Closes handles and frees resources.
//
// Should be called once per benchmark iteration.
bool CloseHandles(benchmark::State& state);
// Offset of the zx_thread_exit() syscall from the start of the vDSO.
uintptr_t thread_exit_offset = 0;
// Base address of child process's stack. Also serves as process entry point.
zx_vaddr_t stack_base = 0;
uintptr_t sp = 0;
// Address in child process of zx_thread_exit() syscall.
uintptr_t thread_exit_addr = 0;
zx_handle_t proc_handle = ZX_HANDLE_INVALID;
zx_handle_t vmar_handle = ZX_HANDLE_INVALID;
zx_handle_t thread_handle = ZX_HANDLE_INVALID;
zx_handle_t stack_vmo = ZX_HANDLE_INVALID;
zx_handle_t vdso_vmo = ZX_HANDLE_INVALID;
zx_handle_t channel = ZX_HANDLE_INVALID;
zx_handle_t channel_to_transfer = ZX_HANDLE_INVALID;
};
void Process::SetUp(benchmark::State& state) {
// The child process will simply call zx_thread_exit() so we need to know the address of the
// syscall in the child's addres space. We'll compute that by finding its offset in the vDSO and
// later adding the offset to the vDSO's base address.
Dl_info dl_info;
if (dladdr(reinterpret_cast<void*>(&zx_thread_exit), &dl_info) == 0) {
state.SkipWithError("Failed to get address of syscall");
return;
}
thread_exit_offset = (uintptr_t)dl_info.dli_saddr - (uintptr_t)dl_info.dli_fbase;
}
bool Process::InitChildProcess(benchmark::State& state) {
// Initialization of the child process is modeled after mini-process.
// In order to make a syscall, the child needs to have the vDSO mapped. Launchpad makes this
// easy. Use launchpad to map the vDSO into the child process and compute the address of
// zx_thread_exit(). Since launchpad takes ownership of the handles passed to
// launchpad_create_with_process, duplicate them first so we can destroy the launchpad once the
// vDSO is mapped.
zx_handle_t lp_proc_handle = ZX_HANDLE_INVALID;
if (zx_handle_duplicate(proc_handle, ZX_RIGHT_SAME_RIGHTS, &lp_proc_handle) != ZX_OK) {
state.SkipWithError("Failed to duplicate proc_handle");
return false;
}
zx_handle_t lp_vmar_handle = ZX_HANDLE_INVALID;
if (zx_handle_duplicate(vmar_handle, ZX_RIGHT_SAME_RIGHTS, &lp_vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to duplicate vmar_handle");
return false;
}
launchpad_t* lp = NULL;
if (launchpad_create_with_process(lp_proc_handle, lp_vmar_handle, &lp) != ZX_OK) {
state.SkipWithError("Failed to create launchpad");
return false;
}
if (launchpad_get_vdso_vmo(&vdso_vmo) != ZX_OK) {
state.SkipWithError("Failed to get vDSO");
return false;
}
zx_vaddr_t vdso_base;
if (launchpad_elf_load_extra(lp, vdso_vmo, &vdso_base, NULL) != ZX_OK) {
state.SkipWithError("Failed to load vDSO");
return false;
}
launchpad_destroy(lp);
thread_exit_addr = vdso_base + thread_exit_offset;
// The child process needs a stack and some code to execute. Create a stack and copy the body of
// call_exit() to the bottom of the stack.
constexpr uint32_t stack_perm =
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_PERM_EXECUTE;
// Must be larger than the machine code of call_exit() and smaller than the stack.
constexpr size_t num_to_copy = 1024;
constexpr uint64_t stack_size = 4096;
if (zx_vmo_create(stack_size, 0, &stack_vmo) != ZX_OK) {
state.SkipWithError("Failed to create vmo");
return false;
}
size_t actual = 0;
if (zx_vmo_write(stack_vmo, reinterpret_cast<void*>(&call_exit), 0, num_to_copy, &actual) !=
ZX_OK) {
state.SkipWithError("Failed to write vmo");
return false;
}
if (actual != num_to_copy) {
state.SkipWithError("Failed to fully write vmo");
return false;
}
if (zx_vmar_map(vmar_handle, 0, stack_vmo, 0, stack_size, stack_perm, &stack_base) != ZX_OK) {
state.SkipWithError("Failed to map vmo");
return false;
}
sp = compute_stack_pointer(stack_base, stack_size);
// The child process needs a thread.
if (zx_thread_create(proc_handle, tname, sizeof(tname), 0, &thread_handle) != ZX_OK) {
state.SkipWithError("Failed to create thread");
return false;
}
// It will also need a channel to its parent even though it won't use it.
if (zx_channel_create(0, &channel, &channel_to_transfer) != ZX_OK) {
state.SkipWithError("Failed to create channel");
return false;
}
return true;
}
bool Process::CloseHandles(benchmark::State& state) {
if (proc_handle != ZX_HANDLE_INVALID) {
if (zx_handle_close(proc_handle) != ZX_OK) {
state.SkipWithError("Failed to close proc_handle");
return false;
}
proc_handle = ZX_HANDLE_INVALID;
}
if (vmar_handle != ZX_HANDLE_INVALID) {
if (zx_handle_close(vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to close vmar_handle");
return false;
}
vmar_handle = ZX_HANDLE_INVALID;
}
if (thread_handle != ZX_HANDLE_INVALID) {
if (zx_handle_close(thread_handle) != ZX_OK) {
state.SkipWithError("Failed to close thread_handle");
return false;
}
thread_handle = ZX_HANDLE_INVALID;
}
if (stack_vmo != ZX_HANDLE_INVALID) {
if (zx_handle_close(stack_vmo) != ZX_OK) {
state.SkipWithError("Failed to close stack_vmo");
return false;
}
stack_vmo = ZX_HANDLE_INVALID;
}
if (vdso_vmo != ZX_HANDLE_INVALID) {
if (zx_handle_close(vdso_vmo) != ZX_OK) {
state.SkipWithError("Failed to close vdso_vmo");
return false;
}
vdso_vmo = ZX_HANDLE_INVALID;
}
if (channel != ZX_HANDLE_INVALID) {
if (zx_handle_close(channel) != ZX_OK) {
state.SkipWithError("Failed to close channel");
return false;
}
channel = ZX_HANDLE_INVALID;
}
if (channel_to_transfer != ZX_HANDLE_INVALID) {
if (zx_handle_close(channel_to_transfer) != ZX_OK) {
state.SkipWithError("Failed to close channel_to_transfer");
return false;
}
channel_to_transfer = ZX_HANDLE_INVALID;
}
return true;
}
// This benchmark measures zx_create_process(). Note, the process is not started.
BENCHMARK_F(Process, Create)(benchmark::State& state) {
zx_handle_t job = zx_job_default();
while (state.KeepRunning()) {
if (zx_process_create(job, pname, sizeof(pname), 0, &proc_handle, &vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to create process");
return;
}
state.PauseTiming();
if (!CloseHandles(state)) {
return;
}
state.ResumeTiming();
}
}
// This benchmark measures zx_start_process().
BENCHMARK_F(Process, Start)(benchmark::State& state) {
zx_handle_t job = zx_job_default();
while (state.KeepRunning()) {
state.PauseTiming();
if (zx_process_create(job, pname, sizeof(pname), 0, &proc_handle, &vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to create process");
return;
}
if (!InitChildProcess(state)) {
return;
}
state.ResumeTiming();
if (zx_process_start(proc_handle, thread_handle, stack_base, sp, channel_to_transfer,
thread_exit_addr) != ZX_OK) {
state.SkipWithError("Failed to start");
return;
}
state.PauseTiming();
channel_to_transfer = ZX_HANDLE_INVALID;
if (zx_object_wait_one(thread_handle, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL) != ZX_OK) {
state.SkipWithError("Failed to wait on child");
return;
}
if (!CloseHandles(state)) {
return;
}
state.ResumeTiming();
}
}
// This benchmark measures creating and starting a minimal process. Note, it does not wait for
// for the process to terminate.
BENCHMARK_F(Process, CreateStart)(benchmark::State& state) {
zx_handle_t job = zx_job_default();
while (state.KeepRunning()) {
if (zx_process_create(job, pname, sizeof(pname), 0, &proc_handle, &vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to create process");
return;
}
state.PauseTiming();
if (!InitChildProcess(state)) {
return;
}
state.ResumeTiming();
if (zx_process_start(proc_handle, thread_handle, stack_base, sp, channel_to_transfer,
thread_exit_addr) != ZX_OK) {
state.SkipWithError("Failed to start");
return;
}
state.PauseTiming();
channel_to_transfer = ZX_HANDLE_INVALID;
if (zx_object_wait_one(thread_handle, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL) != ZX_OK) {
state.SkipWithError("Failed to wait on child");
return;
}
if (!CloseHandles(state)) {
return;
}
state.ResumeTiming();
}
}
// This benchmark measures creating, starting, and waiting for completion of a minimal process.
BENCHMARK_F(Process, CreateStartWait)(benchmark::State& state) {
zx_handle_t job = zx_job_default();
while (state.KeepRunning()) {
if (zx_process_create(job, pname, sizeof(pname), 0, &proc_handle, &vmar_handle) != ZX_OK) {
state.SkipWithError("Failed to create process");
return;
}
state.PauseTiming();
if (!InitChildProcess(state)) {
return;
}
state.ResumeTiming();
if (zx_process_start(proc_handle, thread_handle, stack_base, sp, channel_to_transfer,
thread_exit_addr) != ZX_OK) {
state.SkipWithError("Failed to start");
return;
}
channel_to_transfer = ZX_HANDLE_INVALID;
if (zx_object_wait_one(thread_handle, ZX_TASK_TERMINATED, ZX_TIME_INFINITE, NULL) != ZX_OK) {
state.SkipWithError("Failed to wait on child");
return;
}
state.PauseTiming();
if (!CloseHandles(state)) {
return;
}
state.ResumeTiming();
}
}
} // namespace