DPDK系列之十五虚拟化virtio源码分析之vhost-user

这篇具有很好参考价值的文章主要介绍了DPDK系列之十五虚拟化virtio源码分析之vhost-user。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、vhost-user说明

在网络IO的半虚拟中,vhost-user是目前最优秀的解决方案。在DPDK中,同样也采用了这种方式。vhost-user是为了解决内核状态数据操作复杂的情况提出的一种解决方式,通过在用户进程来替代内核进程来实现数据交互的最少化。在vhost-user中,使用Socket进行设备文件间通信(替代了Kernel模式),而数据交换则采用mmap的进程内存宰共享的模式减少数据的交互。
vhost-user在DPDK中的vhost库中实现,其包含了完整的virtio的后端逻辑功能。在软件虚拟交的机OVS中,就应用到了DPDK这个库。
一般来说,vhost-user由OVS为每个虚拟创建珍上vhost端口,来实现相关数据操作。而此时的virtio前端和会调用此相关的端口进行通信。

二、数据结构

基本的数据结构如下(lib/librte_vhost/vhost_user.h):

/* Same structure as vhost-user backend session info */
typedef struct VhostUserCryptoSessionParam {
	int64_t session_id;
	uint32_t op_code;
	uint32_t cipher_algo;
	uint32_t cipher_key_len;
	uint32_t hash_algo;
	uint32_t digest_len;
	uint32_t auth_key_len;
	uint32_t aad_len;
	uint8_t op_type;
	uint8_t dir;
	uint8_t hash_mode;
	uint8_t chaining_dir;
	uint8_t * ciphe_key;
	uint8_t * auth_key;
	uint8_t cipher_key_buf[VHOST_USER_CRYPTO_MAX_CIPHER_KEY_LENGTH];
	uint8_t auth_key_buf[VHOST_USER_CRYPTO_MAX_HMAC_KEY_LENGTH];
} VhostUserCryptoSessionParam;

typedef struct VhostUserVringArea {
	uint64_t u64;
	uint64_t size;
	uint64_t offset;
} VhostUserVringArea;

typedef struct VhostUserInflight {
	uint64_t mmap_size;
	uint64_t mmap_offset;
	uint16_t num_queues;
	uint16_t queue_size;
} VhostUserInflight;

typedef struct VhostUserMsg {
	union {
		uint32_t master; /* a VhostUserRequest value */
		uint32_t slave;  /* a VhostUserSlaveRequest value*/
	} request;

#define VHOST_USER_VERSION_MASK     0x3
#define VHOST_USER_REPLY_MASK       (0x1 << 2)
#define VHOST_USER_NEED_REPLY		(0x1 << 3)
	uint32_t flags;
	uint32_t size; /* the following payload size */
	union {
#define VHOST_USER_VRING_IDX_MASK   0xff
#define VHOST_USER_VRING_NOFD_MASK  (0x1<<8)
		uint64_t u64;
		struct vhost_vring_state state;
		struct vhost_vring_addr addr;
		VhostUserMemory memory;
		VhostUserLog    log;
		struct vhost_iotlb_msg iotlb;
		VhostUserCryptoSessionParam crypto_session;
		VhostUserVringArea area;
		VhostUserInflight inflight;
	} payload;
	int fds[VHOST_MEMORY_MAX_NREGIONS];
	int fd_num;
} __attribute((packed)) VhostUserMsg;

其中最主要的就是最后一个数据结构VhostUserMsg,这个消息里包含着消息的种类、内容和相关内容的数据大小。而这个消息,正是通过vhost_user.c(lib/librte_vhost)中的vhost_user_msg_handler这个函数来处理的。它们之间的消息类型定义如下:

