Поделиться через


Обмен данными: использование пар "ключ-значение" для обмена информацией между узлом и гостем на Hyper-V

Обмен данными — это служба интеграции (также известная как обмен парами "ключ-значение" или KVP), которую можно использовать для совместного использования небольших фрагментов информации между виртуальной машиной (гостевой) и его Hyper-V узлом. Общие сведения о виртуальной машине и узле автоматически создаются и хранятся в виде пар "ключ-значение". Вы также можете создавать собственные пары для совместного использования пользовательских данных.

Пары "ключ-значение" состоят из "ключа" и "значения". Оба являются строками; другие типы данных не поддерживаются. При создании или изменении пары "ключ-значение" она становится видимой как для гостя, так и для хоста. Данные KVP перемещаются по Hyper-V VMbus и не требуют сетевого подключения между гостевым и хостом.

После создания пары "ключ-значение" остаются до тех пор, пока они не будут удалены. Любое приложение, создающее пары "ключ-значение", должно удалить их, если они больше не нужны. Пары "ключ-значение" перемещаются с виртуальной машиной во время динамической миграции.

Гости Windows

В гостевых виртуальных машинах Windows данные KVP хранятся в реестре в следующих разделах:

HKLM\SOFTWARE\Microsoft\Virtual Machine

Данные организованы в следующих подразделах:

  • Виртуальная машина\Auto — данные, описывающие гостевого пользователя. Создается драйверами служб интеграции после загрузки. Видимый хосту как внутренние данные.
  • Виртуальная машина\Внешний — данные, отправленные на гостевую машину из узла пользователем.
  • Виртуальная машина\Гость — данные, созданные на гостевом компьютере. Видимые хосту как не собственные данные.
  • Виртуальная машина\Гостевой\Параметр — данные, отправляемые в гостевой ОС с хоста, описывающего хост.

Добавление значений из гостевого элемента выполняется так же просто, как создание нового строкового значения в HKLM\SOFTWARE\Microsoft\Virtual Machine\Guestразделе . Чтобы изменить это расположение, необходимо быть администратором гостевой системы. Для получения значения можно использовать WMI (PowerShell или другое средство) с узла или удаленного компьютера (с разрешениями).

Сведения об ограничениях размера реестра см. в статье " Ограничения размера элементов реестра" (устаревшая версия).

Добавление новой пары "ключ-значение" в гость

В этом примере для параметра Status задано Readyзначение :

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

Для изменения значения можно использовать тот же синтаксис.

Пары "Ключ-значение" запроса в гостевой системе

Чтобы запросить значение внешнего подраздела (данные, отправленные гостем из узла):

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

Гости Linux

В Linux нет реестра, поэтому элементы KVP хранятся в файловой системе. Для обработки должен выполняться демон-процесс hv_kvp_daemon. Для большинства дистрибутивов с установленными драйверами служб Linux Integration Services (LIS) или драйверами ядра эта управляющая программа запускается автоматически. В некоторых случаях для установки и запуска управляющей программы могут потребоваться дополнительные шаги.

Службы интеграции Linux реализуют обмен данными с пулами KVP. Пул KVP — это файл, хранящийся в определенном пути. Существует четыре файла пула:

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

Эти файлы пула соответствуют следующим наборам ключей в реестре Windows:

  • Пул 0: Virtual Machine\External
  • Пул 1. Virtual Machine\Guest
  • Пул 2. Virtual Machine\Auto
  • Пул 3: Virtual Machine\Guest\Parameter

Замечание

Дополнительные сведения о поддержке KVP Linux см. в статье Linux и FreeBSD Virtual Machines on Hyper-V.

Инфраструктура пар "ключ-значение" может работать неправильно без обновления программного обеспечения Linux. При возникновении проблем обратитесь к поставщику рассылки за обновлением.

Структура пула

Каждый файл пула содержит записи с этой структурой:

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

Эти константы размера определены в hyperv.h (заголовок ядра, распределенный с источниками ядра Linux).

Чтение и отображение значений из пула 0

Этот пример считывает значения KVP из пула 0 и отображает их.

```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;
}

Создайте элемент KVP в пуле 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;
}

Удалите элемент KVP из пула 1

В этом примере удаляется элемент.

#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;
}

Работа с парами "ключ-значение" из узла с помощью WMI

В следующих примерах используется пространство имен WMI версии 2. Для WMI версии 1 (более ранних версий) удалите \v2 сегмент из пути пространства имен.

Замечание

Если вы используете Windows 8 или Windows 8.1, установите Client Hyper-V, чтобы получить пространства имен.

Чтение значения из хоста

В этом примере получается значение ключа Status из виртуальной машины с именем 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
    }
}

Добавление или изменение пар "ключ-значение" с хоста

Чтобы добавить пару "ключ-значение" из узла, получите экземпляры службы управления и виртуальной машины и создайте новый экземпляр Msvm_KvpExchangeDataItem. При создании нового экземпляра укажите Name, Data и Source (должно быть 0). Затем вызовите функцию AddKvpItems.

Правка запросов на пары "ключ-значение", созданные хостом, аналогична запросам, инициированным гостем, но требует дополнительной ассоциации Msvm_KvpExchangeComponentSettingData. Изменение и удаление значений работает так же: укажите то же имя ключа и вызовите соответствующее Modify или Remove метод.

Это важно

В приведенных ниже примерах используется пространство имен версии 2. Если вы используете Windows Server 2008 или Windows Server 2008 R2, удалите \v2 сегмент.

Добавление новой пары "ключ-значение"

$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))

Запросите пары «ключ-значение» на узле

$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
    }
}

Изменение пары "ключ-значение"

$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))

Удалите пару "ключ-значение"

$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))

См. также