使用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 程序即可,运行结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404087.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RabbitMQ】 相关概念 + 工作模式

本文将介绍一些MQ中常见的概念&#xff0c;同时也会简单实现一下RabbitMQ的工作流程。 MQ概念 Message Queue消息队列。是用来存储消息的队列&#xff0c;多用于分布式系统之间的通信。 系统间调用通常有&#xff1a;同步通信和异步通信。MQ就是在异步通信的时候使用的。 同…

萝卜快跑和端到端的自动驾驶(1)

先看一篇论文 2311.18636 (arxiv.org) 这篇论文里有一个非常好的图 比较了一下模块化任务(级联任务)和端到端自动驾驶的区别 首先什么叫模块化任务(级联) 如上图所示&#xff0c;左边的方块中的子方块&#xff0c;是展示了自动驾驶获取数据的途径&#xff0c;这里包括&…

Java之文件操作和IO

目录 File类 属性 构造方法 方法 文件内容的读写 InputStream OutputStream File类 属性 修饰符及类型属性说明static StringpathSeparator依赖于系统的路径分隔符&#xff0c;String类型的表示static charpathSeparator依赖于系统的路径分隔符&#xff0c;char类型的…

vscode 远程免密登录

Windows R 输入 cmd在命令行终端中输入 ssh-keygen 一直回车、确定 生成秘钥 3. C:\用户\xxx.ssh 拷贝公钥内容 id_rsa.pub 4. 在虚拟机~/.ssh/ 下创建文件touch authorized_keys,拷贝公钥内容 id_rsa.pub粘贴到authorized_keys里即可。

uniapp自定义请求头信息header

添加请求头&#xff1a;uniapp自定义请求头信息header&#xff0c;如下&#xff1a;添加tenant-id参数 代码

信创教育:培养未来科技创新的生力军

随着全球数字化转型的加速&#xff0c;信息技术应用创新&#xff08;简称“信创”&#xff09;产业作为推动国家信息技术自主可控和产业升级的关键领域&#xff0c;正迎来前所未有的发展机遇。信创教育&#xff0c;作为培养未来科技创新生力军的重要阵地&#xff0c;其重要性和…

win的netassist TCP测试工具和Linux的nc工具使用

写在前面 有时工作中我们编写一些tcp相关的程序&#xff0c;而netassist就是这样一款辅助我们进行TCP功能测试的工具&#xff0c;你可以从这里下载。 1&#xff1a;netassist使用 我们需要一个server程序&#xff0c;可以参考这篇文章&#xff0c;启动server后&#xff0c;就…

系列:水果甜度个人手持设备检测-产品规划的方案和实现思路

系列:水果甜度个人手持设备检测 -- 产品规划的方案和实现思路 背景 我们在前面篇章中&#xff0c;大致的检索了一下市面存在的产品&#xff0c;并采用启发性搜索的办法从国家知识产权局的专利库、中国知网CNKI的学术文献库、各种文章、论坛甚至是GitHub中对我们预研的方向进…

EmguCV学习笔记 VB.Net 2.S 特别示例

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 教程VB.net版本请访问&#xff1a;EmguCV学习笔记 VB.Net 目录-CSDN博客 教程C#版本请访问&#xff1a;EmguCV学习笔记 C# 目录-CSD…

【区块链+金融服务】中国银联区块链可信存证服务 | FISCO BCOS应用案例

随着金融行业信息化的快速推进&#xff0c;“互联网 金融”业务产生了海量的电子数据。例如&#xff0c;截止到 2022 年第二季度&#xff0c; 全国累计信用卡发卡数量约 8.07 亿张&#xff0c;累计银行卡应偿信贷余额为 8.66 万亿元&#xff0c;累计信用卡逾期半年未尝信贷 总…

盲盒抽奖源码

介绍&#xff1a; 功能上还可以,商品和盲盒可以在你程序里添加&#xff0c;设置概率等!! 新盲盒星球抽奖商城手机网站源码 随机开箱抢购 代码有点大&#xff0c;三百多M。 教程搭建很简单&#xff0c;基本10分钟搭建一套&#xff0c;可一个服务器搭建多套&#xff0c;只要你…

云计算实训31——playbook(剧本)基本应用、playbook常见语法、playbook和ansible操作的编排

playbook(剧本): 是ansible⽤于配置,部署,和管理被控节点的剧本。⽤ 于ansible操作的编排。 使⽤的格式为yaml格式 一、YMAL格式 以.yaml或.yml结尾 ⽂件的第⼀⾏以 "---"开始&#xff0c;表明YMAL⽂件的开始(可选的) 以#号开头为注释 列表中的所有成员都开始于…

不能使用乘除法、for、while、if、else、switch、case求1+2+3+...+n

求123...n_牛客题霸_牛客网 (nowcoder.com) 描述 求123...n&#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&#xff09;。 数据范围&#xff1a; 0<n≤2000<n≤200 进阶&#xff1a; 空间复杂度 O(1)O(…

深度相机与红外光相机+摄像头RTSP协议

0、绪论 一般而言&#xff0c;从深度信息的角度考虑相机&#xff0c;相机可以分为&#xff1a;单目&#xff0c;双目&#xff0c;RGB-D相机&#xff1b;单目或者双目相机都是通过被动的接收信息之后通过算法解算得到图片中的深度信息&#xff0c;​ RGB-D相机是主动式的&#…

本地私有化部署PDF处理神器Stirling PDF并实现无公网IP远程在线访问

文章目录 前言1. 安装Docker2. 本地安装部署StirlingPDF3. Stirling-PDF功能介绍4. 安装cpolar内网穿透5. 固定Stirling-PDF公网地址 前言 本篇文章我们将在Linux上使用Docker在本地部署一个开源的PDF工具——Stirling PDF&#xff0c;并且结合cpolar的内网穿透实现公网随时随…

通过共享目录上传后门

本文来自无问社区&#xff0c;更多实战内容可前往查看http://www.wwlib.cn/index.php/artread/artid/13337.html 操作步骤 枚举目标主机开启的共享服务信息&#xff1a;10.0.0.6 smbclient -L //10.0.0.6 -U spotWARNING: The "syslog" option is deprecated Ente…

【数据结构】关于Java对象比较,以及优先级队列的大小堆创建你了解多少???

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;这期讲解对象的比较&#xff0c;以及优先级队列堆&#xff0c;希望你能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/MSex7 &#x1f308;感兴趣的小伙伴看一看小编主页&…

LeetCode --- 410周赛

题目列表 3248. 矩阵中的蛇 3249. 统计好节点的数目 3250. 单调数组对的数目 I 3251. 单调数组对的数目 II 一、矩阵中的蛇 只要按照题目要求模拟即可&#xff0c;代码如下 class Solution { public:int finalPositionOfSnake(int n, vector<string>& commands…

9 算术、关系、逻辑、赋值、位操作、三元运算符及其优先级

目录​​​​​​​ 1 运算符基础 1.1 什么是运算符 1.2 什么是表达式 1.3 左操作数和右操作数 1.4 运算符分类 1.4.1 按照操作数个数分类 1.4.2 按照功能分类 1.5 如何掌握运算符 2 算术运算符 2.1 正号和负号 2.2 加、减、乘、除 2.3 取模&#xff08;取余&#…

Git的使用-初级

Git 主要可以使用的远程仓库有 Github &#xff0c;Gitee 如果在国内建议使用 Gitee 比较快 从远程仓库下载工程 在安装好了 Git 后&#xff0c;我们右键单击一个本地的文件夹作为下载的目的地&#xff0c;选择 Git Bash Here 便可以通过 Linux 命令行的形式操作 Git Linux…