typedef enum VhostUserRequest {
    VHOST_USER_NONE = 0,
    VHOST_USER_GET_FEATURES = 1,
    VHOST_USER_SET_FEATURES = 2,
    VHOST_USER_SET_OWNER = 3,
    VHOST_USER_RESET_OWNER = 4,
    VHOST_USER_SET_MEM_TABLE = 5,
    VHOST_USER_SET_LOG_BASE = 6,
    VHOST_USER_SET_LOG_FD = 7,
    VHOST_USER_SET_VRING_NUM = 8,
    VHOST_USER_SET_VRING_ADDR = 9,
    VHOST_USER_SET_VRING_BASE = 10,
    VHOST_USER_GET_VRING_BASE = 11,
    VHOST_USER_SET_VRING_KICK = 12,
    VHOST_USER_SET_VRING_CALL = 13,
    VHOST_USER_SET_VRING_ERR = 14,
    VHOST_USER_GET_PROTOCOL_FEATURES = 15,
    VHOST_USER_SET_PROTOCOL_FEATURES = 16,
    VHOST_USER_GET_QUEUE_NUM = 17,
    VHOST_USER_SET_VRING_ENABLE = 18,
    VHOST_USER_SEND_RARP = 19,
    VHOST_USER_NET_SET_MTU = 20,
    VHOST_USER_SET_SLAVE_REQ_FD = 21,
    VHOST_USER_IOTLB_MSG = 22,
    VHOST_USER_MAX
} VhostUserRequest;

随着版本的迭代和新的设备及相关控制手段增加会引起些消息的增加。
再看一下相关的共享内存数据结构(lib/librte_vhost/vhost_user.h):

/*对应qemu端的region结构*/
typedef struct VhostUserMemoryRegion {
	uint64_t guest_phys_addr;
	uint64_t memory_size;
	uint64_t userspace_addr;
	uint64_t mmap_offset;
} VhostUserMemoryRegion;

typedef struct VhostUserMemory {
	uint32_t nregions;
	uint32_t padding;
	VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
} VhostUserMemory;

//lib/librte_vhost/rte_vhost.h
/**
 * Information relating to memory regions including offsets to
 * addresses in QEMUs memory file.
 */
struct rte_vhost_mem_region {
	uint64_t guest_phys_addr;
	uint64_t guest_user_addr;
	uint64_t host_user_addr;
	uint64_t size;
	void	 *mmap_addr;
	uint64_t mmap_size;
	int fd;
};

/**
 * Memory structure includes region and mapping information.
 */
struct rte_vhost_memory {
	uint32_t nregions;
	struct rte_vhost_mem_region regions[];
};

上面的两个不同文件夹的相关数据结构体互相对应。

三、基本流程

1、连接和初始化
连接的建立是使用Sokcet来进行的。在前面的消息数据结构体中,其实是定义了很多消息枚举和相关的数组的。这个上面的数据结构体中已经有所体现。

int
rte_vhost_driver_start(const char * path)
{
	struct vhost_user_socket * vsocket;
	static pthread_t fdset_tid;

	pthread_mutex_lock(&vhost_user.mutex);
	vsocket = find_vhost_user_socket(path);
	pthread_mutex_unlock(&vhost_user.mutex);

	if (!vsocket)
		return -1;

	if (fdset_tid == 0) {
		/**
		 * create a pipe which will be waited by poll and notified to
		 * rebuild the wait list of poll.
		 */
		if (fdset_pipe_init(&vhost_user.fdset) < 0) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"failed to create pipe for vhost fdset\n");
			return -1;
		}

		int ret = rte_ctrl_thread_create(&fdset_tid,
			"vhost-events", NULL, fdset_event_dispatch,
			&vhost_user.fdset);
		if (ret != 0) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"failed to create fdset handling thread\n");

			fdset_pipe_uninit(&vhost_user.fdset);
			return -1;
		}
	}

	if (vsocket->is_server)
		return vhost_user_start_server(vsocket);
	else
		return vhost_user_start_client(vsocket);
}

通过线程来启动分发控制,最后根据是客户端或者服务端来启动相应的功能函数。
当有一个新的连接时,则处理为:

