使用Virtio Driver实现一个计算阶乘的小程序——QEMU平台
目录
一、概述
二、代码部分
1、Virtio 前端
(1) User Space
(2) Kernel Space
2、Virtio 后端
三、运行
QEMU Version:qemu-7.2.0
Linux Version:linux-5.4.239
一、概述
本篇文章的主要内容是使用Virtio前后端数据传输的机制实现一个计算阶乘的小程序,主要功能是在Virtio driver中传递一个整数到Virtio device,在Virtio device中计算这个整数的阶乘,计算完成后再将计算结果传递给Virtio driver,下面是代码部分。
二、代码部分
代码主要分为两个部分,分别是Virtio前端(Guest Os)和Virtio后端(QEMU),而Virtio前端又分User Space和Kernel Space。
1、Virtio 前端
(1) User Space
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>int main(int argc, char *argv[])
{int fd, retvalue;uint32_t factorial[1];if(argc != 2) {printf("ERROR: please enter two parameters!\n");return -1;}factorial[0] = atoi(argv[1]); /* string to number */fd = open("/dev/virtio_misc", O_RDWR);if(fd < 0) {printf("ERROR: virtio_misc open failed!\n");return -1;}retvalue = write(fd, factorial, sizeof(factorial));if(retvalue < 0) {printf("ERROR: write failed!\r\n");close(fd);return -1;}close(fd);return 0;
}
(2) Kernel Space
linux-5.4.239/drivers/virtio/Makefile
......
obj-y += virtio_test.o
......
linux-5.4.239/include/uapi/linux/virtio_ids.h
#ifndef _LINUX_VIRTIO_IDS_H
#define _LINUX_VIRTIO_IDS_H
/** Virtio IDs**/......#define VIRTIO_ID_TEST 45 /* virtio test */#endif /* _LINUX_VIRTIO_IDS_H */
linux-5.4.239/include/uapi/linux/virtio_test.h
#ifndef _LINUX_VIRTIO_TEST_H_
#define _LINUX_VIRTIO_TEST_H_#include <linux/types.h>
#include <linux/virtio_types.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>#define VIRTIO_TEST_F_CAN_PRINT 0struct virtio_test_config {__u32 num_pages;__u32 actual;
};struct virtio_test_stat {__virtio16 tag;__virtio64 val;
} __attribute__((packed));#endif
linux-5.4.239/drivers/virtio/virtio_test.c
#include <linux/virtio.h>
#include <linux/virtio_test.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>#define MISC_NAME "virtio_misc"
#define MISC_MINOR 144struct test_request {__virtio32 arg1;char arg2[32];
};struct test_response {__virtio32 ret;
};struct virtio_test {struct test_request req;struct test_response res;struct virtio_device *vdev;struct virtqueue *factorial_vq;
};static struct virtio_test *vt_dev;static void print_response_data(struct virtio_test *vt)
{printk("virtio response ret is %d\n",vt->res.ret);
}/* Called from virtio device, in IRQ context */
static void test_request_done(struct virtqueue *vq)
{uint32_t len;struct virtio_test *vt;printk(" %s called, line: %d \n", __func__, __LINE__);do {virtqueue_disable_cb(vq);while ((vt = virtqueue_get_buf(vq, &len)) != NULL) {// request packet will be completed by response packetprint_response_data(vt);}if (unlikely(virtqueue_is_broken(vq)))break;} while (!virtqueue_enable_cb(vq));
}static void build_test_request(struct virtio_test *vt, uint32_t num)
{vt->req.arg1 = num;strncpy(vt->req.arg2, "hello back end!", sizeof(vt->req.arg2));
}static void virtio_test_submit_request(uint32_t num)
{struct virtqueue *vq;struct virtio_test *vt;struct scatterlist out_sg, in_sg, *sgs[2];int num_out = 0, num_in = 0;vt = vt_dev;vq = vt->factorial_vq;build_test_request(vt, num);sg_init_one(&out_sg, &vt->req, sizeof(vt->req));sgs[num_out++] = &out_sg;sg_init_one(&in_sg, &vt->res, sizeof(vt->res));sgs[num_out + num_in++] = &in_sg;/* We should always be able to add one buffer to an empty queue. */virtqueue_add_sgs(vq, sgs, num_out, num_in, vt, GFP_ATOMIC);virtqueue_kick(vq);
}static int init_vqs(struct virtio_test *vt)
{int err, nvqs;struct virtqueue *vqs[1];vq_callback_t *callbacks[] = { test_request_done };const char * const names[] = { "virtio_test"};nvqs = virtio_has_feature(vt->vdev, VIRTIO_TEST_F_CAN_PRINT) ? 1 : 0;err = virtio_find_vqs(vt->vdev, nvqs, vqs, callbacks, names, NULL);if (err)return err;vt->factorial_vq = vqs[0];return 0;
}static void remove_common(struct virtio_test *vt)
{vt->vdev->config->reset(vt->vdev);vt->vdev->config->del_vqs(vt->vdev);
}static int virtio_misc_open(struct inode *inode, struct file *filp)
{return 0;
}static int virtio_misc_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t virtio_misc_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{int ret;uint32_t factorial[1];ret = copy_from_user(factorial, buf, count);if(ret < 0)return -EINVAL;virtio_test_submit_request(factorial[0]);return 0;
}struct file_operations virtio_misc_fops = {.owner = THIS_MODULE,.open = virtio_misc_open,.release = virtio_misc_release,.write = virtio_misc_write,
};static struct miscdevice virtio_miscdev = {.minor = MISC_MINOR,.name = MISC_NAME,.fops = &virtio_misc_fops,
};static int virttest_probe(struct virtio_device *vdev)
{int err;struct virtio_test *vt;if (!vdev->config->get) {return -EINVAL;}vdev->priv = vt = kmalloc(sizeof(*vt), GFP_KERNEL);if (!vt) {err = -ENOMEM;goto out;}vt->vdev = vdev;err = init_vqs(vt);if (err)goto out_free_vt;virtio_device_ready(vdev);vt_dev = vt;/* misc driver registered */err = misc_register(&virtio_miscdev);if(err < 0) {printk( "misc register is failed\n");goto out_free_misc;}printk( "misc register has succeeded\n");return 0;out_free_misc:misc_deregister(&virtio_miscdev);
out_free_vt:kfree(vt);
out:return err;
}static void virttest_remove(struct virtio_device *vdev)
{struct virtio_test *vt = vdev->priv;remove_common(vt);kfree(vt);vt_dev = NULL;misc_deregister(&virtio_miscdev);
}static struct virtio_device_id id_table[] = {{ VIRTIO_ID_TEST, VIRTIO_DEV_ANY_ID },{ 0 },
};static unsigned int features[] = {VIRTIO_TEST_F_CAN_PRINT,
};static struct virtio_driver virtio_test_driver = {.feature_table = features,.feature_table_size = ARRAY_SIZE(features),.driver.name = KBUILD_MODNAME,.driver.owner = THIS_MODULE,.id_table = id_table,.probe = virttest_probe,.remove = virttest_remove,
};module_virtio_driver(virtio_test_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio test driver");
MODULE_LICENSE("GPL");
下面对 virtio_test.c 文件中的virtio_test_submit_request函数进行解释,函数如下:
static void virtio_test_submit_request(uint32_t num)
{struct virtqueue *vq;struct virtio_test *vt;struct scatterlist out_sg, in_sg, *sgs[2];int num_out = 0, num_in = 0;vt = vt_dev;vq = vt->factorial_vq;build_test_request(vt, num);sg_init_one(&out_sg, &vt->req, sizeof(vt->req));sgs[num_out++] = &out_sg;sg_init_one(&in_sg, &vt->resp, sizeof(vt->resp));sgs[num_out + num_in++] = &in_sg;/* We should always be able to add one buffer to an empty queue. */virtqueue_add_sgs(vq, sgs, num_out, num_in, vt, GFP_ATOMIC);virtqueue_kick(vq);
}
virtio_test_submit_request函数主要用来构建前端请求包并将数据数据添加到Vring中,然后通知QEMU后端,参数num是用户传递的一个参数,进入到函数里面, build_test_request 函数用来构建请求包。
sg_init_one(&out_sg, &vb->req, sizeof(vb->req));
sgs[num_out++] = &out_sg;
sg_init_one(&in_sg, &vb->res, sizeof(vb->res));
sgs[num_out + num_in++] = &in_sg;
Virtio前后端数据传输是通过Linux内核中的scatter-gather(SG)列表来进行管理的。scatter-gather列表是一种数据结构,用于将多个不连续的内存块组合成一个逻辑上的连续块,以便进行数据传输。
sg_init_one函数初始化两个SG条目out_sg和in_sg,分别指向vb->req和vb->res,并设置其大小为sizeof(vb->req)和sizeof(vb->res)。vb->req的内容即为一个请求数据包,用于写入到后端设备,而vb->res则是用来存放从设备接收到的数据。
sgs[num_out++] = &out_sg是将out_sg的地址添加到sgs数组中。num_out是一个索引,表示添加到列表中的输出SG条目的数量。通过num_out++,确保下一个输出SG条目将被添加到数组的下一个位置。
sgs[num_out + num_in++] = &in_sg则是将in_sg的地址添加到sgs数组中,添加的位置是基于已添加的num_out和num_in之和,这里num_in 初始化为 0,所以 in_sg 被添加到了out_sg的后面,在这里sgs数组的前半部分也就是sgs[0]用于存储输出SG条目,而后半部分sgs[1]用于存储输入SG条目。通过num_in++,确保下一个输入SG条目被添加到sgs的适当位置。
virtqueue_add_sgs(vq, sgs, num_out, num_in, vt, GFP_ATOMIC);
vq: 指向一个virtqueue结构体的指针,这个结构体就是host和guest之间通信的一个虚拟队列。
sgs: 指向一个scatterlist结构体数组的指针,表示scatterlist元素指向内存中的一个物理地址非连续区域,也就是上面填充的sgs[2]数组。
num_out: 指定了sgs数组中用于输出的scatterlist的数量。
num_in: 指定了sgs数组中用于输入的scatterlist的数量。
vt: struct virtio_test 类型的一个结构体。
GFP_ATOMIC: 表示这个操作应该在原子上下文中进行,不能睡眠(即不能等待I/O操作或内存分配)。
virtqueue_add_sgs 函数主要将一组散列列表添加到虚拟队列vq中,而在virtqueue_add_sgs函数中又会调用virtqueue_add函数,用来将新的数据更新到 vring_virtqueue->vring的具体实现。
最后在调用 virtqueue_kick 函数通知QEMU 后端有数据更新了。
2、Virtio 后端
qemu-7.2.0/hw/virtio/meson.build
......virtio_ss.add(when: 'CONFIG_VIRTIO_TEST', if_true: files('virtio-test.c'))......
qemu-7.2.0/hw/virtio/Kconfig
config VIRTIO_TESTbooldefault ydepends on VIRTIO
qemu-7.2.0/include/standard-headers/linux/virtio_ids.h
#ifndef _LINUX_VIRTIO_IDS_H
#define _LINUX_VIRTIO_IDS_H
/** Virtio IDs**/
......#define VIRTIO_ID_TEST 45 /* virtio test */......
#endif /* _LINUX_VIRTIO_IDS_H */
qemu-7.2.0/include/standard-headers/linux/virtio_test.h
#ifndef _LINUX_VIRTIO_TEST_H
#define _LINUX_VIRTIO_TEST_H#include "standard-headers/linux/types.h"
#include "standard-headers/linux/virtio_types.h"
#include "standard-headers/linux/virtio_ids.h"
#include "standard-headers/linux/virtio_config.h"#define VIRTIO_TEST_F_CAN_PRINT 0struct virtio_test_config {uint32_t num_pages;uint32_t actual;uint32_t event;
};struct virtio_test_stat {__virtio16 tag;__virtio64 val;
} QEMU_PACKED;#endif
qemu-7.2.0/include/hw/virtio/virtio-test.h
#ifndef QEMU_VIRTIO_TEST_H
#define QEMU_VIRTIO_TEST_H#include "standard-headers/linux/virtio_test.h"
#include "hw/virtio/virtio.h"#define TYPE_VIRTIO_TEST "virtio-test-device"
#define VIRTIO_TEST(obj) \OBJECT_CHECK(VirtIOTest, (obj), TYPE_VIRTIO_TEST)typedef struct VirtIOTest {VirtIODevice parent_obj;VirtQueue *ivq;uint32_t host_features;QEMUTimer *stats_timer;uint32_t actual;uint32_t event;uint32_t num_pages;size_t stats_vq_offset;VirtQueueElement *stats_vq_elem;
} VirtIOTest;#endif
qemu-7.2.0/hw/virtio/virtio.c
const char *virtio_device_names[] = {......[VIRTIO_ID_TEST] = "virtio-test"
};
qemu-7.2.0/hw/virtio/virtio-test.c
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/iov.h"
#include "hw/virtio/virtio.h"
#include "sysemu/kvm.h"
#include "sysemu/hax.h"
#include "exec/address-spaces.h"
#include "qapi/error.h"
#include "qapi/qapi-events-misc.h"
#include "qapi/visitor.h"
#include "qemu/error-report.h"
#include "hw/virtio/virtio-bus.h"
#include "hw/virtio/virtio-access.h"
#include "migration/migration.h"#include "hw/virtio/virtio-test.h"static uint32_t Queue_Size = 128;struct test_request {uint32_t arg1;char arg2[32];
};struct test_response {uint32_t ret;
};static uint32_t factorial(uint32_t n) { uint32_t result = 1;for (uint32_t i = 1; i <= n; i++) { result *= i; } return result;
}static void print_req_and_build_resp_pack(struct test_request *req, struct test_response *res)
{ qemu_log("QEMU: >>> get arg1 [ %d ] form the front end <<<\n", req->arg1);qemu_log("QEMU: >>> get arg2 [ %s ] form the front end <<<\n", req->arg2);res->ret = factorial(req->arg1);
}static void virtio_test_handle_output(VirtIODevice *vdev, VirtQueue *vq)
{struct test_request req;struct test_response res;VirtQueueElement *elem;size_t offset = 0;for (;;) {elem = virtqueue_pop(vq, sizeof(VirtQueueElement));if (!elem)return;if (!iov_to_buf(elem->out_sg, elem->out_num, offset, &req, sizeof(req))) {qemu_log("QEMU ERROR: iov_to_buf function failed.\n");virtqueue_detach_element(vq, elem, 0);continue;}print_req_and_build_resp_pack(&req, &res);iov_from_buf(elem->in_sg, elem->in_num, offset, &res, sizeof(res));virtqueue_push(vq, elem, sizeof(res));virtio_notify(vdev, vq);g_free(elem);}
}static void virtio_test_get_config(VirtIODevice *vdev, uint8_t *config_data)
{VirtIOTest *dev = VIRTIO_TEST(vdev);struct virtio_test_config config;config.actual = cpu_to_le32(dev->actual);config.event = cpu_to_le32(dev->event);memcpy(config_data, &config, sizeof(struct virtio_test_config));
}static void virtio_test_set_config(VirtIODevice *vdev,const uint8_t *config_data)
{VirtIOTest *dev = VIRTIO_TEST(vdev);struct virtio_test_config config;memcpy(&config, config_data, sizeof(struct virtio_test_config));dev->actual = le32_to_cpu(config.actual);dev->event = le32_to_cpu(config.event);
}static uint64_t virtio_test_get_features(VirtIODevice *vdev, uint64_t f,Error **errp)
{VirtIOTest *dev = VIRTIO_TEST(vdev);f |= dev->host_features;virtio_add_feature(&f, VIRTIO_TEST_F_CAN_PRINT);return f;
}static void virtio_test_device_realize(DeviceState *dev, Error **errp)
{VirtIODevice *vdev = VIRTIO_DEVICE(dev);VirtIOTest *s = VIRTIO_TEST(dev);virtio_init(vdev, VIRTIO_ID_TEST, sizeof(struct virtio_test_config));s->ivq = virtio_add_queue(vdev, Queue_Size, virtio_test_handle_output);
}static void virtio_test_device_unrealize(DeviceState *dev)
{VirtIODevice *vdev = VIRTIO_DEVICE(dev);virtio_cleanup(vdev);
}static int virtio_test_post_load_device(void *opaque, int version_id)
{return 0;
}static const VMStateDescription vmstate_virtio_test_device = {.name = "virtio-test-device",.version_id = 1,.minimum_version_id = 1,.post_load = virtio_test_post_load_device,.fields = (VMStateField[]) {VMSTATE_UINT32(actual, VirtIOTest),VMSTATE_END_OF_LIST()},
};static const VMStateDescription vmstate_virtio_test = {.name = "virtio-test",.minimum_version_id = 1,.version_id = 1,.fields = (VMStateField[]) {VMSTATE_VIRTIO_DEVICE,VMSTATE_END_OF_LIST()},
};static Property virtio_test_properties[] = {DEFINE_PROP_END_OF_LIST(),
};static void virtio_test_class_init(ObjectClass *klass, void *data)
{DeviceClass *dc = DEVICE_CLASS(klass);VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);dc->props_ = virtio_test_properties;dc->vmsd = &vmstate_virtio_test;set_bit(DEVICE_CATEGORY_MISC, dc->categories);vdc->realize = virtio_test_device_realize;vdc->unrealize = virtio_test_device_unrealize;vdc->get_config = virtio_test_get_config;vdc->set_config = virtio_test_set_config;vdc->get_features = virtio_test_get_features;vdc->vmsd = &vmstate_virtio_test_device;
}static const TypeInfo virtio_test_info = {.name = TYPE_VIRTIO_TEST,.parent = TYPE_VIRTIO_DEVICE,.instance_size = sizeof(VirtIOTest),.class_init = virtio_test_class_init,
};static void virtio_register_types(void)
{type_register_static(&virtio_test_info);
}type_init(virtio_register_types)
下面对virtio-test.c文件中的virtio_test_handle_output函数进行分析,如下:
static void virtio_test_handle_output(VirtIODevice *vdev, VirtQueue *vq)
{struct test_request req;struct test_response res;VirtQueueElement *elem;size_t offset = 0;for (;;) {elem = virtqueue_pop(vq, sizeof(VirtQueueElement));if (!elem)return;if (!iov_to_buf(elem->out_sg, elem->out_num, offset, &req, sizeof(req))) {qemu_log("QEMU ERROR: iov_to_buf function failed.\n");virtqueue_detach_element(vq, elem, 0);continue;}print_req_and_build_resp_pack(&req, &res);iov_from_buf(elem->in_sg, elem->in_num, offset, &res, sizeof(res));virtqueue_push(vq, elem, sizeof(res));virtio_notify(vdev, vq);g_free(elem);}
}
VirtQueueElement 结构体:
typedef struct VirtQueueElement
{unsigned int index;unsigned int len;unsigned int ndescs;unsigned int out_num;unsigned int in_num;hwaddr *in_addr;hwaddr *out_addr;struct iovec *in_sg;struct iovec *out_sg;
} VirtQueueElement;struct iovec {void *iov_base;size_t iov_len;
};
VirtQueueElement 结构体如上所示,in_addr和 out_addr保存的是guest的物理地址,而in_sg和out_sg中的地址是host的虚拟地址,物理地址和虚拟地址之间需要进行映射。
index:记录该buffer的首个物理内存块对应的描述符在描述符表中的下标,因为一个buffer数据可能由多个物理内存保存。
out_num/in_num:表示输出和输入块的数量。一个buffer可能包含可读区和可写区,因为一个buffer由多个物理块组成,有的物理块是可读而有的物理块是可写,out_num表示可读块的数量,而in_num表示可写块的数量。
in_addr/out_addr:记录的是可读块和可写块的物理地址(客户机的物理地址)。因为in_addr/out_addr是客户机的物理地址,如果host要访问这些地址,则需要将Guest物理地址映射成Host的虚拟地址。
in_sg/out_sg:根据上面的分析,in_sg和out_sg就是保存的对应Guest物理块在Host的虚拟地址和长度。
elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
virtqueue_pop函数主要功能为:
1、以 vq->last_avail_idx为索引从VRingAvail的ring数组中获取一个buffer head索引,并赋值到elem.index,然后获取各个guest物理buffer的相关信息。
2、将可写的物理buffer地址(客户机物理地址)记录到in_addr数组中,而可读的记录到out_addr数组中,并记录in_num和out_num,直到最后一个desc记录完毕。
3、获取完成后再将in_addr和out_addr映射成虚拟地址,并记录到in_sg和out_sg数组中,这样才可以在host中访问到。
调用virtqueue_pop函数之后,QEMU后端就已经获取了buffer的相关信息,继续分析
if (!iov_to_buf(elem->out_sg, elem->out_num, offset, &req, sizeof(req))) {......print_req_and_build_resp_pack(&req, &res);iov_from_buf(elem->in_sg, elem->in_num, offset, &res, sizeof(res));
iov_to_buf 函数用于将 iovec 结构体数组中的数据复制到用户提供的缓冲区中,函数参数解释如下:
elem->out_sg:指向iovec结构体数组的指针。
elem->out_num:指定了elem->out_sg数组中iovec结构体的数量。
offset:指定了从哪个位置开始复制数据。
&req:存放Guest前端request的缓冲区指针,把从iovec数组中读取的数据复制到这个缓冲区中。
sizeof(req):这个参数指定了目标缓冲区req的大小,即函数最多可以复制多少字节到req中。
函数会从 elem->out_sg 指向的 iovec 数组开始,跳过 offset 指定的字节数,然后将数据复制到 req 指向的缓冲区中,直到达到 req 的大小限制或所有 iovec 中的数据都被复制完毕为止。
经过前面的分析,输出项out_sg指向的地址的内容就读取到了req结构体中,然后读取req结构体中的内容即可读取前端的数据,在这里是调用print_req_and_build_resp_pack函数,获取req中的数据计算阶乘,并初始化好struct test_response为返回前端数据做准备。
iov_from_buf(elem->in_sg, elem->in_num, offset, &resp, sizeof(resp));
elem->in_sg:指向一个iovec数组的指针,用于存储数据的分段信息。
elem->in_num:表示elem->in_sg数组中可以使用的iovec的数量。
offset:从缓冲区开始复制的偏移量。
&resp:将数据从res复制到iov向量列表中去。
sizeof(resp):res的大小。
和iov_to_buf函数的操作相反,iov_from_buf函数是将一段数据buf(res)的内容复制到由 iovec 数组描述的内存区域中去,也就是elem->in_sg中。
到目前为止就完成了根据前端传递来的数据计算阶乘,并将response包放入了in_sg中,然后调用virtqueue_push函数取消之前物理内存映射到虚拟内存的操作,并更新vring_ used表,如下:
void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem,unsigned int len)
{RCU_READ_LOCK_GUARD();virtqueue_fill(vq, elem, len, 0);virtqueue_flush(vq, 1);
}
最后再调用 virtio_notify 函数告诉前端传递过来的数据已经处理完毕了,然后前端再做一些其它的处理。
三、运行
在运行 qemu 时需要加上 -device virtio-test-device 参数,例如:
......
-machine virt \
-machine gic_version=3 \
-smp 4 \
-m 1024 \
-display none -nographic \
-device virtio-test-device \
......
如果编译成功运行 User Space 程序即可,运行结果如下:

