다음을 통해 공유


데이터 교환: 키-값 쌍을 사용하여 Hyper-V 호스트와 게스트 간에 정보 공유

데이터 교환은 가상 머신(게스트)과 해당 Hyper-V 호스트 간에 작은 정보를 공유하는 데 사용할 수 있는 통합 서비스(키-값 쌍 교환 또는 KVP라고도 함)입니다. 가상 머신 및 호스트에 대한 일반 정보는 자동으로 만들어지고 키-값 쌍으로 저장됩니다. 사용자 지정 데이터를 공유하는 고유한 쌍을 만들 수도 있습니다.

키-값 쌍은 "키"와 "값"으로 구성됩니다. 둘 다 문자열입니다. 다른 데이터 형식은 지원되지 않습니다. 키-값 쌍을 만들거나 변경하면 게스트와 호스트 모두에 표시됩니다. KVP 데이터는 Hyper-V VMbus를 통해 이동하며 게스트와 호스트 간의 네트워크 연결이 필요하지 않습니다.

키-값 쌍이 만들어지면 삭제될 때까지 유지됩니다. 키-값 쌍을 만드는 모든 애플리케이션은 더 이상 필요하지 않을 때 해당 쌍을 삭제해야 합니다. 키-값 쌍은 실시간 마이그레이션 중에 가상 머신과 함께 이동합니다.

윈도우 게스트

Windows 게스트에서 KVP 데이터는 다음 아래에 레지스트리에 저장됩니다.

HKLM\SOFTWARE\Microsoft\Virtual Machine

데이터는 다음 하위 키로 구성됩니다.

  • Virtual Machine\Auto – 게스트를 설명하는 데이터입니다. 통합 서비스 드라이버가 로드한 후 생성됩니다. 호스트에 내장 데이터로 표시됩니다.
  • Virtual Machine\External – 사용자가 호스트에서 게스트로 푸시한 데이터입니다.
  • Virtual Machine\Guest – 게스트에서 만든 데이터입니다. 호스트에 기본이 아닌 데이터로 표시됩니다.
  • Virtual Machine\Guest\Parameter – 호스트를 설명하는 호스트에서 게스트로 푸시된 데이터입니다.

게스트 내에서 값을 추가하는 것은 아래에서 HKLM\SOFTWARE\Microsoft\Virtual Machine\Guest새 문자열 값을 만드는 것만큼 간단합니다. 이 위치를 수정하려면 게스트의 관리자여야 합니다. 호스트 또는 원격 컴퓨터(사용 권한 포함)에서 WMI(PowerShell 또는 기타 도구)를 사용하여 값을 검색할 수 있습니다.

레지스트리 크기 제한에 대한 자세한 내용은 (레거시) 문서 Registry 요소 크기 제한을 참조하세요.

게스트에 새로운 키-값 쌍을 추가하세요.

이 예제에서 상태 값은 다음으로 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 풀은 특정 경로에 저장된 파일입니다. 4개의 풀 파일이 있습니다.

/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

비고

Linux KVP 지원에 대한 자세한 내용은 Hyper-V의 Linux 및 FreeBSD Virtual Machines를 참조하세요.

키-값 쌍 인프라가 Linux 소프트웨어 업데이트 없이는 제대로 작동하지 않을 수 있습니다. 문제가 발생하는 경우 배포 공급업체에 문의하여 업데이트를 요청하세요.

풀 구조

각 풀 파일에는 다음 구조의 레코드가 포함됩니다.

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

이러한 크기 상수는 (Linux 커널 원본과 함께 배포된 커널 헤더)에 정의 hyperv.h 됩니다.

풀 0에서 값 읽기 및 표시

이 샘플은 풀 0에서 KVP 값을 읽고 표시합니다.

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

풀 1에서 KVP 항목 만들기

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

풀 1에서 KVP 항목 삭제

이 샘플에서는 항목을 삭제합니다.

#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 v2 네임스페이스를 사용합니다. WMI v1(이전 버전)의 경우 네임스페이스 경로에서 \v2 세그먼트를 제거합니다.

비고

Windows 8 또는 Windows 8.1을 사용하는 경우 클라이언트 Hyper-V 설치하여 네임스페이스를 가져옵니다.

호스트에서 값을 읽습니다.

이 예제에서는 다음과 같은 StatusVM에서 키 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
    }
}

호스트에서 키-값 쌍을 추가하거나 수정하기

호스트에서 키-값 쌍을 추가하려면 관리 서비스와 VM의 인스턴스를 모두 가져와서 새 인스턴스 Msvm_KvpExchangeDataItem를 만듭니다. 새 인스턴스를 만들 때, Name, Data, 및 Source(반드시 0)를 지정합니다. 그런 다음 AddKvpItems를 호출합니다.

호스트에서 만든 키-값 쌍에 대한 쿼리는 게스트 쿼리와 유사하지만 추가 연결 홉 Msvm_KvpExchangeComponentSettingData이 필요합니다. 값 수정 및 삭제는 동일한 방식으로 작동합니다. 동일한 키 이름을 지정하고 적절한 Modify 또는 Remove 메서드를 호출합니다.

중요합니다

아래 예제에서는 v2 네임스페이스를 사용합니다. 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))

참고하십시오