/* call back when there is new vhost-user connection from client  */
static void
vhost_user_server_new_connection(int fd, void *dat, int * remove __rte_unused)
{
	struct vhost_user_socket *vsocket = dat;

	fd = accept(fd, NULL, NULL);
	if (fd < 0)
		return;

	RTE_LOG(INFO, VHOST_CONFIG, "new vhost user connection is %d\n", fd);
	vhost_user_add_connection(fd, vsocket);
}

其实你看lib/librte_vhost/socket.c中的代码,如果有Socket编程的经验的一眼就可以看出好多相关的处理函数和处理手段。

2、数据通信
数据通信中数据交互使用mmap,相关设置代码:

static int
vhost_user_set_mem_table(struct virtio_net **pdev, struct VhostUserMsg *msg,
			int main_fd)
{
	struct virtio_net *dev = *pdev;
	struct VhostUserMemory *memory = &msg->payload.memory;
	struct rte_vhost_mem_region *reg;
	void *mmap_addr;
	uint64_t mmap_size;
	uint64_t mmap_offset;
	uint64_t alignment;
	uint32_t i;
	int populate;

	if (validate_msg_fds(msg, memory->nregions) != 0)
		return RTE_VHOST_MSG_RESULT_ERR;

	if (memory->nregions > VHOST_MEMORY_MAX_NREGIONS) {
		RTE_LOG(ERR, VHOST_CONFIG,
			"too many memory regions (%u)\n", memory->nregions);
		goto close_msg_fds;
	}

	if (dev->mem && !vhost_memory_changed(memory, dev->mem)) {
		RTE_LOG(INFO, VHOST_CONFIG,
			"(%d) memory regions not changed\n", dev->vid);

		close_msg_fds(msg);

		return RTE_VHOST_MSG_RESULT_OK;
	}

	if (dev->mem) {
		free_mem_region(dev);
		rte_free(dev->mem);
		dev->mem = NULL;
	}

	/* Flush IOTLB cache as previous HVAs are now invalid */
	if (dev->features & (1ULL << VIRTIO_F_IOMMU_PLATFORM))
		for (i = 0; i < dev->nr_vring; i++)
			vhost_user_iotlb_flush_all(dev->virtqueue[i]);

	dev->nr_guest_pages = 0;
	if (dev->guest_pages == NULL) {
		dev->max_guest_pages = 8;
		dev->guest_pages = rte_zmalloc(NULL,
					dev->max_guest_pages *
					sizeof(struct guest_page),
					RTE_CACHE_LINE_SIZE);
		if (dev->guest_pages == NULL) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"(%d) failed to allocate memory "
				"for dev->guest_pages\n",
				dev->vid);
			goto close_msg_fds;
		}
	}

	dev->mem = rte_zmalloc("vhost-mem-table", sizeof(struct rte_vhost_memory) +
		sizeof(struct rte_vhost_mem_region) * memory->nregions, 0);
	if (dev->mem == NULL) {
		RTE_LOG(ERR, VHOST_CONFIG,
			"(%d) failed to allocate memory for dev->mem\n",
			dev->vid);
		goto free_guest_pages;
	}
	dev->mem->nregions = memory->nregions;

	for (i = 0; i < memory->nregions; i++) {
		reg = &dev->mem->regions[i];

		reg->guest_phys_addr = memory->regions[i].guest_phys_addr;
		reg->guest_user_addr = memory->regions[i].userspace_addr;
		reg->size            = memory->regions[i].memory_size;
		reg->fd              = msg->fds[i];

		/*
		 * Assign invalid file descriptor value to avoid double
		 * closing on error path.
		 */
		msg->fds[i] = -1;

		mmap_offset = memory->regions[i].mmap_offset;

		/* Check for memory_size + mmap_offset overflow */
		if (mmap_offset >= -reg->size) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"mmap_offset (%#"PRIx64") and memory_size "
				"(%#"PRIx64") overflow\n",
				mmap_offset, reg->size);
			goto free_mem_table;
		}

		mmap_size = reg->size + mmap_offset;

		/* mmap() without flag of MAP_ANONYMOUS, should be called
		 * with length argument aligned with hugepagesz at older
		 * longterm version Linux, like 2.6.32 and 3.2.72, or
		 * mmap() will fail with EINVAL.
		 *
		 * to avoid failure, make sure in caller to keep length
		 * aligned.
		 */
		alignment = get_blk_size(reg->fd);
		if (alignment == (uint64_t)-1) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"couldn't get hugepage size through fstat\n");
			goto free_mem_table;
		}
		mmap_size = RTE_ALIGN_CEIL(mmap_size, alignment);
		if (mmap_size == 0) {
			/*
			 * It could happen if initial mmap_size + alignment
			 * overflows the sizeof uint64, which could happen if
			 * either mmap_size or alignment value is wrong.
			 *
			 * mmap() kernel implementation would return an error,
			 * but better catch it before and provide useful info
			 * in the logs.
			 */
			RTE_LOG(ERR, VHOST_CONFIG, "mmap size (0x%" PRIx64 ") "
					"or alignment (0x%" PRIx64 ") is invalid\n",
					reg->size + mmap_offset, alignment);
			goto free_mem_table;
		}

		populate = (dev->dequeue_zero_copy) ? MAP_POPULATE : 0;
		mmap_addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
				 MAP_SHARED | populate, reg->fd, 0);

		if (mmap_addr == MAP_FAILED) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"mmap region %u failed.\n", i);
			goto free_mem_table;
		}

		reg->mmap_addr = mmap_addr;
		reg->mmap_size = mmap_size;
		reg->host_user_addr = (uint64_t)(uintptr_t)mmap_addr +
				      mmap_offset;

		if (dev->dequeue_zero_copy)
			if (add_guest_pages(dev, reg, alignment) < 0) {
				RTE_LOG(ERR, VHOST_CONFIG,
					"adding guest pages to region %u failed.\n",
					i);
				goto free_mem_table;
			}

		RTE_LOG(INFO, VHOST_CONFIG,
			"guest memory region %u, size: 0x%" PRIx64 "\n"
			"\t guest physical addr: 0x%" PRIx64 "\n"
			"\t guest virtual  addr: 0x%" PRIx64 "\n"
			"\t host  virtual  addr: 0x%" PRIx64 "\n"
			"\t mmap addr : 0x%" PRIx64 "\n"
			"\t mmap size : 0x%" PRIx64 "\n"
			"\t mmap align: 0x%" PRIx64 "\n"
			"\t mmap off  : 0x%" PRIx64 "\n",
			i, reg->size,
			reg->guest_phys_addr,
			reg->guest_user_addr,
			reg->host_user_addr,
			(uint64_t)(uintptr_t)mmap_addr,
			mmap_size,
			alignment,
			mmap_offset);

		if (dev->postcopy_listening) {
			/*
			 * We haven't a better way right now than sharing
			 * DPDK's virtual address with Qemu, so that Qemu can
			 * retrieve the region offset when handling userfaults.
			 */
			memory->regions[i].userspace_addr =
				reg->host_user_addr;
		}
	}
	if (dev->postcopy_listening) {
		/* Send the addresses back to qemu */
		msg->fd_num = 0;
		send_vhost_reply(main_fd, msg);

		/* Wait for qemu to acknolwedge it's got the addresses
		 * we've got to wait before we're allowed to generate faults.
		 */
		VhostUserMsg ack_msg;
		if (read_vhost_message(main_fd, &ack_msg) <= 0) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"Failed to read qemu ack on postcopy set-mem-table\n");
			goto free_mem_table;
		}

		if (validate_msg_fds(&ack_msg, 0) != 0)
			goto free_mem_table;

		if (ack_msg.request.master != VHOST_USER_SET_MEM_TABLE) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"Bad qemu ack on postcopy set-mem-table (%d)\n",
				ack_msg.request.master);
			goto free_mem_table;
		}

		/* Now userfault register and we can use the memory */
		for (i = 0; i < memory->nregions; i++) {
#ifdef RTE_LIBRTE_VHOST_POSTCOPY
			reg = &dev->mem->regions[i];
			struct uffdio_register reg_struct;

			/*
			 * Let's register all the mmap'ed area to ensure
			 * alignment on page boundary.
			 */
			reg_struct.range.start =
				(uint64_t)(uintptr_t)reg->mmap_addr;
			reg_struct.range.len = reg->mmap_size;
			reg_struct.mode = UFFDIO_REGISTER_MODE_MISSING;

			if (ioctl(dev->postcopy_ufd, UFFDIO_REGISTER,
						&reg_struct)) {
				RTE_LOG(ERR, VHOST_CONFIG,
					"Failed to register ufd for region %d: (ufd = %d) %s\n",
					i, dev->postcopy_ufd,
					strerror(errno));
				goto free_mem_table;
			}
			RTE_LOG(INFO, VHOST_CONFIG,
				"\t userfaultfd registered for range : "
				"%" PRIx64 " - %" PRIx64 "\n",
				(uint64_t)reg_struct.range.start,
				(uint64_t)reg_struct.range.start +
				(uint64_t)reg_struct.range.len - 1);
#else
			goto free_mem_table;
#endif
		}
	}

	for (i = 0; i < dev->nr_vring; i++) {
		struct vhost_virtqueue *vq = dev->virtqueue[i];

		if (vq->desc || vq->avail || vq->used) {
			/*
			 * If the memory table got updated, the ring addresses
			 * need to be translated again as virtual addresses have
			 * changed.
			 */
			vring_invalidate(dev, vq);

			dev = translate_ring_addresses(dev, i);
			if (!dev) {
				dev = *pdev;
				goto free_mem_table;
			}

			*pdev = dev;
		}
	}

	dump_guest_pages(dev);

	return RTE_VHOST_MSG_RESULT_OK;

