Compartir a través de


Intercambio de datos: uso de pares clave-valor para compartir información entre el host y el invitado en Hyper-V

Data Exchange es un servicio de integración (también conocido como intercambio de pares clave-valor o KVP) que se puede usar para compartir pequeños fragmentos de información entre una máquina virtual (invitado) y su host de Hyper-V. La información general sobre la máquina virtual y el host se crean y almacenan automáticamente como pares clave-valor. También puede crear sus propios pares para compartir datos personalizados.

Los pares clave-valor constan de una "clave" y un "valor". Ambos son cadenas; no se admite ningún otro tipo de datos. Cuando se crea o cambia un par clave-valor, se vuelve visible tanto para el invitado como para el host. Los datos de KVP viajan a través de la Hyper-V VMbus y no requieren ninguna conectividad de red entre el invitado y el host.

Una vez creados, los pares clave-valor permanecen hasta que se eliminan. Cualquier aplicación que cree pares clave-valor debe eliminarlos cuando ya no sean necesarios. Los pares clave-valor se mueven con la máquina virtual durante la migración en vivo.

Invitados de Windows

En los invitados de Windows, los datos KVP se almacenan en el registro en:

HKLM\SOFTWARE\Microsoft\Virtual Machine

Los datos se organizan en estas subclaves:

  • Máquina virtual\Auto : datos que describen al sistema invitado. Creado por los controladores de servicio de integración después de cargarse. Visible para el host como datos intrínsecos.
  • Máquina virtual\Externo – Datos transferidos al invitado desde el host por un usuario.
  • Máquina virtual\Invitado : datos creados en el invitado. Visible para el host como datos no intrínsecos.
  • Máquina virtual\Invitado\Parámetro: datos transferidos al invitado desde el host que describen al host.

Agregar valores desde dentro del entorno invitado es tan sencillo como crear un nuevo valor de cadena en HKLM\SOFTWARE\Microsoft\Virtual Machine\Guest. Debe ser administrador en el sistema virtual para modificar esta ubicación. Puede usar WMI (PowerShell u otras herramientas) desde el host o una máquina remota (con permisos) para recuperar el valor.

Para obtener información sobre los límites de tamaño del Registro, consulte el artículo (heredado) Límites de tamaño de elemento del Registro.

Agregar un nuevo par clave-valor en el sistema operático huésped

En este ejemplo, el valor de Status se establece en Ready:

$regPath = "HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest"
Set-ItemProperty -Path $regPath -Name "Status" -Value "Ready" -Type String

Puede usar la misma sintaxis para cambiar el valor.

Consultar pares de clave-valor en el sistema invitado

Para consultar el valor de la subclave Externo (datos transferidos al invitado desde el anfitrión):

$regPath = "HKLM:\SOFTWARE\Microsoft\Virtual Machine\External"
Get-ItemProperty -Path $regPath -Name "Name"

Invitados de Linux

Linux no tiene un registro, por lo que los elementos KVP se almacenan en el sistema de archivos. Un proceso de demonio, hv_kvp_daemon, debe ejecutarse para controlar el procesamiento. Para la mayoría de las distribuciones con Linux Integration Services (LIS) o controladores en kernel instalados, este demonio se inicia automáticamente. En algunos casos, es posible que sea necesario realizar pasos adicionales para instalar e iniciar el proceso en segundo plano.

Los servicios de integración de Linux implementan el intercambio de datos con grupos KVP. Un grupo de KVP es un archivo almacenado en una ruta de acceso específica. Hay cuatro archivos de pool:

/var/lib/hyperv/.kvp_pool_0
/var/lib/hyperv/.kvp_pool_1
/var/lib/hyperv/.kvp_pool_2
/var/lib/hyperv/.kvp_pool_3

Estos archivos de grupo se asignan a los siguientes conjuntos de claves del Registro de Windows:

  • Grupo 0: Virtual Machine\External
  • Grupo 1: Virtual Machine\Guest
  • Grupo 2: Virtual Machine\Auto
  • Grupo 3: Virtual Machine\Guest\Parameter

Nota:

Para obtener más información sobre la compatibilidad con KVP de Linux, consulte Máquinas virtuales Linux y FreeBSD en Hyper-V.

Es posible que la infraestructura del par clave-valor no funcione correctamente sin una actualización de software de Linux. Póngase en contacto con el proveedor de distribución para obtener una actualización si encuentra problemas.

Estructura de pool

Cada archivo de grupo contiene registros con esta estructura:

struct kvp_record
{
    unsigned char key[ HV_KVP_EXCHANGE_MAK_KEY_SIZE ];
    unsigned char value[ HV_KVP_EXCHANGE_MAX_VALUE_SIZE ];
};

Esas constantes de tamaño se definen en hyperv.h (un encabezado de kernel distribuido con los orígenes del kernel de Linux).

Leer y mostrar valores del grupo 0