相关文章:
使用Virtio Driver实现一个计算阶乘的小程序——QEMU平台
目录 一、概述 二、代码部分 1、Virtio 前端 (1) User Space (2) Kernel Space 2、Virtio 后端 三、运行 QEMU Version:qemu-7.2.0 Linux Version:linux-5.4.239 一、概述 本篇文章的主要内容是使用Virtio前后端数据传输的机制实现一个计算阶乘的…...
【PyCharm】配置“清华镜像”地址
文章目录 前言一、清华镜像是什么?二、pip是什么?三、具体步骤1.复制镜像地址2.打开PyCharm,然后点击下图红框的选项3.在弹出的新窗口点击下图红框的选项进行添加4.在URL输入框中粘贴第一步复制的地址,名字可以不更改,…...
IO器件性能评估
整体逻辑:需要先了解到读写速率的差异,在明确使用场景。比如应用启动过程中的IO主要是属于随机读的io 评估逻辑: UFS 与 eMMC主要差别在io读写能力: 1,对比UFS、eMMC的规格书标注的io读写能力 ufs spec : sequentia…...
在js中判断对象是空对象的几种方法
使用 Object.keys() 方法 Object.keys() 方法返回对象自身的可枚举属性名称组成的数组。如果数组的长度为 0,那么对象是空的。 function isEmptyObject(obj) {return Object.keys(obj).length 0 && obj.constructor Object; }const obj1 {}; const obj2…...
【整理】后端接口设计和优化相关思路汇总
文章目录 明确的接口定义和文档化使用RESTful设计规范分页和过滤合理使用缓存限流与熔断机制安全性设计异步处理与后台任务接口参数校验(入参和出参)接口扩展性考虑核心接口,线程池隔离关键接口,日志打印接口功能单一性原则接口查…...
docker 部署 sql server
众所周知,sql server不好装,本人之前装了两次,这个数据库简直是恶心。 突然想到,用docker容器吧 果然可以 记得放开1433端口 还有 记得docker加速,不然拉不到镜像的最后工具还是要装的,这个就自己研究吧。 …...
微信云开发云存储 下载全部文件
一、安装 首先按照这个按照好依赖,打开cmd 安装 | 云开发 CloudBase - 一站式后端云服务 npm i -g cloudbase/cli 安装可能遇到的问题 ‘tcb‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。-CSDN博客 二、登录 在cmd输入 tcb login 三、…...
1、巡线功能实现(7路数字循迹)
一、小车运行 1.PWM初始化函数 (pwm.c中编写) 包括四个轮子PWM通道使用的GPIO接口初始化、定时器初始化、PWM通道初始化。 void PWM_Init(uint16_t arr,uint16_t psc); 2.PWM占空比设置函数 (pwm.c中编写) 此函数调用了四个通道设置占空比的函数,作用是方便修改四…...
来了...腾讯内推的软件测试面试PDF 文档(共107页)
不多说,直接上干货(展示部分以腾讯面试纲要为例)完整版文末领取 通过大数据总结发现,其实软件测试岗的面试都是差不多的。常问的有下面这几块知识点: 全网首发-涵盖16个技术栈 第一部分,测试理论&#x…...
Android大脑--systemserver进程
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。 本文摘要 系统native进程的文章就先告一段落了,从这篇文章开始写Java层的文章,本文同样延续自述的方式来介绍systemserver进程,通过本文您将…...
python项目部署:Nginx和UWSGI认识
Nginx: HTTP服务器,反向代理,静态资源转发,负载均衡,SSL终端,缓存,高并发处理。 UWSGI: Python应用程序服务器,WSGI兼容,多进程管理,快速应用部署,多种协议支…...
【区块链+金融服务】农业大宗供应链线上融资平台 | FISCO BCOS应用案例
释放数据要素价值,FISCO BCOS 2024 应用案例征集 粮食贸易受季节性影响显著。每年的粮收季节,粮食收储企业会根据下游订单需求,从上游粮食贸易商或粮农手 里大量采购粮食,并分批销售给下游粮食加工企业(面粉厂、饲料厂…...
2025ICASSP Author Guidelines
Part I: General Information Procedure ICASSP 2025 论文提交与评审过程将与往届会议类似: 有意参加会议的作者需提交一份完整描述其创意和相关研究成果的文件,技术内容(包括图表和可能的参考文献)最多为4页&…...
Openstack 所需要的共享服务组件及核心组件
openstack 共享服务组件: 数据库服务(Database service):MariaDB及MongoDB 消息传输服务(messages queues):RabbitMQ 缓存(cache):Memcache 时间同步(time sync)&…...
解密Linux中的通用块层:加速存储系统,提升系统性能
通用块层 通用块层是Linux中的一个重要组件,用于管理不同块设备的统一接口,减少不同块设备的差异带来的影响。它位于文件系统和磁盘驱动之间,类似于Java中的适配器模式,让我们无需关注底层实现,只需提供固定接口即可。…...
浅析国有商业银行人力资源数字化平台建设
近年来,在复杂的国际经济金融环境下,中国金融市场整体运行保持稳定。然而,随着国内金融机构改革的不断深化,国有商业银行全面完成股改上市,金融市场规模逐步扩大,体系日益完善,同时行业的竞争也…...
微信h5跳转消息页关注公众号,关注按钮闪一下消失
一、需求背景 在微信里访问h5页面,在页面里跳转到微信公众号消息页关注公众号。如下图: 二、实现跳转消息页关注公众号 跳转链接是通过 https://mp.weixin.qq.com/mp/profile_ext?actionhome&__bizxxxxx&scene110#wechat_redirect 来实现。…...
掌握PyTorch的加权随机采样:WeightedRandomSampler全解析
标题:掌握PyTorch的加权随机采样:WeightedRandomSampler全解析 在机器学习领域,数据不平衡是常见问题,特别是在分类任务中。PyTorch提供了一个强大的工具torch.utils.data.WeightedRandomSampler,专门用于处理这种情况…...
网络丢包深度解析:影响、原因及优化策略
摘要 网络丢包是数据在传输过程中未能成功到达目的地的现象,它对网络通信的性能有着显著的影响。本文将深入探讨网络丢包的定义、原因、对性能的影响,以及如何通过技术手段进行检测和优化。 1. 网络丢包的定义 网络丢包发生在数据包在源和目的地之间的…...
Hadoop入门基础(一):深入探索Hadoop内部处理流程与核心三剑客
在大数据的世界里,处理海量数据的需求越来越多,而Hadoop作为开源的分布式计算框架,成为了这一领域的核心技术之一。 一、Hadoop简介 Hadoop是Apache Software Foundation开发的一个开源分布式计算框架,旨在使用简单的编程模型来…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