free_mem_table:
	free_mem_region(dev);
	rte_free(dev->mem);
	dev->mem = NULL;
free_guest_pages:
	rte_free(dev->guest_pages);
	dev->guest_pages = NULL;
close_msg_fds:
	close_msg_fds(msg);
	return RTE_VHOST_MSG_RESULT_ERR;
}

地址的转换是在下面的函数:

/* Converts QEMU virtual address to Vhost virtual address. */
static uint64_t
qva_to_vva(struct virtio_net *dev, uint64_t qva, uint64_t *len)
{
	struct rte_vhost_mem_region *r;
	uint32_t i;

	if (unlikely(!dev || !dev->mem))
		goto out_error;

	/* Find the region where the address lives. */
	for (i = 0; i < dev->mem->nregions; i++) {
		r = &dev->mem->regions[i];

		if (qva >= r->guest_user_addr &&
		    qva <  r->guest_user_addr + r->size) {

			if (unlikely(*len > r->guest_user_addr + r->size - qva))
				*len = r->guest_user_addr + r->size - qva;

			return qva - r->guest_user_addr +
			       r->host_user_addr;
		}
	}
out_error:
	*len = 0;

	return 0;
}

数据的实际通信,在前面分析过,就是“rte_vhost_enqueue_burst”和“rte_vhost_dequeue_burst”这两个函数。

