> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/man/man4/hv_vss.4.md).

# hv\_vss.4

`hv_vss` — Hyper-V 卷影复制服务 API

## 名称

`hv_vss`

## 概要

`#include <dev/hyperv/hv_snapshot.h>`

```sh
#define VSS_SUCCESS		0x00000000
#define VSS_FAIL		0x00000001
enum hv_vss_op_t {
	HV_VSS_NONE = 0,
	HV_VSS_CHECK,
	HV_VSS_FREEZE,
	HV_VSS_THAW,
	HV_VSS_COUNT
};
struct hv_vss_opt_msg {
	uint32_t	opt;		/* 操作 */
	uint32_t	status;		/* 0 表示成功，1 表示错误 */
	uint64_t	msgid;		/* 用于标识事务的 ID */
	uint8_t		reserved[48];	/* 保留值全为零 */
};
```

## 描述

应用程序的冻结或解冻功能对于保证应用程序一致性备份非常重要。在 Windows 平台上，VSS 用于执行实时备份。但对于运行于 Hyper-V 上的虚拟机客户机，尚未定义相应的 VSS。例如，一个正在运行的数据库服务器实例知道应用程序的冻结/解冻应何时开始或结束，但它无法感知来自 Hyper-V 主机的冻结/解冻通知。`hv_vss` 旨在通知应用程序冻结/解冻请求。因此，它扮演中介角色，将来自 Hyper-V 主机的冻结/解冻命令转发给在 FreeBSD 虚拟机上注册了 VSS 服务的用户态应用程序，并将结果发送回 Hyper-V 主机。

通常，hv\_vss\_daemon(8) 负责冻结/解冻 UFS 文件系统，并在系统启动后自动启动。当 Hyper-V 主机想要对 FreeBSD 虚拟机创建快照时，会先向 FreeBSD 虚拟机发送 VSS 能力检查。`hv_vss` 接收到请求后，如果应用程序已注册，则将请求转发给用户态应用程序。只有当 `hv_vss` 收到来自应用程序的 VSS\_SUCCESS 响应后，才会通知 hv\_vss\_daemon(8) 检查是否支持文件系统冻结/解冻。在此期间发生任何错误，`hv_vss` 将通知 Hyper-V 主机不支持 VSS。此外，在向 Hyper-V 主机发送响应前有一个默认超时限制。如果应用程序和 hv\_vss\_daemon(8) 的总响应时间超过此值，将发生超时，并向 Hyper-V 主机响应不支持 VSS。

Hyper-V 主机确认 FreeBSD 虚拟机支持 VSS 后，会向虚拟机发送冻结请求，`hv_vss` 会首先将其转发给应用程序。应用程序完成冻结后，应通知 `hv_vss`，然后由 hv\_vss\_daemon(8) 触发文件系统级冻结。当应用程序和 hv\_vss\_daemon(8) 两端的冻结都完成后，`hv_vss` 将通知 Hyper-V 主机冻结完成。当然，与 VSS 能力检查相同，也设置了一个超时限制，以确保 FreeBSD 虚拟机上的冻结不会挂起。如果发生任何错误或超时，Hyper-V 端的冻结将失败。

Hyper-V 主机在创建快照后会发送解冻请求，通常此时间段非常短，以免阻塞正在运行的应用程序。`hv_vss` 首先通过通知 hv\_vss\_daemon(8) 解冻文件系统，然后通知用户注册的应用程序。在向 Hyper-V 主机发送响应前也有超时检查。

VSS 能力检查、冻结或解冻中使用的所有默认超时限制相同。目前为 15 秒。

## 注释

`hv_vss` 目前仅支持 UFS。如果任何文件系统分区不是 UFS，VSS 能力检查将失败。如果应用程序未注册 VSS，`hv_vss` 仅支持文件系统级一致性备份。设备在再次打开之前应先关闭。如果要同时打开 "/dev/hv\_appvss\_dev" 两次或更多次，将返回错误（-1），并设置 errno。

如果 hv\_vss\_daemon(8) 在系统启动后被终止，VSS 功能将无法工作。

## 实例

以下是一个完整示例，在收到来自 `hv_vss` 的这些通知时除了等待 2 秒外什么都不做：

