The running vdev (vdev trace)

Updated: October 28, 2024

Like other vdevs, the trace vdev responds to requests from a guest in its VM.

When the guest tries to access the location in guest-physical memory that has been allocated to vdev trace, the hardware virtualization support:

  1. Traps the attempt.
  2. Forces the guest to exit.
  3. Informs the hypervisor.

From the configuration of the VM hosting the guest, the hypervisor knows that the request is for vdev trace, and hands execution to the vdev.

For more information about guest exits, see the Performance Tuning chapter in the User's Guide.

Responding to requests

Just like physical devices, vdevs are designed to respond to specific requests from a device driver. Below are brief descriptions of the vdev trace functions that respond to requests from the guest-app pseudo-driver, which writes to the vdev's memory space just as if it were writing to a hardware device's memory space.

vdtrace_control()

The vdtrace_control() function is specified by the vdtrace_factory structure's control member:

static struct vdev_factory vdtrace_factory = {
    .next = NULL, // patched
    .control = vdtrace_control,
    ...

This function uses pre-defined callbacks (see Definitions in vdev-core.h in the API Reference). During startup, it looks after parsing the configuration input that will be used to populate the vdev's factory structure (vdtrace_factory). After startup is complete, it looks after control operations, such as reporting its status when the qvm process calls the VDEV_CTRL_STATUS callback.

Notice that the vdev_thread_create() call is after VDEV_CTRL_GUEST_CONFIGURED, because this function may be called only after the vdev and its VM are fully configured (see Function Safety in the Virtual Device Developer's API Reference).

vdtrace_vread() and vdtrace_vwrite()

The vdtrace_vread() function is specified by the vdtrace_factory structure's vread member:

static struct vdev_factory vdtrace_factory = {
    ...
    .control = vdtrace_control,
    .vread = vdtrace_vread,
    ...

This function reads the value of the counter that is created by the vdtrace_control() function's call to vdev_thread_create(), and updated by vdtrace_thread() approximately every second.

Note that vdtrace_vread() calls the guest_cpu_write() function, which writes data into guest memory. Just as when a device driver reads from a physical device, the physical device writes data to memory accessible to the driver, when a device driver in a guest attempts to read from a vdev, the vdev must write the requested data to memory accessible to the guest's device driver.

The vdtrace_vwrite() function is specified by the vdtrace_factory structure's vwrite member:

static struct vdev_factory vdtrace_factory = {
	...
	.vread = vdtrace_vread,
	.vwrite = vdtrace_vwrite,
	...

This function writes the value of the counter updated by vdtrace_thread() approximately every second.

Notice that vdtrace_vwrite() calls the guest_cpu_read() function, which reads data from a specified vCPU. When the guest's device driver writes data, the vdev must read it.

For more information, see Reading and writing in a vdev in the “About Writing Vdevs” chapter.

vdtrace_thread()

The vdtrace_thread() function increments a counter, then sleeps approximately one second, then begins again. It calls pthread_mutex_lock() to lock a mutex, increments the counter, then calls pthread_mutex_unlock(). The mutex is not strictly required, but it is used here simply to show how mutexes can be used.

After it has used mmap() to map guest-physical memory into its address space, the guest-app pseudo-driver (or another driver) in the guest can read the value of the counter from vdev trace's location in guest-physical memory, just as in an unvirtualized system it would read a value from a physical device's location in physical (host-physical) memory.

static void * vdtrace_thread(void *arg)
{
    vdev_t *vdp = arg;
    struct trace_state * const state = vdp->v_device;

    while (1) {
        /* Using a mutex here is overkill, but it shows how to use the provided mutex. */
        pthread_mutex_lock(&vdp->v_mtx);
        state->counter++;
        pthread_mutex_unlock(&vdp->v_mtx);
        usleep(999999);
    }
    return NULL; // Execution should never reach here.
}
  翻译: