热迁移中VirtIO-PCI设备的配置空间处理
文章目录
- 问题现象
- 定位过程
- 日志分析
- 源端
- 目的端
- 原理分析
- 基本原理
- 上下文分析
- 复现分析
- patch分析
- 总结
- 解决方案
问题现象
- 集群升级虚拟化组件版本,升级前存量运行并挂载了virtio磁盘的虚拟机集群内热迁移到升级后的节点失败,QEMU报错如下:
2023-09-15T04:52:39.221053Z qemu-kvm: get_pci_config_device: Bad config data: i=0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
2023-09-15T04:52:39.221140Z qemu-kvm: Failed to load PCIDevice:config
2023-09-15T04:52:39.221148Z qemu-kvm: Failed to load virtio-blk:virtio
2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device '0000:00:0b.0/virtio-blk'
定位过程
- 通过“Failed to load”关键字可以确认迁移目的端报错且该日志为目的端虚机启动是Qemu进程日志,通过日志关键字“Bad config data”,搜索集群其它节点是否有相同报错的虚机,搜索到另一个虚机迁移失败有相同报错,问题有机率复现。
日志分析
源端
- 选取其中一个问题虚机,查看源端虚机启动和热迁移发起的时间:
/* 虚机启动时间 */
2023-09-15 02:14:55.253+0000: starting up libvirt version:xxx
...
/* 虚机启动时pci号最大的一块virtio磁盘 */
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x9,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1,write-cache=on \
...
/* 虚机启动时pci号最大的设备 */
-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0xa \
/* 虚机第一次迁移时间 */
2023-09-15 04:52:30.383+0000: initiating migration
/* 虚机第二次迁移时间 */
2023-09-15 05:46:23.278+0000: initiating migration
- 从界面事件日志看,虚机在10:14被克隆后启动运行(Qemu日志比前端界面时间早8小时),启动时有一个磁盘,ID为virtio-disk0,pci的bdf号为0:9.0,启动后10:15界面显示立即热添加了一块磁盘。QEMU日志无记录。虚拟机分别在12:52和13:46发起热迁移,都失败,对应QEMU日志:
2023-09-15 04:52:30.383+0000: initiating migration
2023-09-15 05:46:23.278+0000: initiating migration
目的端
- 目的端虚机第一次启动时间
2023-09-15 04:52:30.070+0000: starting up libvirt version:xxx
...
/* 源端启动时挂载的virtio磁盘*/
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x9,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1,write-cache=on \
...
/* 源端启动后热添加的virtio磁盘 */
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0xb,drive=drive-virtio-disk1,id=virtio-disk1,bootindex=4,write-cache=on \
...
/* 源端启动后热添加的virtio网卡 */
-device virtio-net-pci,mq=on,vectors=10,rx_queue_size=1024,netdev=hostnet1,id=net1,mac=52:54:00:6a:ef:94,bus=pci.0,addr=0xc \
/* 热迁移目的端启动的虚机,命令行中会增加-incoming defer参数 */
/* 表示虚机内存的读取通过启动后的migrate_incoming qmp 命令指定 */
-incoming defer \
-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0xa \
/* 第一次迁移报错 */
2023-09-15T04:52:39.221053Z qemu-kvm: get_pci_config_device: Bad config data: i=0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
2023-09-15T04:52:39.221140Z qemu-kvm: Failed to load PCIDevice:config
2023-09-15T04:52:39.221148Z qemu-kvm: Failed to load virtio-blk:virtio
2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device '0000:00:0b.0/virtio-blk'
...
- 第一次迁移失败时间点2023-09-15 04:52,目的端报错:
get_pci_config_device: Bad config data: i=0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
- 第二次迁移失败时间点2023-09-15 05:46,目的端报相同错误。另外,目的端QEMU启动命令行比源端多出两个-device,分别是热添加的virtio-disk1磁盘和net1网卡,bdf号分别是00:b.0,00:c.0。
原理分析
- 通过上面的日志,我们仅知道迁移报错了,报错的关键函数是
get_pci_config_device
,这个函数在迁移中起什么作用呢?为什么会有pci配置空间的报错?这个报错涉及哪些基本原理呢?下面我们简单分析关于virtio-pci设备的基本原理。
基本原理
- pci配置空间布局
pci规范定义pci配置空间长度为256byte,其中通用头部为64byte,也称为预定义空间,通用头部的前16byte格式如下,之后就是bar空间及其它内容,偏移0x5字节定义了status字段,其中有1bit定义为Capabilities List,它是pci规范定义的附加空间标志位,Capabilities List的意义是允许在pci设备配置空间之后加上额外的寄存器,这些寄存器由Capability List组织起来,用来实现特定的功能(virtio-pci基于该特性实现各类设备),附加空间在64字节配置空间之后,该bit为1表示在capabilities pointer偏移处(0x34)存放了附加寄存器组的起始偏移。
- virtio-pci配置空间布局
virtio-pci通过capabilities list存放规范中定义的数据结构,结构如下:
- list由若干元素连接而成,每个元素的头三个字节有通用的格式:第1 byte为capability ID,表示实现了何种capability,对于virtio-blk,其ID为0x90; 第2 byte为list中下一个元素的偏移,如果list结束,第2 byte为0;第3 byte为元素的长度。以virtio-blk为例,每个元素格式如下:
/* This is the PCI capability header: */
struct virtio_pci_cap {__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */__u8 cap_next; /* Generic PCI field: next ptr. */__u8 cap_len; /* Generic PCI field: capability length */__u8 cfg_type; /* Identifies the structure. */__u8 bar; /* Where to find it. */__u8 id; /* Multiple capabilities of the same type */__u8 padding[2]; /* Pad to full dword. */__le32 offset; /* Offset within bar. */__le32 length; /* Length of the structure, in bytes. */
};
- 可以看到virtio-pci规范也遵循了pci的规范,从第四个字节开始,为virtio-blk特有内容,其中cfg_type用于标识virtio-pci定义的数据结构类型,定义如下:
/* Common configuration */
#define VIRTIO_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
/* ISR Status */
#define VIRTIO_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define VIRTIO_PCI_CAP_PCI_CFG 5
/* Shared memory region */
#define VIRTIO_PCI_CAP_SHARED_MEMORY_CFG 8
/* Vendor-specific data */
#define VIRTIO_PCI_CAP_VENDOR_CFG 9
- 整个virtio-blk配置空间内容如下:
- 上面是virtio pci设备的简单介绍,完整的分析可以参考:VirtIO实现原理——PCI基础
上下文分析
- 了解基础的virtio pci设备配置空间后,继续分析迁移报错的出错上下文:
static int get_pci_config_device(QEMUFile *f, void *pv, size_t size,VMStateField *field)
{PCIDevice *s = container_of(pv, PCIDevice, config);PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(s);uint8_t *config; /* 1byte的指针 */int i;assert(size == pci_config_size(s));config = g_malloc(size); /* 分配0x100=256字节内存用来存放pci的配置空间信息 */qemu_get_buffer(f, config, size); /* 从迁移流中读取256字节的pci配置空间内容*/for (i = 0; i < size; ++i) { /* 按字节移动逐一比较配置空间内容 */if ((config[i] ^ s->config[i]) & /* 如果配置空间不相同,报错 */s->cmask[i] & ~s->wmask[i] & ~s->w1cmask[i]) {error_report("%s: Bad config data: i=0x%x read: %x device: %x ""cmask: %x wmask: %x w1cmask:%x", __func__,i, config[i], s->config[i],s->cmask[i], s->wmask[i], s->w1cmask[i]);g_free(config);return -EINVAL;}}memcpy(s->config, config, size);......
}
- get_pci_config_device实现目的端加载pci设备配置空间,从迁移的流中读取源端传来的pci设备配置空间内容。再比较本地QEMU实例化pci设备得到的pci空间内容,如果两个值不同,说明源端pci设备的配置空间内容和目标端初始化的pci设备配置空间内容不同,通常该问题是由于迁移两端的同一个virtio-pci设备有不同的配置导致,比如virtio队列数配置不同会报类似错误:
get_pci_config_device: Bad config data: i=0x9a read: 1 device: 2 cmask: ff wmask: 0 w1cmask:0
- 报错原因是从源端读取到的virtio队列数为1,但目标端初始化队列数为2。其中i=0x90表示读取内容在pci配置空间(总长256byte)的偏移。分析本次报错:
get_pci_config_device: Bad config data: i=0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
- 目的端在配置空间偏移0x10的地方读取了一字节的数据,源端内容为0xc1,目的端内容为0x1,高4bit的内容不同,源端为0xc,目的端为0x0。pci配置空间0x10及BAR0的内容(参考基本原理一节),因此进一步确定是迁移两端的同一个virtio-pci设备BAR0内容不同导致的报错。
- BAR0中存放的是什么地址呢?
BAR0映射了1个virtio-pci规范定义的IO空间,该IO空间用于实现对virtio设备配置空间访问的一个可选方法,IO空间格式如下:
struct virtio_pci_cfg_cap {struct virtio_pci_cap cap;u8 pci_cfg_data[4]; /* Data for BAR access. */
};
- 当Guest驱动想访问某个virtio-blk配置空间的某个区域(common configuration, notification, ISR and device-specific configuration)时,首先获取要访问的bar号(cap.bar),长度(cap.length)和偏移(cap.offset),将其设置到cap中(其它字段: cap.cap_vndr:0x9, cap.cap_next:70,cap._cap_len:14,cap.cfg_type: 05,与capabilies list中的VIRTIO_PCI_CAP_PCI_CFG元素相同),实际动作是往BAR0中记录的IO地址写上述内容,QEMU会将对应的信息放到pci_cfg_data中供Guest驱动读取。通过这样的方式,Guest驱动可以实现对virtio-blk配置空间数据的访问。
- 从上可知,迁移的virtio设备,由于源端上BAR0地址的bit[4,7]内容为0xc,目的端BAR0地址的bit[4,7]内容为0,分析目的端日志:
2023-09-15T04:52:39.221154Z qemu-kvm: error while loading state for instance 0x0 of device ‘0000:00:0b.0/virtio-blk’
迁移的设备是1个磁盘设备,其bdf号为00:0b.0,对比源端的virtio-pci设备:
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x9,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1,write-cache=on
-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0xa
- 第1个磁盘的pci号为00:09.0,随后是virtio-balloon-pci设备,分配到00:0a.0,pci bdf为00:0b.0的设备在QEMU源端日志的启动命令行中并没有出现,因此只可能是热插拔的设备分配到了该bdf号,从界面看,虚机启动后有热添加设备事件,首先是热添加磁盘,之后是热添加网卡。假定pci号按顺序被分配(通常如此),则热添加的virtio磁盘分配到的bdf号为00:0b.0,热添加的virtio网卡分配到的bdf号为00:0c.0。进一步描述迁移失败过程是在旧版本环境中通过克隆创建的虚机,磁盘热添加后,热迁移到升级后版本的节点报错。按照该方法,可以稳定复现该问题。
复现分析
- 查看复现的虚机PCI设备空间布局
virsh qemu-monitor-command {vm_uuid} --hmp info pci
...Bus 0, device 9, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xd080 [0xd0bf].BAR1: 32 bit memory at 0xfea59000 [0xfea59fff].BAR4: 64 bit prefetchable memory at 0xfe208000 [0xfe20bfff].id "virtio-disk0"Bus 0, device 10, function 0:Class 0255: PCI device 1af4:1002IRQ 10.BAR0: I/O at 0xd100 [0xd11f].BAR4: 64 bit prefetchable memory at 0xfe20c000 [0xfe20ffff].id "balloon0"Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xffc0 [0xffff].BAR1: 32 bit memory at 0xfebff000 [0xfebfffff].BAR4: 64 bit prefetchable memory at 0x4287fffc000 [0x4287fffffff].id "virtio-disk1"
...
- 可以看到第一个磁盘的BAR0空间bit[4,7]内容为0xc,与QEMU日志报错信息匹配:
get_pci_config_device: Bad config data: i=0x10 read: c1 device: 1 cmask: ff wmask: 80 w1cmask:0
- 源端选取一个相同硬件配置的虚机,查看第二个virtio磁盘的pci信息BAR0地址相同:
Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xffc0 [0xffff].BAR1: 32 bit memory at 0xfebff000 [0xfebfffff].BAR4: 64 bit prefetchable memory at 0x4287fffc000 [0x4287fffffff].id "virtio-disk1"
- 冷重启该虚机,为保证QEMU分配的pci号不变,以下面的步骤冷重启该虚机:
virsh dumpxml {vm_uuid} >> {vm_uuid}.xml
virsh destroy {vm_uuid}
virsh undefine {vm_uuid}
virsh define {vm_uuid}.xml
virsh start {vm_uuid}
- 查看冷重启后虚机的第2块盘的pci信息如下:
Bus 0, device 11, function 0:SCSI controller: PCI device 1af4:1001IRQ 0.BAR0: I/O at 0xd080 [0xd0ff].BAR1: 32 bit memory at 0xfea9a000 [0xfea9afff].BAR4: 64 bit prefetchable memory at 0xfe210000 [0xfe213fff].id "virtio-disk1"
- 两个版本设备的IO空间的确发生了变化。新版本BAR0的IO空间边长。
- 对比存量运行虚机和冷重启后虚机设备的IO空间,冷重启后的虚机磁盘的BAR0 IO空间从3f扩大为7f。
patch分析
- 分析新版本引入的特性中,与virtio-blk设备相关的patch只有TRIM/UNMAP特性,社区patch如下:
37b06f8d46 virtio-blk: add DISCARD and WRITE_ZEROES features
20764be042 virtio-blk: set config size depending on the features enabled
ba550851f5 virtio-net: make VirtIOFeature usable for other virtio devices
5c81161f80 virtio-blk: add "discard" and "write-zeroes" properties
bbe8bd4d85 virtio-blk: add host_features field in VirtIOBlock
00f639fb8f virtio-blk: add acct_failed param to virtio_blk_handle_rw_error
- 该commit是vhost-user-blk实现TRIM/UNMAP的核心修改,为什么这个commit会导致virito-blk的BAR0 IO空间变长?因为这个commit需要定义virtio spec要求的discard/write_zeroes相关参数,因此扩展了virtio-blk的配置空间,配置空间原来提供的内容以下数据结构:
struct virtio_blk_config {/* The capacity (in 512-byte sectors). */uint64_t capacity;/* The maximum segment size (if VIRTIO_BLK_F_SIZE_MAX) */uint32_t size_max;/* The maximum number of segments (if VIRTIO_BLK_F_SEG_MAX) */uint32_t seg_max;/* geometry of the device (if VIRTIO_BLK_F_GEOMETRY) */struct virtio_blk_geometry {uint16_t cylinders;uint8_t heads;uint8_t sectors;} geometry;/* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */uint32_t blk_size;/* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY *//* exponent for physical block per logical block. */uint8_t physical_block_exp;/* alignment offset in logical blocks. */uint8_t alignment_offset;/* minimum I/O size without performance penalty in logical blocks. */uint16_t min_io_size;/* optimal sustained I/O size in logical blocks. */uint32_t opt_io_size;/* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */uint8_t wce;uint8_t unused;/* number of vqs, only available when VIRTIO_BLK_F_MQ is set */uint16_t num_queues;
}
- 该数据结构总长度为36字节,再加上struct virtio_pci_cap的长度16字节,总计52字节。IO空间为64字节,可以满足IO访问要求。当引入discard/write_zeroes之后,数据结构struct virtio_blk_config新增了以下字段:
+
+ /* the next 3 entries are guarded by VIRTIO_BLK_F_DISCARD */
+ /*
+ * The maximum discard sectors (in 512-byte sectors) for
+ * one segment.
+ */
+ uint32_t max_discard_sectors;
+ /*
+ * The maximum number of discard segments in a
+ * discard command.
+ */
+ uint32_t max_discard_seg;
+ /* Discard commands must be aligned to this number of sectors. */
+ uint32_t discard_sector_alignment;
+
+ /* the next 3 entries are guarded by VIRTIO_BLK_F_WRITE_ZEROES */
+ /*
+ * The maximum number of write zeroes sectors (in 512-byte sectors) in
+ * one segment.
+ */
+ uint32_t max_write_zeroes_sectors;
+ /*
+ * The maximum number of segments in a write zeroes
+ * command.
+ */
+ uint32_t max_write_zeroes_seg;
+ /*
+ * Set if a VIRTIO_BLK_T_WRITE_ZEROES request may result in the
+ * deallocation of one or more of the sectors.
+ */
+ uint8_t write_zeroes_may_unmap;
+
+ uint8_t unused1[3];
- 总计增加了24字节。因此IO空间至少需要52+24 = 76字节才能满足访问要求。QEMU按照64字节对齐,如果新版本按照开源的策略默认打开discard特性,在初始化virtio-blk设备时会将IO空间扩展为128字节,配置空间会变长:
virtio_init(vdev, "virtio-blk", VIRTIO_ID_BLOCK, sizeof(struct virtio_blk_config));
总结
- TRIM/UNMAP特性的引入使高版本QEMU在模拟virtio-blk设备时,PCI配置空间变长。会导致迁移是目的端加载设备状态报错。
- QEMU virtio设备在支持新特性时,目的端除了检查两端源端的feature是否是目的端的子集,还会检查virtio-pci配置空间内容:
- 如果新特性仅仅引入feature bit,前端如果不支持,在前后端协商时,该feature bit不会被置位。因此热迁移时,就算目的端默认开启新特性,也不会在热迁移过程中进行协商,最终目的端feature也不会包含该特性。只有冷重启后才能生效。这种情况不存在热迁移兼容性问题。
- 如果新特性不仅引入feature bit,还对virtio-pci规范中定义的配置空间数据结构进行了扩展,从而引起配置空间变化,比如这里的TRIM/UNMAP特性,则会存在热迁移兼容性问题。
解决方案
- QEMU virtio-pci设备在初始化pci空间时,应该根据feature是否使能来动态计算PCI空间的长度并初始化,只有使能该特性的feature,才能将其涉及的数据结构计算到PCI空间的长度中,否则不应该在配置空间提供该feature相关数据结构。社区在下面的commit实现了该逻辑:
20764be042 virtio-blk: set config size depending on the features enabled
- 控制面如Libvirt,在涉及到平滑升级的处理时,如果目的端有新增的feature且涉及virtio设备配置空间改变时,应该在热迁移时显式关闭该特性,从而保证热迁移源端和目的端配置空间相同。
相关文章:

