使用Virtio Driver实现一个计算阶乘的小程序——QEMU平台

embedded/2024/9/23 9:18:03/

目录

一、概述

二、代码部分

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.ppmy.cn/embedded/99557.html

相关文章

计算机网络——TCP协议与UDP协议详解(下)

一、TCP协议 1.1 TCP协议的报文 TCP全称为 "传输控制协议(Transmission Control Protocol")。人如其名&#xff0c;要对数据的传输进行一个详细的控制。我们先看其报文格式&#xff0c;如下图&#xff1a; TCP报文由以下几个字段组成&#xff1a; 源端口号和目标端口…

两张图理解ARP原理

同一网段 不同网段

Eureka原理与实践:构建高效的微服务架构

Eureka原理与实践&#xff1a;构建高效的微服务架构 Eureka的核心原理Eureka Server&#xff1a;服务注册中心Eureka Client&#xff1a;服务提供者与服务消费者 Eureka的实践应用集成Eureka到Spring Cloud项目中创建Eureka Server创建Eureka Client&#xff08;服务提供者&…

追问试面试系列:分布式事务

hi 你好 感谢您的围观,本篇文章主要是分享分布式事务相关的问题。面试题一共整理19道,从分布式事务的一些理论开始,最后道项目中的分布式事务问题。 话不多说,我们直接进入正文。 面试官:什么是分布式事务? 其实嘛,分布式事务从实质上看与数据库事务的概念是一致的,…

Linux下获取文件或目录的状态信息

一.概述 在Linux下进行文件、目录编程时经常需要获取指定文件的属性信息&#xff0c;比如: 文件类型、大小、创建日期、修改日期等属性信息。 获取这些属性信息非常有用,比如: 打开一个设备文件时&#xff0c;为了防止未知错误&#xff0c;可以提前判断打开的设备文件是否是正确…

绕过检测与持久化的无文件攻击(第二篇)

1. 绕过防御机制的策略 混淆技术&#xff1a;使用 PowerShell 的混淆技术&#xff0c;例如 Base64 编码&#xff0c;来绕过防病毒软件的检测。内存混淆&#xff1a;通过动态分配和释放内存&#xff0c;扰乱内存扫描工具。 2. 无文件持久化技术 WMI 事件订阅&#xff1a;利用…

如何保证每次生成的都同一张人脸?AI绘画Stable Diffusion的Reference only插件人物一致性教程

Ai绘画有一个很现实的问题&#xff0c;要保证每次画出的都是同一个人物的话&#xff0c;很费劲。 Midjourney就不必说了&#xff0c;人物的高度一致性一直得不到很好的解决。而在Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;常用办法是通过同一个Seed值&#xf…

信刻光盘摆渡系统安全合规实现跨网数据单向导入/导出

在当今信息化、数字化时代&#xff0c;各种数据传输和储存技术发展迅速&#xff0c;各安全领域行业对跨网数据交互需求日益迫切&#xff0c;数据传输的安全可靠性对于整个过程的重要性不可忽视。应如何解决网络安全与效率之间的矛盾&#xff0c;如何安全合规地实现跨网数据单向…