当前位置: 首页 > news >正文

使用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_sgin_sg,分别指向vb->reqvb->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_outnum_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&#xff1a;qemu-7.2.0 Linux Version&#xff1a;linux-5.4.239 一、概述 本篇文章的主要内容是使用Virtio前后端数据传输的机制实现一个计算阶乘的…...

【PyCharm】配置“清华镜像”地址

文章目录 前言一、清华镜像是什么&#xff1f;二、pip是什么&#xff1f;三、具体步骤1.复制镜像地址2.打开PyCharm&#xff0c;然后点击下图红框的选项3.在弹出的新窗口点击下图红框的选项进行添加4.在URL输入框中粘贴第一步复制的地址&#xff0c;名字可以不更改&#xff0c;…...

IO器件性能评估

整体逻辑&#xff1a;需要先了解到读写速率的差异&#xff0c;在明确使用场景。比如应用启动过程中的IO主要是属于随机读的io 评估逻辑&#xff1a; UFS 与 eMMC主要差别在io读写能力&#xff1a; 1&#xff0c;对比UFS、eMMC的规格书标注的io读写能力 ufs spec : sequentia…...

在js中判断对象是空对象的几种方法

使用 Object.keys() 方法 Object.keys() 方法返回对象自身的可枚举属性名称组成的数组。如果数组的长度为 0&#xff0c;那么对象是空的。 function isEmptyObject(obj) {return Object.keys(obj).length 0 && obj.constructor Object; }const obj1 {}; const obj2…...

【整理】后端接口设计和优化相关思路汇总

文章目录 明确的接口定义和文档化使用RESTful设计规范分页和过滤合理使用缓存限流与熔断机制安全性设计异步处理与后台任务接口参数校验&#xff08;入参和出参&#xff09;接口扩展性考虑核心接口&#xff0c;线程池隔离关键接口&#xff0c;日志打印接口功能单一性原则接口查…...

docker 部署 sql server

众所周知&#xff0c;sql server不好装&#xff0c;本人之前装了两次&#xff0c;这个数据库简直是恶心。 突然想到&#xff0c;用docker容器吧 果然可以 记得放开1433端口 还有 记得docker加速&#xff0c;不然拉不到镜像的最后工具还是要装的&#xff0c;这个就自己研究吧。 …...

微信云开发云存储 下载全部文件

一、安装 首先按照这个按照好依赖&#xff0c;打开cmd 安装 | 云开发 CloudBase - 一站式后端云服务 npm i -g cloudbase/cli 安装可能遇到的问题 ‘tcb‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。-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中编写) 此函数调用了四个通道设置占空比的函数&#xff0c;作用是方便修改四…...

来了...腾讯内推的软件测试面试PDF 文档(共107页)

不多说&#xff0c;直接上干货&#xff08;展示部分以腾讯面试纲要为例&#xff09;完整版文末领取 通过大数据总结发现&#xff0c;其实软件测试岗的面试都是差不多的。常问的有下面这几块知识点&#xff1a; 全网首发-涵盖16个技术栈 第一部分&#xff0c;测试理论&#x…...

Android大脑--systemserver进程

用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 本文摘要 系统native进程的文章就先告一段落了&#xff0c;从这篇文章开始写Java层的文章&#xff0c;本文同样延续自述的方式来介绍systemserver进程&#xff0c;通过本文您将…...

python项目部署:Nginx和UWSGI认识

Nginx: HTTP服务器&#xff0c;反向代理&#xff0c;静态资源转发&#xff0c;负载均衡&#xff0c;SSL终端&#xff0c;缓存&#xff0c;高并发处理。 UWSGI: Python应用程序服务器&#xff0c;WSGI兼容&#xff0c;多进程管理&#xff0c;快速应用部署&#xff0c;多种协议支…...

【区块链+金融服务】农业大宗供应链线上融资平台 | FISCO BCOS应用案例

释放数据要素价值&#xff0c;FISCO BCOS 2024 应用案例征集 粮食贸易受季节性影响显著。每年的粮收季节&#xff0c;粮食收储企业会根据下游订单需求&#xff0c;从上游粮食贸易商或粮农手 里大量采购粮食&#xff0c;并分批销售给下游粮食加工企业&#xff08;面粉厂、饲料厂…...

2025ICASSP Author Guidelines

Part I: General Information ​​​​​​Procedure ICASSP 2025 论文提交与评审过程将与往届会议类似&#xff1a; 有意参加会议的作者需提交一份完整描述其创意和相关研究成果的文件&#xff0c;技术内容&#xff08;包括图表和可能的参考文献&#xff09;最多为4页&…...

Openstack 所需要的共享服务组件及核心组件

openstack 共享服务组件&#xff1a; 数据库服务&#xff08;Database service&#xff09;:MariaDB及MongoDB 消息传输服务&#xff08;messages queues&#xff09;&#xff1a;RabbitMQ 缓存(cache)&#xff1a;Memcache 时间同步&#xff08;time sync&#xff09;&…...

解密Linux中的通用块层:加速存储系统,提升系统性能

通用块层 通用块层是Linux中的一个重要组件&#xff0c;用于管理不同块设备的统一接口&#xff0c;减少不同块设备的差异带来的影响。它位于文件系统和磁盘驱动之间&#xff0c;类似于Java中的适配器模式&#xff0c;让我们无需关注底层实现&#xff0c;只需提供固定接口即可。…...

浅析国有商业银行人力资源数字化平台建设

近年来&#xff0c;在复杂的国际经济金融环境下&#xff0c;中国金融市场整体运行保持稳定。然而&#xff0c;随着国内金融机构改革的不断深化&#xff0c;国有商业银行全面完成股改上市&#xff0c;金融市场规模逐步扩大&#xff0c;体系日益完善&#xff0c;同时行业的竞争也…...

微信h5跳转消息页关注公众号,关注按钮闪一下消失

一、需求背景 在微信里访问h5页面&#xff0c;在页面里跳转到微信公众号消息页关注公众号。如下图&#xff1a; 二、实现跳转消息页关注公众号 跳转链接是通过 https://mp.weixin.qq.com/mp/profile_ext?actionhome&__bizxxxxx&scene110#wechat_redirect 来实现。…...

掌握PyTorch的加权随机采样:WeightedRandomSampler全解析

标题&#xff1a;掌握PyTorch的加权随机采样&#xff1a;WeightedRandomSampler全解析 在机器学习领域&#xff0c;数据不平衡是常见问题&#xff0c;特别是在分类任务中。PyTorch提供了一个强大的工具torch.utils.data.WeightedRandomSampler&#xff0c;专门用于处理这种情况…...

网络丢包深度解析:影响、原因及优化策略

摘要 网络丢包是数据在传输过程中未能成功到达目的地的现象&#xff0c;它对网络通信的性能有着显著的影响。本文将深入探讨网络丢包的定义、原因、对性能的影响&#xff0c;以及如何通过技术手段进行检测和优化。 1. 网络丢包的定义 网络丢包发生在数据包在源和目的地之间的…...

Hadoop入门基础(一):深入探索Hadoop内部处理流程与核心三剑客

在大数据的世界里&#xff0c;处理海量数据的需求越来越多&#xff0c;而Hadoop作为开源的分布式计算框架&#xff0c;成为了这一领域的核心技术之一。 一、Hadoop简介 Hadoop是Apache Software Foundation开发的一个开源分布式计算框架&#xff0c;旨在使用简单的编程模型来…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...