热迁移中VirtIO-PCI设备的配置空间处理
文章目录 问题现象定位过程日志分析源端目的端 原理分析基本原理上下文分析复现分析patch分析 总结解决方案 问题现象 集群升级虚拟化组件版本,升级前存量运行并挂载了virtio磁盘的虚拟机集群内热迁移到升级后的节点失败,QEMU报错如下: 202…...

模拟滤波器的基础知识和设计
信号处理工作中滤波器的应用是非常广泛的,可以分成模拟滤波器和数字滤波器两种,数字滤波器主要包括两种,IIR和FIR,这两种滤波器后面统一说,今天先来说一说模拟滤波器(主要是我先用Python实现了Matlab书里面…...
机器学习基础-Pandas学习笔记
Pandas Python的数据分析库,与Numpy配合使用,可以从常见的格式如CSV、JSON等中读取数据。可以进行数据清洗、数据加工工作。数据结构Series,Pandas.Series(data,index,dtype,name,copy) data类型是Numpy的ndarray类型,index指定下…...
【GIT版本控制】--协作流程
一、Fork与Pull Request Git协作流程中的关键概念包括Fork和Pull Request,它们允许多人在项目中协作并贡献代码。以下是关于Fork和Pull Request的简要总结: 1. Fork: Fork是指复制一个Git仓库,通常是一个开源项目的仓库…...
简析Cookie、Session、Token
手打不易,如果转摘,请注明出处! 注明原文:https://zhangxiaofan.blog.csdn.net/article/details/133498756 文章目录 简析Cookie、Session、Token什么是 Cookie ?什么是 Session ?Cookie 和 Session 到底是…...

加速attention计算的工业标准:flash attention 1和2算法的原理及实现
transformers目前大火,但是对于长序列来说,计算很慢,而且很耗费显存。对于transformer中的self attention计算来说,在时间复杂度上,对于每个位置,模型需要计算它与所有其他位置的相关性,这样的计…...
小程序获取用户手机号
在小程序中获取用户手机号需要以下步骤: 首先需要授权用户手机号,即在小程序中调用 wx.login() 方法获取用户的登录凭证,在回调函数中调用 wx.getUserInfo() 方法获取用户的个人信息,并且设置 withCredentials 参数为 true。 在获…...

Zama的fhEVM:基于全同态加密实现的隐私智能合约
1. 引言 Zama的fhEVM定位为: 基于全同态加密实现的隐私智能合约 解决方案 开源代码见: https://github.com/zama-ai/fhevm(TypeScript Solidity) Zama的fhEVM协议中主要包含: https://github.com/zama-ai/tfhe-…...
Mac M1安装ROS1或ROS2
1.首先进入Anaconda官网,安装Anaconda 2.创建、激活并配置环境 #创建环境 conda create -n ROS #激活环境 conda activate ROS #配置环境 conda config --add channels conda-forge conda config --add channels robostack conda config --set channel_priority st…...

[NISACTF 2022]popchains - 反序列化+伪协议
[NISACTF 2022]popchains 一、解题流程二、小小疑惑 一、解题流程 1、链条:Road_is_Long(construct->wakeup【page$r】-> toString【string$m】)-> Make_a_Change(construct->get【effort$t】)-> Try_W…...

分贝定义简介
一、什么是分贝 辅助单元Bel表示任何给定部件、电路或系统的输入和输出之间的对数比L,并且可以用电压、电流或功率来表示: 如果使用场量(电压或电流)代替功率量,则: 我们可以将增益或损耗因子相加为正或负dB值,而不是将其乘以比率。 分贝与功率转化的速读表如下所示:…...

socket简介
套接字(Socket)实质上就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,为应用层进程利网络协议交换数据提供了相应机制。套接字出于承上启下的作用,向上连接应用进程…...

【AI视野·今日Robot 机器人论文速览 第四十九期】Fri, 6 Oct 2023
AI视野今日CS.Robotics 机器人学论文速览 Fri, 6 Oct 2023 Totally 29 papers 👉上期速览✈更多精彩请移步主页 Interesting: 📚ContactGen, 基于生成模型的抓取手势生成,类人五指手。(from 伊利诺伊大学 香槟) 数据集:GRAB da…...
七、互联网技术——SQL查询
文章目录 一、基础查询二、高级查询三、SQL视图一、基础查询 某学校的教学信息关系数据库中有如下两个表(表的名字和字段均用中文名字)学生表(学号,姓名,性别,专业)成绩表(学号,课程名,分数)用SQL语句表达下述查询:[问题1]检索分数高于80分的所有学生的学号和分数select 学…...

1.6 计算机网络的性能
思维导图: 1.6.1 计算机网络的性能指标 前言: 我的理解: 这段前言主要介绍了关于计算机网络性能的两个方面的讨论。首先,计算机网络的性能可以通过一些重要的性能指标来衡量。但除了这些指标之外,还有一些非性能特征…...

小程序中如何核销订单和优惠券
小程序已成为许多商家线上线下开展业务的重要渠道。客户在小程序中下单/领券后,可能需要商家现场扫码核销,例如超市购物、卖票、游乐园等线下场景。下面就介绍小程序中如何核销订单和优惠券。 一、订单核销 订单核销是指商家在小程序中确认顾客已经支付…...
211 毕业就入职 30 人的小公司是什么体验
为什么“选择”了 30 人的小公司? 作为一个 211 毕业的学生,进入 30 人的小公司不管是 8 年前还是现在,应该都是比较稀少的,但是当面的我阴差阳错进了这样一个小公司。 为什么我选择进入这样一个 30 人的小公司呢?主…...
aardio 读取 Excel文件,显示在 listview 中
编写 main.aardio 如下 import win.ui; /*DSG{{*/ winform win.form(text"excel1";right801;bottom500) winform.add( button1{cls"button";text"读取Excel文件";left19;top14;right126;bottom44;z1}; button2{cls"button";text&quo…...

Web:前端常用的几种Http请求GET和POST样例
1、简述 在Web开发过程中,少不了发起Http请求服务端的接口数据,在不同的框架中使用了不同的Http请求方式,常用的请求有fetch、 ajax、 axios、XMLHttpRequest、request,以下样例仅供参考。 2、Fetch Fetch API 是一种 JavaScr…...
clickonce 发布的winform 如何CA认证?
要为使用ClickOnce发布的WinForms应用程序启用CA(证书颁发机构)认证,您可以按照以下步骤进行操作: 1. **获取数字证书**: - 首先,您需要获得一个数字证书,通常从受信任的CA购买。这个数字证…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
土建施工员考试:建筑施工技术重点知识有哪些?
《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目,核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容,附学习方向和应试技巧: 一、施工组织与进度管理 核心目标: 规…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...
「Java基本语法」变量的使用
变量定义 变量是程序中存储数据的容器,用于保存可变的数据值。在Java中,变量必须先声明后使用,声明时需指定变量的数据类型和变量名。 语法 数据类型 变量名 [ 初始值]; 示例:声明与初始化 public class VariableDemo {publi…...