En este ejemplo se leen los valores de KVP del grupo 0 y se muestran.

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "../include/linux/hyperv.h"

typedef struct kvp_record
{
    unsigned char key [HV_KVP_EXCHANGE_MAX_KEY_SIZE];
    unsigned char value [HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
} KVP_RECORD;

KVP_RECORD myRecords[200];

void KVPAcquireLock(int fd)
{
    struct flock fl = {F_RDLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLKW, &fl))
    {
        perror("fcntl lock");
        exit (-10);
    }
}

void KVPReleaseLock(int fd)
{
    struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLK, &fl))
    {
        perror("fcntl unlock");
        exit (-20);
    }
}

int main (int argc, char **argv)
{
    char poolName[] = "/var/lib/hyperv/.kvp_pool_0";
    int   i;
    int   fd;
    int   bytesRead;
    int   numRecords;

    fd = open(poolName, O_RDONLY);
    if (-1 == fd)
    {
        printf("Error: Unable to open pool file %s\n", poolName);
        exit (-30);
    }

    KVPAcquireLock(fd);
    bytesRead = read(fd, myRecords, sizeof(myRecords));
    KVPReleaseLock(fd);

    numRecords = bytesRead / sizeof(struct kvp_record);
    printf("Number of records : %d\n", numRecords);

    for (i = 0; i < numRecords; i++)
    {
        printf("  Key  : %s\n  Value: %s\n\n", myRecords[i].key, myRecords[i].value);
    }

    close(fd);

    return 0;
}

Creación de un elemento KVP en el grupo 1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "../include/linux/hyperv.h"

typedef struct kvp_record
{
    unsigned char key [HV_KVP_EXCHANGE_MAX_KEY_SIZE];
    unsigned char value [HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
} KVP_RECORD;

void KVPAcquireWriteLock(int fd)
{
    struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLKW, &fl))
    {
        perror("fcntl lock");
        exit (-10);
    }
}

void KVPReleaseLock(int fd)
{
    struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLK, &fl))
    {
        perror("fcntl unlock");
        exit (-20);
    }
}

int main (int argc, char **argv)
{
    char poolName[] = "/var/lib/hyperv/.kvp_pool_1";
    int   fd;
    KVP_RECORD newKvp;

    if (3 != argc)
    {
        printf("Usage: WritePool keyName valueString\n\n");
        exit (-5);
    }

    fd = open(poolName, O_WRONLY);
    if (-1 == fd)
    {
        printf("Error: Unable to open pool file %s\n", poolName);
        exit (-30);
    }

    memset((void *)&newKvp, 0, sizeof(KVP_RECORD));
    memcpy(newKvp.key, argv[1], strlen(argv[1]));
    memcpy(newKvp.value, argv[2], strlen(argv[2]));

    KVPAcquireWriteLock(fd);
    write(fd, (void *)&newKvp, sizeof(KVP_RECORD));
    KVPReleaseLock(fd);

    close(fd);

    return 0;
}

Eliminación de un elemento KVP del grupo 1

En este ejemplo se elimina un elemento.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <uapi/linux/hyperv.h>

typedef struct kvp_record
{
    unsigned char key [HV_KVP_EXCHANGE_MAX_KEY_SIZE];
    unsigned char value [HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
} KVP_RECORD;

void KVPAcquireWriteLock(int fd)
{
    struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLKW, &fl))
    {
        perror("fcntl lock");
        exit (-10);
    }
}

void KVPReleaseLock(int fd)
{
    struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
    fl.l_pid = getpid();

    if (-1 == fcntl(fd, F_SETLK, &fl))
    {
        perror("fcntl unlock");
        exit (-20);
    }
}

int find_record_offset(int fd, char *key)
{
    int bytesRead;
    int offset = 0;
    int retval = -1;

    KVP_RECORD kvpRec;

    while (1)
    {
        lseek(fd, offset, SEEK_SET);
        bytesRead = read(fd, &kvpRec, sizeof(KVP_RECORD));
        if (0 == bytesRead)
        {
            break;
        }

        if (0 == strcmp(key, (const char *) kvpRec.key))
        {
            retval = offset;
            break;
        }

        offset += sizeof(KVP_RECORD);
    }

    return retval;
}