3、通知机制

基本上是采用eventfd的方式,这和网络通信保持一致:

static int
vhost_user_set_vring_kick(struct virtio_net **pdev, struct VhostUserMsg *msg,
			int main_fd __rte_unused)
{
	struct virtio_net *dev = *pdev;
	struct vhost_vring_file file;
	struct vhost_virtqueue *vq;
	int expected_fds;

	expected_fds = (msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK) ? 0 : 1;
	if (validate_msg_fds(msg, expected_fds) != 0)
		return RTE_VHOST_MSG_RESULT_ERR;

	file.index = msg->payload.u64 & VHOST_USER_VRING_IDX_MASK;
	if (msg->payload.u64 & VHOST_USER_VRING_NOFD_MASK)
		file.fd = VIRTIO_INVALID_EVENTFD;
	else
		file.fd = msg->fds[0];
	RTE_LOG(INFO, VHOST_CONFIG,
		"vring kick idx:%d file:%d\n", file.index, file.fd);

	/* Interpret ring addresses only when ring is started. */
	dev = translate_ring_addresses(dev, file.index);
	if (!dev) {
		if (file.fd != VIRTIO_INVALID_EVENTFD)
			close(file.fd);

		return RTE_VHOST_MSG_RESULT_ERR;
	}

	*pdev = dev;

	vq = dev->virtqueue[file.index];

	/*
	 * When VHOST_USER_F_PROTOCOL_FEATURES is not negotiated,
	 * the ring starts already enabled. Otherwise, it is enabled via
	 * the SET_VRING_ENABLE message.
	 */
	if (!(dev->features & (1ULL << VHOST_USER_F_PROTOCOL_FEATURES))) {
		vq->enabled = 1;
		if (dev->notify_ops->vring_state_changed)
			dev->notify_ops->vring_state_changed(
				dev->vid, file.index, 1);
	}

	if (vq->kickfd >= 0)
		close(vq->kickfd);
	vq->kickfd = file.fd;

	if (vq_is_packed(dev)) {
		if (vhost_check_queue_inflights_packed(dev, vq)) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"failed to inflights for vq: %d\n", file.index);
			return RTE_VHOST_MSG_RESULT_ERR;
		}
	} else {
		if (vhost_check_queue_inflights_split(dev, vq)) {
			RTE_LOG(ERR, VHOST_CONFIG,
				"failed to inflights for vq: %d\n", file.index);
			return RTE_VHOST_MSG_RESULT_ERR;
		}
	}

	return RTE_VHOST_MSG_RESULT_OK;
}

