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

C++并发编程之跨应用程序与驱动程序的单生产者单消费者队列

设计一个单生产者单消费者队列(SPSC队列),不使用C++ STL库或操作系统原子操作函数,并且将其放入跨进程共享内存中以便在Ring3(用户模式)和Ring0(内核模式)之间传递数据,是一个复杂的任务。这通常涉及到内核驱动开发和用户态程序设计的深入知识。

以下是一个简化的设计概述,用于指导如何构建这样的队列:

1. 定义队列结构

首先,你需要定义一个队列数据结构,它将存储在共享内存中。这个结构应该包含队列的头部和尾部指针,以及用于存储数据的缓冲区。

typedef struct QueueItem {// 数据字段,根据需要定义char data[DATA_SIZE];
} QueueItem;typedef struct SPSCQueue {volatile unsigned int head; // 队列头指针volatile unsigned int tail; // 队列尾指针QueueItem items[QUEUE_SIZE]; // 队列数据项
} SPSCQueue;

2. 共享内存创建与映射

在Ring3(用户态)和Ring0(内核态)之间共享内存通常涉及到创建一个内存映射区域,这可以通过操作系统提供的机制来完成,例如在Windows上可以使用内存映射文件。

在内核态,你需要使用特定的内核API来映射这块内存,以便内核态的代码和用户态的代码可以访问同一块内存区域。

3. 同步机制

由于不能使用原子操作函数,你需要实现一种简单的同步机制来确保生产者和消费者之间的正确交互。一种可能的方法是使用一个简单的自旋锁或者信号量机制。

例如,你可以使用一个标志位来表示队列是否正在被访问:

typedef struct SPSCQueue {volatile unsigned int head;volatile unsigned int tail;volatile bool isLocked; // 表示队列是否被锁定的标志位QueueItem items[QUEUE_SIZE];
} SPSCQueue;

生产者和消费者在操作队列之前都需要检查isLocked标志位。

4. 生产者逻辑

生产者负责将数据放入队列中。它应该遵循以下逻辑:

  1. 检查队列是否已满。
  2. 如果队列未满且未被锁定,则锁定队列,并添加数据到队列尾部。
  3. 更新尾部指针,并解锁队列。

5. 消费者逻辑

消费者负责从队列中取出数据。它应该遵循以下逻辑:

  1. 检查队列是否为空。
  2. 如果队列非空且未被锁定,则锁定队列,并从队列头部取出数据。
  3. 更新头部指针,并解锁队列。

6. 注意事项

  • 内存对齐:确保共享内存中的数据结构对齐,以避免潜在的内存访问问题。
  • 错误处理:添加适当的错误处理逻辑,以处理共享内存创建失败、队列满或空等异常情况。
  • 性能优化:由于不能使用原子操作,自旋锁可能会导致CPU资源的浪费。在实际应用中,可能需要考虑更高效的同步机制。

7. 跨Ring3/Ring0通信

在Ring3和Ring0之间通信时,需要确保内核态和用户态都能正确地访问和修改共享内存中的队列。在内核态中,你可能需要编写特定的驱动程序来处理这些操作,并确保安全性。

总的来说,这个设计是一个高级概述,具体实现将取决于你的操作系统和环境。在开发过程中,需要深入了解操作系统的内存管理、进程间通信以及内核态与用户态交互的细节。

根据前述设计思想实现一个单生产者单消费者队列(SPSC队列),并将其放入跨进程共享内存中,以便在Ring3(用户态)和Ring0(内核态)之间传递数据。这个实现将分为用户态(Ring3)和内核态(Ring0)两个部分。

用户态(Ring3)实现

用户态代码负责创建和映射共享内存,并实现生产者和消费者的逻辑。

1. 定义队列结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>#define QUEUE_SIZE 1024
#define DATA_SIZE 64typedef struct QueueItem {char data[DATA_SIZE];
} QueueItem;typedef struct SPSCQueue {volatile unsigned int head;volatile unsigned int tail;volatile bool isLocked;QueueItem items[QUEUE_SIZE];
} SPSCQueue;