```sh
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <stdint.h>
#include <syslog.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <ufs/ffs/fs.h>
#include <paths.h>
#include <sys/ioccom.h>
#include <dev/hyperv/hv_snapshot.h>
#define UNDEF_FREEZE_THAW	(0)
#define FREEZE			(1)
#define THAW			(2)
#define CHECK			(3)
#define	VSS_LOG(priority, format, args...) do	{				\
		if (is_debugging == 1) {					\
			if (is_daemon == 1)					\
				syslog(priority, format, ## args);		\
			else							\
				printf(format, ## args);			\
		} else {							\
			if (priority < LOG_DEBUG) {				\
				if (is_daemon == 1)				\
					syslog(priority, format, ## args);	\
				else						\
					printf(format, ## args);		\
			}							\
		}								\
	} while(0)
#define CHECK_TIMEOUT		1
#define CHECK_FAIL		2
#define FREEZE_TIMEOUT		1
#define FREEZE_FAIL		2
#define THAW_TIMEOUT		1
#define THAW_FAIL		2
static int is_daemon        = 1;
static int is_debugging     = 0;
static int simu_opt_waiting = 2; // 秒
#define GENERIC_OPT(TIMEOUT, FAIL)						\
	do {									\
		sleep(simu_opt_waiting);					\
		if (opt == CHECK_TIMEOUT) {					\
			sleep(simu_opt_waiting * 10);				\
			VSS_LOG(LOG_INFO, "%s timeout simulationn",		\
			    __func__);						\
			return (0);						\
		} else if (opt == CHECK_FAIL) {					\
			VSS_LOG(LOG_INFO, "%s failure simulationn",		\
			    __func__);						\
			return (CHECK_FAIL);					\
		} else {							\
			VSS_LOG(LOG_INFO, "%s success simulationn",		\
			    __func__);						\
			return (0);						\
		}								\
	} while (0)
static int
check(int opt)
{
	GENERIC_OPT(CHECK_TIMEOUT, CHECK_FAIL);
}
static int
freeze(int opt)
{
	GENERIC_OPT(FREEZE_TIMEOUT, FREEZE_FAIL);
}
static int
thaw(int opt)
{
	GENERIC_OPT(THAW_TIMEOUT, THAW_FAIL);
}
static void usage(const char* cmd) {
	fprintf(stderr,
	    "%s -f <0|1|2>: simulate app freeze."
	    " 0: successful, 1: freeze timeout, 2: freeze failedn"
	    " -c <0|1|2>: simulate vss feature check"
	    " -t <0|1|2>: simulate app thaw."
	    " 0: successful, 1: freeze timeout, 2: freeze failedn"
	    " -d : enable debug moden"
	    " -n : run this tool under non-daemon moden", cmd);
}
int
main(int argc, char* argv[]) {
	int ch, freezesimuop = 0, thawsimuop = 0, checksimuop = 0, fd, r, error;
	uint32_t op;
	struct pollfd app_vss_fd[1];
	struct hv_vss_opt_msg  userdata;
	while ((ch = getopt(argc, argv, "f:c:t:dnh")) != -1) {
		switch (ch) {
		case 'f':
			/* 以常规进程运行以便调试 */
			freezesimuop = (int)strtol(optarg, NULL, 10);
			break;
		case 't':
			thawsimuop = (int)strtol(optarg, NULL, 10);
			break;
		case 'c':
			checksimuop = (int)strtol(optarg, NULL, 10);
			break;
		case 'd':
			is_debugging = 1;
			break;
		case 'n':
			is_daemon = 0;
			break;
		case 'h':
		default:
			usage(argv[0]);
			exit(0);
		}
	}
	openlog("APPVSS", 0, LOG_USER);
	/* 首先成为守护进程 */
	if (is_daemon == 1)
		daemon(1, 0);
	else
		VSS_LOG(LOG_DEBUG, "Run as regular process.n");
	VSS_LOG(LOG_INFO, "HV_VSS starting; pid is: %dn", getpid());
	fd = open(VSS_DEV(APP_VSS_DEV_NAME), O_RDWR);
	if (fd < 0) {
		VSS_LOG(LOG_ERR, "Fail to open %s, error: %d %sn",
		    VSS_DEV(APP_VSS_DEV_NAME), errno, strerror(errno));
		exit(EXIT_FAILURE);
	}
	app_vss_fd[0].fd     = fd;
	app_vss_fd[0].events = POLLIN | POLLRDNORM;
	while (1) {
		r = poll(app_vss_fd, 1, INFTIM);
		VSS_LOG(LOG_DEBUG, "poll returned r = %d, revent = 0x%xn",
		    r, app_vss_fd[0].revents);
		if (r == 0 || (r < 0 && errno == EAGAIN) ||
		    (r < 0 && errno == EINTR)) {
			/* 无可读内容 */
			continue;
		}
		if (r < 0) {
			/*
			 * 对于 EAGAIN 以外的 poll 返回失败，
			 * 我们选择退出。
			 */
			VSS_LOG(LOG_ERR, "Poll failed.n");
			perror("poll");
			exit(EIO);
		}
		/* 从字符设备读取 */
		error = ioctl(fd, IOCHVVSSREAD, &userdata);
		if (error < 0) {
			VSS_LOG(LOG_ERR, "Read failed.n");
			perror("pread");
			exit(EIO);
		}
		if (userdata.status != 0) {
			VSS_LOG(LOG_ERR, "data read errorn");
			continue;
		}
		op = userdata.opt;
		switch (op) {
		case HV_VSS_CHECK:
			error = check(checksimuop);
			break;
		case HV_VSS_FREEZE:
			error = freeze(freezesimuop);
			break;
		case HV_VSS_THAW:
			error = thaw(thawsimuop);
			break;
		default:
			VSS_LOG(LOG_ERR, "Illegal operation: %dn", op);
			error = VSS_FAIL;
		}
		if (error)
			userdata.status = VSS_FAIL;
		else
			userdata.status = VSS_SUCCESS;
		error = ioctl(fd, IOCHVVSSWRITE, &userdata);
		if (error != 0) {
			VSS_LOG(LOG_ERR, "Fail to write to devicen");
			exit(EXIT_FAILURE);
		} else {
			VSS_LOG(LOG_INFO, "Send response %d for %s to kerneln",
			    userdata.status, op == HV_VSS_FREEZE ? "Freeze" :
			    (op == HV_VSS_THAW ? "Thaw" : "Check"));
		}
	}
	return 0;
}
```

## 参见

[hv\_utils(4)](/man/man4/hv_utils.4.md), hv\_vss\_daemon(8)

## 历史

该守护进程于 2016 年 10 月引入，由 Microsoft Corp. 开发。

## 作者

`hv_vss` 的 FreeBSD 支持最早由 Microsoft BSD Integration Services Team <bsdic@microsoft.com> 添加。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://book.bsdcn.org/man/man4/hv_vss.4.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