上述交互使用Poll机制,也就是轮询,来不断驱动着数据的流动。

四、总结

可以说从内核转到用户空间本身就是一个非常大的进步。减少甚至不和内核打交道,这实际就是设计上的解耦,同时增加了内核的安全性。反而效率成为了一种为了需要产生的有益的副作用。从这一点可以看出,软件设计思想的提高和在实际上的普及应用,是非常重要的。想到,才有可能做到。文章来源地址https://www.toymoban.com/news/detail-424171.html

到了这里,关于DPDK系列之十五虚拟化virtio源码分析之vhost-user的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • KVM虚拟化解决方案系列之KVM架构篇

    虚拟化是云计算的基础,在有虚拟化之前,一个物理主机上只能安装一个操作系统和运行一个核心业务程序。在有了虚拟化之后,一个物理主机上可以运行多台虚拟机,虚拟机上可以安装不同的操作系统和运行不同的核心业务程序,虚拟机共享物理主机的CPU、内存、I/O硬件资

    2024年02月09日
    浏览(39)
  • KVM虚拟化解决方案系列之KVM部署篇(1-4)

    通过《KVM虚拟化解决方案系列之KVM架构篇》我们了解了KVM的基本架构之后,那么接下来继续介绍如何使用KVM来搭建自己的虚拟化环境,搭建环境如表1所示。 表1. KVM搭建环境 主机名 角色 操作系统 IP地址 备注 kvm01 KVM主机1 CentOS-7-x86_64-DVD-1810.ISO 192.168.150.151 kvm02 KVM主机2 ubuntu-

    2024年02月14日
    浏览(39)
  • KVM虚拟化解决方案系列之KVM管理工具-libvirt介绍篇

    KVM作为后起之秀,在公有云Hytervisor市场中占主宰地位,如一大批基于OpenStack二次开发的云厂商。而老牌的商业VMware则在私有云Hytervisor市场中占主宰地位,仍然是各大中小企业搭建私有云的首选,不过目前也受到Hyper-V的挑战。 Hypervisor虚拟化技术有很多种实现方式,如KVM、Q

    2024年02月06日
    浏览(45)
  • 【SA8295P 源码分析】17 - 设备虚拟化 之 Passthrough透传、Vdev Trap、HAB Socket 原理解析

    【源码分析】 因为一些原因,本文需要移除, 对于已经购买的兄弟,不用担心,不是跑路, 我会继续持续提供技术支持, 有什么模块想学习的,或者有什么问题有疑问的, 请私聊我,我们 +VX 沟通技术问题,一起学习,一起进步 接下来,我一一私聊已经购买的兄弟添加V

    2024年02月12日
    浏览(35)
  • Linux Kernel入门到精通系列讲解(QEMU-虚拟化篇) 2.2 新增加CPU外设之UART、中断控制器和pFLASH

    上一章节我们将 CPU 和部分 memory 已经初始化完成了,具体实现可以查看代码仓库,本章节我们将在上一章节的基础之上去增加 pFlash 设备(作为初始化完 ROM 后的第一个代码存储区)、中断控制器和 UART 设备。

    2024年04月25日
    浏览(38)
  • Linux Kernel入门到精通系列讲解(QEMU-虚拟化篇) 2.1 新增加一个RISC-V CPU(NARUTO-PI)

    上一章节我们讲解了开源的 QEMU 开发板怎么启动,从这章节开始,我们将会亲手去从无到有开发一个 CPU ,它包括 CPU Core , Memory Device , Communication Controller 和 Device 等等。 注意,本章节中调用的很多自定义宏都在 include/hw/riscv/naruto.h 文件,这里我就不展开说了,大家下载我

    2024年04月25日
    浏览(38)
  • 云计算虚拟化技术与开发-------虚拟化技术应用第一章内容(虚拟化技术概念、虚拟化特征、虚拟化目的、半虚拟化和全虚拟化特点和区别、虚拟化实现的三种结构的特点和区别)

    目录 虚拟化技术第一章主要内容 虚拟化技术的概念: 虚拟化的特征:         虚拟化的目的: 虚拟化与云计算的关系: 半虚拟化和全虚拟化的特点和区别:  虚拟化实现的三种结构的特点和区别:         虚拟化(Virtualization)是把物理资源转变为逻辑上可以管理

    2024年02月03日
    浏览(53)
  • 什么是网络虚拟化 网络虚拟化简介

    这个概念产生的比较久了,VLAN,VPN, VPLS等 都可以归为网络虚拟化的技术。近年来,云计算的浪潮席卷IT界。几乎所有的IT基础构架都在朝着云的方向发展。在云计算的发展中,虚拟化技术一直是重要的推动因素。作为基础构架,服务器和存储的虚拟化已经发展的有声有色,

    2024年02月05日
    浏览(48)
  • 云计算基础-计算虚拟化-内存虚拟化

    内存在物理上是由内存卡提供的,也就是我们俗称的内存条,内存条提供了物理内存。 在物理内存之上还有虚拟内存,虚拟内存操作系统给程序分配的一段连续的内存,属于逻辑上的概念。 虚拟内存和物理内存之间会有一个映射关系,这个映射关系我们称之为页表,通过页

    2024年02月20日
    浏览(55)
  • 01 openEuler虚拟化-KVM虚拟化简介

    1.1 简介 在计算机技术中,虚拟化是一种资源管理技术,它将计算机的各种实体资源(处理器、内存、磁盘、网络适配器等)予以抽象、转换后呈现,并可分割、组合为一个或多个计算机配置环境。这种资源管理技术打破了实体结构不可分割的障碍,使这些资源在虚拟化后不

    2023年04月26日
    浏览(73)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包