2. 创建和映射共享内存
SPSCQueue* create_shared_memory(const char* name) {int fd = shm_open(name, O_CREAT | O_RDWR, 0666);if (fd == -1) {perror("shm_open");exit(EXIT_FAILURE);}if (ftruncate(fd, sizeof(SPSCQueue)) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}SPSCQueue* queue = (SPSCQueue*) mmap(NULL, sizeof(SPSCQueue), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (queue == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}close(fd);return queue;
}void destroy_shared_memory(const char* name, SPSCQueue* queue) {if (munmap(queue, sizeof(SPSCQueue)) == -1) {perror("munmap");exit(EXIT_FAILURE);}if (shm_unlink(name) == -1) {perror("shm_unlink");exit(EXIT_FAILURE);}
}void initialize_queue(SPSCQueue* queue) {queue->head = 0;queue->tail = 0;queue->isLocked = false;memset(queue->items, 0, sizeof(queue->items));
}

3. 生产者逻辑
void producer(SPSCQueue* queue) {while (1) {while (queue->isLocked) {// 自旋等待,直到队列解锁}// 检查队列是否已满if ((queue->tail + 1) % QUEUE_SIZE == queue->head) {printf("Queue is full, skipping...\n");continue;}queue->isLocked = true;// 写入数据snprintf(queue->items[queue->tail].data, DATA_SIZE, "Data %u", queue->tail);queue->tail = (queue->tail + 1) % QUEUE_SIZE;printf("Produced: %s\n", queue->items[(queue->tail + QUEUE_SIZE - 1) % QUEUE_SIZE].data);queue->isLocked = false;// 模拟生产时间sleep(1);}
}

4. 消费者逻辑
void consumer(SPSCQueue* queue) {while (1) {while (queue->isLocked) {// 自旋等待,直到队列解锁}// 检查队列是否为空if (queue->head == queue->tail) {printf("Queue is empty, waiting...\n");continue;}queue->isLocked = true;// 读取数据char data[DATA_SIZE];strncpy(data, queue->items[queue->head].data, DATA_SIZE);queue->head = (queue->head + 1) % QUEUE_SIZE;printf("Consumed: %s\n", data);queue->isLocked = false;// 模拟消费时间sleep(1);}
}

5. 主函数
int main() {const char* shm_name = "/spsc_queue";SPSCQueue* queue = create_shared_memory(shm_name);initialize_queue(queue);pid_t pid = fork();if (pid == 0) {// 子进程 (消费者)consumer(queue);} else {// 父进程 (生产者)producer(queue);}return 0;
}

Linux 实现

内核态(Ring0)实现

内核态代码需要编写一个内核模块,该模块可以访问和操作共享内存中的队列。这里提供一个简化的示例,展示如何在内核模块中访问共享内存。

1. 定义队列结构

内核态代码的队列结构与用户态代码相同:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>#define QUEUE_SIZE 1024
#define DATA_SIZE 64typedef struct QueueItem {char data[DATA_SIZE];
} QueueItem;typedef struct SPSCQueue {volatile unsigned int head;volatile unsigned int tail;volatile bool isLocked;QueueItem items[QUEUE_SIZE];
} SPSCQueue;

2. 访问共享内存
static SPSCQueue* queue;static int __init spsc_queue_init(void) {// 假设共享内存已经创建并映射到某个地址// 这里只是一个示例,实际情况下需要从用户态传递共享内存的地址queue = (SPSCQueue*) 0x12345678; // 假地址// 初始化队列(如果需要)queue->head = 0;queue->tail = 0;queue->isLocked = false;memset(queue->items, 0, sizeof(queue->items));printk(KERN_INFO "SPSC Queue module initialized.\n");return 0;
}static void __exit spsc_queue_exit(void) {printk(KERN_INFO "SPSC Queue module exited.\n");
}static int kernel_producer() {while (1) {while (queue->isLocked) {// 自旋等待,直到队列解锁}// 检查队列是否已满if ((queue->tail + 1) % QUEUE_SIZE == queue->head) {printk(KERN_INFO "Queue is full, skipping...\n");continue;}queue->isLocked = true;// 写入数据snprintf(queue->items[queue->tail].data, DATA_SIZE, "Data %u from kernel", queue->tail);queue->tail = (queue->tail + 1) % QUEUE_SIZE;printk(KERN_INFO "Produced: %s\n", queue->items[(queue->tail + QUEUE_SIZE - 1) % QUEUE_SIZE].data);queue->isLocked = false;schedule_timeout_interruptible(msecs_to_jiffies(1000)); // 模拟生产时间}return 0;
}static int kernel_consumer() {while (1) {while (queue->isLocked) {// 自旋等待,直到队列解锁}// 检查队列是否为空if (queue->head == queue->tail) {printk(KERN_INFO "Queue is empty, waiting...\n");continue;}queue->isLocked = true;// 读取数据char data[DATA_SIZE];strncpy(data, queue->items[queue->head].data, DATA_SIZE);queue->head = (queue->head + 1) % QUEUE_SIZE;printk(KERN_INFO "Consumed: %s\n", data);queue->isLocked = false;schedule_timeout_interruptible(msecs_to_jiffies(1000)); // 模拟消费时间}return 0;
}static struct task_struct *producer_task;
static struct task_struct *consumer_task;static int __init spsc_queue_init(void) {producer_task = kthread_create(kernel_producer, NULL, "kernel_producer");if (producer_task) {wake_up_process(producer_task);} else {printk(KERN_ERR "Failed to create producer task.\n");return -EFAULT;}consumer_task = kthread_create(kernel_consumer, NULL, "kernel_consumer");if (consumer_task) {wake_up_process(consumer_task);} else {printk(KERN_ERR "Failed to create consumer task.\n");return -EFAULT;}// 假设共享内存已经创建并映射到某个地址// 这里只是一个示例,实际情况下需要从用户态传递共享内存的地址queue = (SPSCQueue*) 0x12345678; // 假地址// 初始化队列(如果需要)queue->head = 0;queue->tail = 0;queue->isLocked = false;memset(queue->items, 0, sizeof(queue->items));printk(KERN_INFO "SPSC Queue module initialized.\n");return 0;
}static void __exit spsc_queue_exit(void) {kthread_stop(producer_task);kthread_stop(consumer_task);printk(KERN_INFO "SPSC Queue module exited.\n");
}module_init(spsc_queue_init);
module_exit(spsc_queue_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple SPSC queue in kernel space");

总结

  • 用户态代码:创建和映射共享内存,实现生产者和消费者的逻辑。
  • 内核态代码:编写内核模块,访问和操作共享内存中的队列。

这个示例展示了如何在用户态和内核态之间实现一个简单的SPSC队列。实际应用中,你需要根据具体的需求和环境进行进一步的优化和调试。特别注意内存映射和同步机制的实现,以确保系统的稳定性和性能。

Windows实现

在 Windows 下实现一个单生产者单消费者(SPSC)队列,并将其置于跨进程共享内存中,同时在 Ring3 应用程序(生产者)和 Ring0 驱动程序(消费者)之间进行数据传递是一个复杂任务。由于 Ring3 和 Ring0 之间存在隔离,共享内存和同步机制需要通过特定的 Windows 内核机制来实现。

以下是一个示例实现,展示了如何在 Ring3 应用程序(生产者)和 Ring0 驱动程序(消费者)之间进行数据传递。


1. 共享内存和同步机制的设计

我们使用 Windows 的 CreateFileMapping 和 MapViewOfFile 来创建共享内存,并使用内核事件(Kernel Event)来实现同步。

队列结构
#define BUFFER_SIZE 1024typedef struct {char buffer[BUFFER_SIZE];  // 环形缓冲区volatile size_t head;      // 生产者指针volatile size_t tail;      // 消费者指针
} SpscQueue;

同步机制
  • Ring3 侧:使用 Windows API 创建事件对象。
  • Ring0 侧:使用 Windows 内核的 KeInitializeEvent 和 KeSetEvent 来处理内核事件。

2. Ring3 应用程序(生产者)

Ring3 应用程序负责生成数据并将其放入共享内存中。

#include <windows.h>
#include <stdio.h>#define BUFFER_SIZE 1024typedef struct {char buffer[BUFFER_SIZE];volatile size_t head;volatile size_t tail;HANDLE hNotEmpty;  // 通知消费者的内核事件HANDLE hNotFull;   // 通知生产者的内核事件
} SpscQueue;int main() {// 1. 创建共享内存HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,    // 使用系统分页文件NULL,                    // 默认安全性PAGE_READWRITE,          // 读写权限0,                       // 对象大小的高32位sizeof(SpscQueue),       // 对象大小的低32位"Global\\SpscQueueMap"   // 共享内存名称(Global 名称可跨进程访问));if (hMapFile == NULL) {printf("Failed to create file mapping (%d).\n", GetLastError());return 1;}// 2. 映射共享内存到 Ring3 进程地址空间SpscQueue* queue = (SpscQueue*)MapViewOfFile(hMapFile,                // 对象句柄FILE_MAP_ALL_ACCESS,     // 读写权限0,                       // 高32位文件映射对象0,                       // 低32位文件映射对象sizeof(SpscQueue)        // 映射视图的大小);if (queue == NULL) {printf("Failed to map view of file (%d).\n", GetLastError());CloseHandle(hMapFile);return 1;}// 3. 初始化队列queue->head = 0;queue->tail = 0;// 4. 创建事件对象queue->hNotEmpty = CreateEvent(NULL, FALSE, FALSE, "Global\\SpscQueueNotEmpty");queue->hNotFull = CreateEvent(NULL, FALSE, TRUE, "Global\\SpscQueueNotFull");if (queue->hNotEmpty == NULL || queue->hNotFull == NULL) {printf("Failed to create events (%d).\n", GetLastError());UnmapViewOfFile(queue);CloseHandle(hMapFile);return 1;}// 5. 生产数据for (int i = 0; i < 100; ++i) {char item[100];sprintf(item, "Item %d", i);// 等待队列有空位WaitForSingleObject(queue->hNotFull, INFINITE);// 写入数据size_t index = queue->head % BUFFER_SIZE;strcpy(queue->buffer + index, item);queue->head++;// 通知消费者队列不为空SetEvent(queue->hNotEmpty);printf("Produced: %s\n", item);// 模拟生产延迟Sleep(100);}// 6. 清理UnmapViewOfFile(queue);CloseHandle(hMapFile);CloseHandle(queue->hNotEmpty);CloseHandle(queue->hNotFull);return 0;
}


3. Ring0 驱动程序(消费者)

Ring0 驱动程序负责从共享内存中读取数据并处理。

驱动程序代码
#include <ntddk.h>#define BUFFER_SIZE 1024typedef struct {char buffer[BUFFER_SIZE];volatile size_t head;volatile size_t tail;KEVENT notEmpty;  // 通知消费者的内核事件KEVENT notFull;   // 通知生产者的内核事件
} SpscQueue;// 全局共享内存指针
SpscQueue* queue = NULL;void DriverUnload(PDRIVER_OBJECT DriverObject) {UNREFERENCED_PARAMETER(DriverObject);// 取消映射共享内存MmUnmapLockedPages(queue, PsGetProcessSectionBaseAddress(PsGetCurrentProcess()));DbgPrint("Driver unloaded.\n");
}NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {UNREFERENCED_PARAMETER(RegistryPath);DriverObject->DriverUnload = DriverUnload;// 1. 打开共享内存HANDLE hMapFile;OBJECT_ATTRIBUTES objAttrs;UNICODE_STRING mapName;RtlInitUnicodeString(&mapName, L"\\BaseNamedObjects\\Global\\SpscQueueMap");InitializeObjectAttributes(&objAttrs, &mapName, OBJ_CASE_INSENSITIVE, NULL, NULL);NTSTATUS status = ZwOpenFile(&hMapFile,FILE_GENERIC_READ | FILE_GENERIC_WRITE,&objAttrs,NULL,FILE_SHARE_READ | FILE_SHARE_WRITE,FILE_ATTRIBUTE_NORMAL);if (!NT_SUCCESS(status)) {DbgPrint("Failed to open shared memory (%x).\n", status);return status;}// 2. 映射共享内存到 Ring0 地址空间queue = (SpscQueue*)MmMapLockedPagesSpecifyCache(NULL,KernelMode,MmCached,NULL,FALSE,NormalPagePriority);if (queue == NULL) {DbgPrint("Failed to map shared memory.\n");ZwClose(hMapFile);return STATUS_UNSUCCESSFUL;}// 3. 初始化内核事件KeInitializeEvent(&queue->notEmpty, NotificationEvent, FALSE);KeInitializeEvent(&queue->notFull, NotificationEvent, TRUE);// 4. 消费数据for (int i = 0; i < 100; ++i) {// 等待队列不为空KeWaitForSingleObject(&queue->notEmpty, Executive, KernelMode, FALSE, NULL);// 读取数据size_t index = queue->tail % BUFFER_SIZE;char item[100];RtlCopyMemory(item, queue->buffer + index, BUFFER_SIZE);queue->tail++;// 通知生产者队列有空位KeSetEvent(&queue->notFull, 0, FALSE);DbgPrint("Consumed: %s\n", item);// 模拟消费延迟LARGE_INTEGER interval;interval.QuadPart = -10 * 1000 * 1000;  // 100msKeDelayExecutionThread(KernelMode, FALSE, &interval);}return STATUS_SUCCESS;
}


4. 关键点说明

共享内存
  • 使用 CreateFileMapping 和 MapViewOfFile 在 Ring3 侧创建共享内存。
  • 在 Ring0 侧使用 ZwOpenFile 和 MmMapLockedPagesSpecifyCache 映射共享内存。
同步机制
  • Ring3 侧使用 Windows API 创建事件对象。
  • Ring0 侧使用内核事件 KEVENT 和 KeWaitForSingleObject 进行同步。
跨 Ring 访问
  • 共享内存名称使用 Global\\ 前缀,确保跨进程和跨 Ring 访问。
  • Ring0 驱动程序通过 ZwOpenFile 打开共享内存对象。

5. 运行步骤

  1. 编译并加载 Ring0 驱动程序。
  2. 运行 Ring3 生产者应用程序。
  3. 驱动程序将从共享内存中消费数据并输出到调试输出。

6. 注意事项

  1. 权限:确保驱动程序和应用程序具有足够的权限访问共享内存。
  2. 内存对齐:确保共享内存结构对齐正确,避免访问异常。
  3. 调试输出:使用 DbgView 查看驱动程序的 DbgPrint 输出。

通过这种方式,可以实现 Ring3 和 Ring0 之间的数据传递。如果需要反向传递数据(Ring0 生产者,Ring3 消费者),可以调整生产者和消费者的逻辑。

相关文章:

C++并发编程之跨应用程序与驱动程序的单生产者单消费者队列

设计一个单生产者单消费者队列&#xff08;SPSC队列&#xff09;&#xff0c;不使用C STL库或操作系统原子操作函数&#xff0c;并且将其放入跨进程共享内存中以便在Ring3&#xff08;用户模式&#xff09;和Ring0&#xff08;内核模式&#xff09;之间传递数据&#xff0c;是一…...

PostgreSQL技术内幕22:vacuum full 和 vacuum

文章目录 0.简介1.概念及使用方式2.工作原理2.1 主要功能2.2 清理流程2.3 防止事务id环绕说明 3.使用建议 0.简介 在之前介绍MVCC文章中介绍过常见的MVCC实现的两种方式&#xff0c;一种是将旧数据放到回滚段&#xff0c;一种是直接生成一条新数据&#xff08;对于删除是不删除…...

【网络】:网络编程套接字

目录 源IP地址和目的IP地址 源MAC地址和目的MAC地址 源端口号和目的端口号 端口号 VS 进程ID TCP协议和UDP协议 网络字节序 字符串IP和整数IP相互转换 查看当前网络的状态 socket编程接口 socket常见API 创建套接字&#xff08;socket&#xff09; 绑定端口号&…...

java基础概念55-不可变集合

一、定义 不可变集合&#xff1a;不可以被修改的集合&#xff0c;只能查询。&#xff08;长度、内容均不可被修改&#xff09; 二、创建不可变集合 【注意】&#xff1a; 此方法是在JDK9中引入的。 2-1、list不可变集合 示例&#xff1a; import java.util.List;public cla…...

深入理解 C++ 函数重载

在 C 中, 函数重载是一个非常强大的特性, 允许多个函数使用相同的名称, 但具有不同的参数类型. 重载解析决定了在给定的调用中, 编译器应选择哪个版本的重载函数. 本文将深入探讨 C 重载解析的工作原理, 帮助你在实际编程中更好地理解这一机制. 重载(Overload) vs 重写(Overri…...

相机和激光雷达的外参标定 - 无标定板版本

1. 实现的效果 通过本软件实现求解相机和LiDAR的外参&#xff0c;即2个传感器之间的三维平移[x, y, z]和三维旋转[roll, pitch, yaw]。完成标定后&#xff0c;可将点云投影到图像&#xff0c;效果图如下&#xff1a; 本软件的优势&#xff1a;&#xff08;1&#xff09;无需特…...

Redis 知识速览

文章目录 1. Redis 简介2. Redis 优缺点3. Redis 高性能4. Redis VM 机制5. Redis 数据类型6. 应用场景7. 持久化8. 过期策略9. 内存相关10. 线程模型11. 事务12. 集群 1. Redis 简介 定义&#xff1a;Redis 是一个用 C 语言编写的高性能非关系型&#xff08;NoSQL&#xff09…...

LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105_中等_C++)(二叉树;递归)

LeetCode 热题 100_从前序与中序遍历序列构造二叉树&#xff08;47_105&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;递归&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;递归…...

使用sqlplus的easy connect时如何指定是链接到shared server还是dedicated process

在oracle配置了shared server的情况下 可以使用 :shared来指定链接到shared server也可以默认不指定 不指定的情况下会默认链接到shared server 如果想链接到 dedicated process 则必须显式指定链接到dedicated process server type的类型包括DEDICATED, SHARED, or POOLED. […...

ubuntu22.4 ROS2 安装gazebo(环境变量配置)

ubuntu版本&#xff1a;ubuntu22.4 最近在学习ROS2 视频教程古月居的入门课&#xff1a; 视频教程 文字笔记 问题 在学到关于Gazebo的时候&#xff0c;遇到下面问题&#xff1a; 运行 $ ros2 launch gazebo_ros gazebo.launch.py在这里卡住&#xff0c;不弹出gazebo 解决…...

【机器学习:十四、TensorFlow与PyTorch的对比分析】

1. 发展背景与社区支持 1.1 TensorFlow的背景与发展 TensorFlow是Google于2015年发布的开源深度学习框架&#xff0c;基于其前身DistBelief系统。作为Google大规模深度学习研究成果的延续&#xff0c;TensorFlow从一开始就定位为生产级框架&#xff0c;强调跨平台部署能力和性…...

[C++]类与对象(上)

目录 &#x1f495;1.C中结构体的优化 &#x1f495;2.类的定义 &#x1f495;3.类与结构体的不同点 &#x1f495;4.访问限定符(public,private,protected) &#x1f495;5.类域 &#x1f495;6.类的实例化 &#x1f495;7.类的字节大小 &#x1f495;8.类的字节大小特例…...

大数据技术实训:Zookeeper集群配置

一、本地模式安装部署 1&#xff09;安装前准备 &#xff08;1&#xff09;安装jdk &#xff08;2&#xff09;拷贝Zookeeper安装包到Linux系统下 &#xff08;3&#xff09;解压到指定目录 tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/ 2&#xff09;配置修改 &am…...

HTML5 加载动画(Loading Animation)

加载动画&#xff08;Loading Animation&#xff09;详解 概述 加载动画是指在数据加载过程中&#xff0c;向用户展示的一种视觉效果&#xff0c;旨在提升用户体验&#xff0c;告知用户系统正在处理请求。它可以减少用户的等待焦虑感&#xff0c;提高界面的互动性。 常见的加…...

C语言进阶-2指针(一)

目录 1. 字符指针1.1 一般用法&#xff1a;字符指针指向单字符1.2 第二种用法&#xff0c;字符串首地址给指针变量1.3 习题,下面代码的输出结果是什么&#xff1f;为什么&#xff1f; 2. 指针数组2.1实例—— 字符指针数组2.2实例——整形指针数组2.3 例子&#xff0c;识别下下…...

【人工智能】用Python进行对象检测:从OpenCV到YOLO的全面指南

对象检测是计算机视觉领域的核心任务之一&#xff0c;广泛应用于视频监控、自动驾驶、智能安防等多个场景。随着深度学习技术的发展&#xff0c;基于传统方法的对象检测逐渐被基于神经网络的先进模型所取代。本文将系统地介绍如何使用Python进行对象检测&#xff0c;重点探讨了…...

《深度剖析算法优化:提升效率与精度的秘诀》

想象一下&#xff0c;你面前有一堆杂乱无章的数据&#xff0c;你需要从中找到特定的信息&#xff0c;或者按照一定的规则对这些数据进行排序。又或者&#xff0c;你要为一个物流公司规划最佳的配送路线&#xff0c;以降低成本和提高效率。这些问题看似复杂&#xff0c;但都可以…...

Mysql--重点篇--索引(索引分类,Hash和B-tree索引,聚簇和非聚簇索引,回表查询,覆盖索引,索引工作原理,索引失效,索引创建原则等)

索引是数据库中用于加速查询操作的重要机制。通过索引&#xff0c;MySQL可以快速定位到满足查询条件的数据行&#xff0c;而不需要扫描整个表。合理的索引设计可以显著提高查询性能&#xff0c;但不合理的索引可能会导致性能下降和磁盘空间浪费。因此&#xff0c;理解索引的工作…...

matlab使用 BP 神经网络进行数据预测的完整流程,包括数据读取、数据预处理等等

%% 初始化程序 warning off % 关闭报警信息 close all % 关闭所有图窗 clear % 清空变量 clc % 清空命令行 setdemorandstream(172) %设置随机种子为1%% 读取数据 data xlsread(Y.xlsx); %% 划分训练集…...

systemd-networkd NetworkManager 介绍

systemd-networkd 和 NetworkManager 的详细介绍 systemd-networkd 和 NetworkManager 都是 Linux 系统中常用的网络管理工具&#xff0c;但它们的设计目标和使用场景不同。以下是它们的详细介绍、功能、使用场景和差异。 1. systemd-networkd systemd-networkd 是一个由 syst…...

基于springboot的校园社团信息系统的设计与实现

其他源码获取可以看首页&#xff1a;代码老y 个人简介&#xff1a;专注于毕业设计项目定制开发&#xff1a;springbootvue系统&#xff0c;Java微信小程序&#xff0c;javaSSM系统等技术开发&#xff0c;并提供远程调试部署、代码讲解、文档指导、ppt制作等技术指导。源码获取&…...

创客匠人:以 AI 利器赋能创始人 IP 打造,加速知识变现新路径

在知识付费与个人 IP 崛起的时代&#xff0c;创客匠人作为行业领先的技术服务商&#xff0c;正通过 AI 工具重构创始人 IP 打造与知识变现的生态。其推出的三大 AI 利器 ——AI 销售信、免训数字人、AI 智能客服&#xff0c;精准解决 IP 运营中的核心痛点。 以 AI 销售信为例&…...

Python使用总结之Mac安装docker并配置wechaty

Python使用总结之Mac安装docker并配置wechaty ✅ 一、安装 Docker Desktop for macOS 1. 下载 Docker Desktop 安装包 访问官网下载安装包&#xff1a; &#x1f449; https://www.docker.com/products/docker-desktop 选择 macOS (Apple 芯片或 Intel 芯片) 版本下载。 …...

Lua和JS的垃圾回收机制

Lua 和 JavaScript 都采用了 自动垃圾回收机制&#xff08;GC&#xff09; 来管理内存&#xff0c;开发者无需手动释放内存&#xff0c;但它们的 实现机制和行为策略不同。下面我们从原理、策略、优缺点等方面来详细对比&#xff1a; &#x1f536; 1. 基本原理对比 特性LuaJa…...

智慧货运飞船多维度可视化管控系统

图扑搭建智慧货运飞船可视化系统&#xff0c;借数字孪生技术&#xff0c;高精度复刻货运飞船外观、结构与运行场景。整合多维度数据&#xff0c;实时呈现飞行状态、设备参数等信息&#xff0c;助力直观洞察货运飞船运行逻辑&#xff0c;为航天运维、任务推演及决策提供数字化支…...

在Docker里面运行Docker

Docker 凭借其轻量级和可移植的容器,无疑改变了软件开发和部署的世界。但如果我告诉你 Docker 本身可以在另一个 Docker 容器中运行,你会怎么想?没错!这个概念通常被称为“Docker Inside Docker”或“DinD”,它为开发人员和系统管理员开辟了一个全新的可能性领域。在这篇博…...

Towards Open World Object Detection概述(论文)

论文&#xff1a;https://arxiv.org/abs/2103.02603 代码&#xff1a;https://github.com/JosephKJ/OWOD Towards Open World Object Detection 迈向开放世界目标检测 Abstract 摘要 Humans have a natural instinct to identify unknown object instances in their environ…...

Linux常用命令学习手册

Linux常用命令学习手册https://download.csdn.net/download/2401_87690752/90953550 《Linux常用命令学习手册》提供了一份实用的Linux操作指南&#xff0c;主要收录了系统管理和文件操作等基础命令。内容涵盖了目录切换、文件查看、权限设置等核心功能&#xff0c;适合Linux初…...

Docker构建自定义的镜像

构建自定义的 Docker 镜像是 Docker 使用中的核心操作之一。通过自定义镜像&#xff0c;你可以将应用程序及其依赖环境打包成一个可移植的容器化镜像。以下是详细的步骤和注意事项&#xff1a; 1. 准备工作 在构建自定义镜像之前&#xff0c;你需要准备以下内容&#xff1a; D…...

Spring WebFlux 整合AI大模型实现流式输出

前言 最近赶上AI的热潮&#xff0c;很多业务都在接入AI大模型相关的接口去方便的实现一些功能&#xff0c;后端需要做的是接入AI模型接口&#xff0c;并整合成流式输出到前端&#xff0c;下面有一些经验和踩过的坑。 集成 Spring WebFlux是全新的Reactive Web技术栈&#xf…...