int main (int argc, char **argv)
{
    char  poolName[] = "/var/lib/hyperv/.kvp_pool_1";
    int   fd;
    int   exitVal = -1;
    int   bytesRead;
    int   bytesWritten;
    int   offset_to_delete;
    int   offset_last_record;
    KVP_RECORD kvpRec;

    if (2 != argc)
    {
        printf("Usage: WritePool keyName valueString\n\n");
        exit (-5);
    }

    fd = open(poolName, O_RDWR, 0644);
    if (-1 == fd)
    {
        printf("Error: Unable to open pool file %s\n", poolName);
        exit (-10);
    }

    KVPAcquireWriteLock(fd);
    offset_to_delete = find_record_offset(fd, argv[1]);
    if (offset_to_delete < 0)
    {
        exitVal = -15;
        goto cleanup2;
    }

    offset_last_record = lseek(fd, -sizeof(KVP_RECORD), SEEK_END);
    if (offset_last_record < 0)
    {
        exitVal = -20;
        goto cleanup2;
    }

    if (offset_last_record != offset_to_delete)
    {
        lseek(fd, offset_last_record, SEEK_SET);
        bytesRead = read(fd, &kvpRec, sizeof(KVP_RECORD));
        lseek(fd, offset_to_delete, SEEK_SET);
        bytesWritten = write(fd, &kvpRec, sizeof(KVP_RECORD));
    }

    ftruncate(fd, offset_last_record);

    exitVal = 0;

cleanup2:
    KVPReleaseLock(fd);

cleanup1:
    close(fd);

    return exitVal;
}

Trabajar con pares clave-valor del host mediante WMI

En los ejemplos siguientes se usa el espacio de nombres WMI v2. Para WMI v1 (versiones anteriores), quite el segmento \v2 de la ruta de acceso del espacio de nombres.

Nota:

Si usa Windows 8 o Windows 8.1, instale Cliente Hyper-V para obtener los espacios de nombres.

Lee el valor desde el host.

En este ejemplo se obtiene el valor de la clave Status de una máquina virtual denominada Vm1:

$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_ComputerSystem -Filter {ElementName = 'Vm1'}
$vm.GetRelated("Msvm_KvpExchangeComponent").GuestExchangeItems | % { \
    $GuestExchangeItemXml = ([XML]$_).SelectSingleNode(\
        "/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'Status']")
    if ($GuestExchangeItemXml -ne $null)
    {
        $GuestExchangeItemXml.SelectSingleNode(\
            "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()" ).Value
    }
}

Adición o modificación de pares clave-valor desde el host

Para agregar un par clave-valor desde el host, obtenga instancias del servicio de administración y de la máquina virtual y cree una nueva instancia de Msvm_KvpExchangeDataItem. Al crear la nueva instancia, especifique , NameDatay Source (debe ser 0). A continuación, llame a AddKvpItems.

La consulta de pares clave-valor creados por el anfitrión es similar a las consultas de invitados, pero requiere un salto de asociación adicional a Msvm_KvpExchangeComponentSettingData. La modificación y eliminación de valores funcionan de la misma manera: especifique el mismo nombre de clave y llame al método adecuado, Modify o Remove.

Importante

En los ejemplos siguientes se usa el espacio de nombres v2. Si está utilizando Windows Server 2008 o Windows Server 2008 R2, elimine el segmento \v2.

Agregar un nuevo par clave-valor

$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\\{1}:{2}", \
    $VmMgmt.ClassPath.Server, \
    $VmMgmt.ClassPath.NamespacePath, \
    "Msvm_KvpExchangeDataItem")).CreateInstance()

$kvpDataItem.Name = "Name"
$kvpDataItem.Data = "Data"
$kvpDataItem.Source = 0

$VmMgmt.AddKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))

Consulta de pares clave-valor en el host

$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_ComputerSystem -Filter {ElementName='VM1'}
($vm.GetRelated("Msvm_KvpExchangeComponent")[0] ).GetRelated("Msvm_KvpExchangeComponentSettingData").HostExchangeItems | % { \
    $GuestExchangeItemXml = ([XML]$_).SelectSingleNode(\
        "/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'Name2']")
    if ($GuestExchangeItemXml -ne $null)
    {
        $GuestExchangeItemXml.SelectSingleNode(\
            "/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()" ).Value
    }
}

Modificación de un par clave-valor

$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\\{1}:{2}", \
    $VmMgmt.ClassPath.Server, \
    $VmMgmt.ClassPath.NamespacePath, \
    "Msvm_KvpExchangeDataItem")).CreateInstance()

$kvpDataItem.Name = "Name"
$kvpDataItem.Data = "Data2"
$kvpDataItem.Source = 0

$VmMgmt.ModifyKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))

Quitar un par clave-valor

$VmMgmt = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_VirtualSystemManagementService
$vm = Get-WmiObject -Namespace root\virtualization\v2 -Class \
    Msvm_ComputerSystem -Filter {ElementName='VM1'}
$kvpDataItem = ([WMIClass][String]::Format("\\{0}\\{1}:{2}", \
    $VmMgmt.ClassPath.Server, \
    $VmMgmt.ClassPath.NamespacePath, \
    "Msvm_KvpExchangeDataItem")).CreateInstance()

$kvpDataItem.Name = "Name"
$kvpDataItem.Data = [String]::Empty
$kvpDataItem.Source = 0

$VmMgmt.RemoveKvpItems($Vm, $kvpDataItem.PSBase.GetText(1))

Consulte también