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

Linux驱动开发 块设备

目录

序言

1.块设备结构

分区(gendisk)

请求(request)

请求队列

1. 多队列架构

2. 默认限制与扩展

bio

2.块设备的使用

头文件与宏定义

blk-mq 相关结构和操作

块设备操作函数

 模块初始化函数

 模块退出函数

3.总结


序言

块设备(如硬盘、虚拟盘)以固定大小的块(扇区)进行读写。块设备驱动的主要任务就是响应来自文件系统的 I/O 请求,并将数据正确地读写到设备对应的存储区域。

为了在多核系统中提高并发性能,最新内核采用了blk-mq(Block Multi-Queue)接口,它使用多个硬件队列来分发和处理 I/O 请求。每个请求包含数据传输的信息(如起始扇区、数据长度等),本篇文章将使用blk-mq而不再讲述单队列模式,另外如果有兴趣深入学习块设备和多队列模式可以阅读此链接。

1.块设备结构

分区(gendisk)

gendisk 是内核中描述一个块设备的结构体,可以理解为分区,它保存了设备的主设备号、次设备号、设备名称、容量、操作函数等信息。当驱动调用 add_disk() 后,系统会将设备显示在 /dev 下。

块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备 无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇区也很常见, 比如,很多CD-ROM盘的扇区都是2K大小。不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512字节为单位。因此,set_capacity()函数也以512字节为单位。

请求(request)

request是描述I/O请求,包含数据传输的信息(如起始扇区、数据长度等)还有bio结构体。

下面只列出部分request结构体中的参数,具体的请查阅源文件或资料。

  • 扇区参数
   sector_t hard_sector; unsigned long hard_nr_sectors; unsigned int hard_cur_sectors;

上述 3 个成员标识还未完成的扇区,hard_sector 是第一个尚未传输的扇区,hard_nr_sectors 是尚待完成的扇区数,hard_cur_sectors 是当前 I/O 操作中待完成的扇区数。这些成员只用于内核块设备层,驱动不应当使用它们。
  在驱动程序中一般使用的是:

sector_t sector; 
unsigned long nr_sectors; 
unsigned int current_nr_sectors;


  这 3 个成员在内核和驱动交互中发挥着重大作用。它们以 512 字节大小为一个扇区,如果硬件的扇区大小不是 512 字节,则需要进行相应的调整。例如,如果硬件的扇区大小是 2048 字节,则在进行硬件操作之前,需要用 4 来除起始扇区号。
  注意:hard_sector 、 hard_nr_sectors 、 hard_cur_sectors 与 sector 、 nr_sectors 、
current_nr_sectors 之间可认为是“副本”关系。

  • struct bio *bio bio是这个请求中包含的 bio 结构体的链表,驱动中不宜直接存取这个成员,而应该使用后文将介绍的rq_for_each_bio()。
  • char *buffer 指向缓冲区的指针,数据应当被传送到或者来自这个缓冲区,这个指针是一个内核虚拟地址,可被驱动直接引用。

使用如下宏可以从 request 获得数据传送的方向

rq_data_dir(struct request *req);
0 返回值表示从设备中读,非 0 返回值表示向设备写。

请求队列

当外部设备或用户程序访问块设备时,会发起I/O请求,而我们的块设备有一个请求队列,我们这里是最新的blk-mq队列,其会为每一个CPU都分配一组软件队列和硬件队列,每个队列可以支持0-1023个I/O请求

1. 多队列架构
  • 软件队列(Software Queues):每个 CPU 核心分配一个队列,减少锁竞争。

  • 硬件派发队列(Hardware Dispatch Queues):根据设备硬件队列数量分配,映射到实际硬件通道。

  • 标签集(Tag Set):管理请求标签,实现请求与硬件的解耦。

2. 默认限制与扩展
  • 队列深度(Queue Depth):默认 1024,但需根据硬件能力调整。

  • 硬件队列数量:建议与 CPU 核心数或硬件通道数对齐。

struct bio { sector_t bi_sector;           /* 标识这个 bio 要传送的第一个(512 字节)扇区。 */ struct bio *bi_next;                           /* 下一个 bio */ struct block_device *bi_bdev; unsigned long bi_flags;       /* 一组描述 bio 的标志,如果这是一个写请求,最低有效位被置位,可以使用bio_data_dir(bio)宏来获得读写方向。 */ unsigned long bi_rw;             /* 低位表示 READ/WRITE,高位表示优先级*/  unsigned short bi_vcnt;           /* bio_vec 数量 */ unsigned short bi_idx;            /* 当前 bvl_vec 索引 */  /*不相邻的物理段的数目*/ unsigned short bi_phys_segments;  /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/ unsigned short bi_hw_segments;  unsigned int bi_size;       /* 以字节为单位所需传输的数据大小,驱动中可以使用bio_sectors(bio)宏获得以扇区为单位的大小。 */ /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个 虚拟的可合并的段的尺寸 */ unsigned int bi_hw_front_size; unsigned int bi_hw_back_size;  unsigned int bi_max_vecs;               /* 我们能持有的最大 bvl_vecs 数 */  struct bio_vec *bi_io_vec;               /* bio_vec 结构体,bio 的核心*/ bio_end_io_t *bi_end_io; atomic_t bi_cnt; void *bi_private;  bio_destructor_t *bi_destructor; }; 

具体如何配置后面会讲到。

bio

I/O 请求的数据通常以 bio(block I/O)表示,一个请求中可能包含多个bio,而 bio中的每个数据段用 bio_vec 表示。一个 bio_vec 指向一段连续的内存页数据,在数据传输过程中我们需要将这些页映射到内核地址空间进行访问(通过 kmapkunmap

bio结构体:

struct bio { sector_t bi_sector;           /* 标识这个 bio 要传送的第一个(512 字节)扇区。 */ struct bio *bi_next;                           /* 下一个 bio */ struct block_device *bi_bdev; unsigned long bi_flags;       /* 一组描述 bio 的标志,如果这是一个写请求,最低有效位被置位,可以使用bio_data_dir(bio)宏来获得读写方向。 */ unsigned long bi_rw;             /* 低位表示 READ/WRITE,高位表示优先级*/  unsigned short bi_vcnt;           /* bio_vec 数量 */ unsigned short bi_idx;            /* 当前 bvl_vec 索引 */  /*不相邻的物理段的数目*/ unsigned short bi_phys_segments;  /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/ unsigned short bi_hw_segments;  unsigned int bi_size;       /* 以字节为单位所需传输的数据大小,驱动中可以使用bio_sectors(bio)宏获得以扇区为单位的大小。 */ /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个 虚拟的可合并的段的尺寸 */ unsigned int bi_hw_front_size; unsigned int bi_hw_back_size;  unsigned int bi_max_vecs;               /* 我们能持有的最大 bvl_vecs 数 */  struct bio_vec *bi_io_vec;               /* bio_vec 结构体,bio 的核心*/ bio_end_io_t *bi_end_io; atomic_t bi_cnt; void *bi_private;  bio_destructor_t *bi_destructor; }; 

bio_vec结构体: 

struct bio_vec { struct page *bv_page;                   /* 页指针 */ unsigned int bv_len;                     /* 传输的字节数 */ unsigned int bv_offset;                   /* 偏移位置 */ }; 

2.块设备的使用

头文件与宏定义

包含内核模块、块设备、内存管理等所需的头文件,并定义设备名称、扇区大小(通常为 512 字节)和设备总大小(例如 16MB)。并且定义了一个自定义数据结构(例如 struct mydisk_device),用来保存设备的存储数据指针和设备大小。全局变量 device 保存了设备的实例。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>
#include <linux/spinlock.h>
#include <linux/blk-mq.h>
#include <linux/bio.h>
#include <linux/highmem.h>   // 用于 kmap/kunmap#define DEVICE_NAME         "myblk"                // 设备名称
#define KERNEL_SECTOR_SIZE  512                    // 内核扇区大小,固定为 512 字节
#define MYDISK_SIZE         (16 * 1024 * 1024)     // 设备大小:16MB 分配内存基本单位为bytestruct mydisk_device {unsigned char *data;   // 存放设备数据的内存区size_t size;           // 设备的总大小(字节)
};static struct mydisk_device *device = NULL;

自定义结构用于保存设备的内存数据区域和大小。在初始化阶段,我们会分配一块内存作为设备的“存储区”。

blk-mq 相关结构和操作

static struct blk_mq_tag_set tag_set;
static struct request_queue *queue = NULL;

tag_set:配置多队列(blk-mq)的参数,如硬件队列数、队列深度、NUMA 亲和性等。

queue:所有 I/O 请求都会被加入到这个请求队列中,blk-mq 会调用我们定义的处理函数来处理这些请求。

blk-mq 请求处理函数

这是驱动核心部分,用于处理每个 I/O 请求。函数中会根据请求的起始扇区和请求长度计算出设备内存的偏移,然后遍历请求中的所有 bio_vec 数据段,根据请求方向(读或写)完成数据拷贝,最后调用 blk_mq_end_request() 通知系统该请求处理完毕

static blk_status_t myblk_mq_fn(struct blk_mq_hw_ctx *hctx,const struct blk_mq_queue_data *bd)
{struct request *req = bd->rq;blk_status_t status = BLK_STS_OK;unsigned long offset;unsigned int total_len;struct req_iterator iter;struct bio_vec bvec;unsigned int copied = 0;/* 根据请求起始扇区计算设备内存偏移 */offset = blk_rq_pos(req) * KERNEL_SECTOR_SIZE;total_len = blk_rq_bytes(req);/* 检查请求是否超出设备范围 */if (offset + total_len > device->size) {printk(KERN_NOTICE "myblk: 请求超出设备范围: offset %lu, len %u\n", offset, total_len);status = BLK_STS_IOERR;goto done;}/* 遍历请求中的每个 bio_vec 数据段 */rq_for_each_segment(bvec, req, iter) {/* 将 bio_vec 中的页映射到内核地址空间 */char *buffer = kmap(bvec.bv_page) + bvec.bv_offset;unsigned int len = bvec.bv_len;if (copied + len > total_len)len = total_len - copied;/* 根据请求类型进行数据拷贝:* - 读请求:将设备数据复制到用户请求缓冲区* - 写请求:将用户数据写入设备内存*/if (rq_data_dir(req) == READ)memcpy(buffer, device->data + offset + copied, len);elsememcpy(device->data + offset + copied, buffer, len);copied += len;kunmap(bvec.bv_page);}done:/* 通知 blk-mq 请求处理完毕 */blk_mq_end_request(req, status);return status;
}
  • 请求参数解析

    • blk_rq_pos(req) 返回请求的起始扇区,乘以 512 得到字节偏移。

    • blk_rq_bytes(req) 返回请求需要传输的总字节数。

  • 边界检查
    检查请求的数据范围是否超过了设备分配的内存。如果超出,则返回错误状态。

  • 遍历 bio_vec 数据段
    使用 rq_for_each_segment() 遍历请求中每个数据段,每个数据段都对应一块内存页。

    • 通过 kmap 将页映射到内核虚拟地址空间,进行数据拷贝。

    • 根据请求方向(读或写)选择合适的 memcpy 操作。

    • 使用 kunmap 解除映射。

  • 结束请求
    调用 blk_mq_end_request() 告诉内核该请求已经完成,状态(成功或错误)作为参数传递。

块设备操作函数

static int myblk_open(struct block_device *bdev, fmode_t mode)
{printk(KERN_INFO "myblk: 设备打开\n");return 0;
}static void myblk_release(struct gendisk *disk, fmode_t mode)
{printk(KERN_INFO "myblk: 设备关闭\n");
}static int myblk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{geo->heads     = 4;geo->sectors   = 32;geo->cylinders = (MYDISK_SIZE) / (4 * 32 * KERNEL_SECTOR_SIZE);geo->start     = 0;return 0;
}
  • open 与 release
    当用户程序通过 /dev/myblk 打开或关闭设备时,这两个函数会被调用。这里仅打印日志,实际应用中可能需要对设备进行状态维护或加锁操作。

  • getgeo
    该函数用于返回设备的几何信息(柱面、磁头、扇区数),部分老旧应用可能依赖这些信息,但对于虚拟设备来说,这个值通常只作兼容性返回。

定义块设备操作结构体:

static struct block_device_operations myblk_fops = {.owner  = THIS_MODULE,.open   = myblk_open,.release= myblk_release,.getgeo = myblk_getgeo,
};

 将前面定义的设备操作函数绑定到块设备操作结构体中,供系统调用。

 模块初始化函数

static int __init myblk_init(void)
{int ret;printk(KERN_INFO "myblk: 模块初始化\n");/* 分配设备结构 */device = kmalloc(sizeof(*device), GFP_KERNEL);if (!device) {ret = -ENOMEM;goto out;}device->size = MYDISK_SIZE;/* 分配设备存储内存 */device->data = vmalloc(device->size);if (!device->data) {ret = -ENOMEM;goto free_device;}/* 初始化 blk-mq tag_set */memset(&tag_set, 0, sizeof(tag_set));tag_set.ops = &mq_ops;             // 指定请求处理函数所在的操作结构tag_set.nr_hw_queues = 1;          // 设置硬件队列数量(这里只使用一个队列)tag_set.queue_depth = 128;         // 队列深度,根据实际需求调整tag_set.numa_node = NUMA_NO_NODE;tag_set.cmd_size = 0;ret = blk_mq_alloc_tag_set(&tag_set);if (ret)goto free_data;/* 初始化请求队列,采用 blk-mq 接口 */queue = blk_mq_init_queue(&tag_set);if (IS_ERR(queue)) {ret = PTR_ERR(queue);goto free_tag_set;}queue->queuedata = device;/* 注册块设备,动态分配主设备号 */major_num = register_blkdev(0, DEVICE_NAME);if (major_num <= 0) {ret = -EBUSY;goto cleanup_queue;}/* 分配并初始化 gendisk 结构 */mydisk = alloc_disk(1);  // 分区数量设为 1if (!mydisk) {ret = -ENOMEM;goto unregister_blk;}mydisk->major = major_num;mydisk->first_minor = 0;mydisk->fops = &myblk_fops;mydisk->private_data = device;snprintf(mydisk->disk_name, 32, DEVICE_NAME);set_capacity(mydisk, device->size / KERNEL_SECTOR_SIZE);mydisk->queue = queue;/* 将设备添加到系统中,使其在 /dev 下可见 */add_disk(mydisk);printk(KERN_INFO "myblk: 模块加载成功\n");return 0;unregister_blk:unregister_blkdev(major_num, DEVICE_NAME);
cleanup_queue:blk_cleanup_queue(queue);
free_tag_set:blk_mq_free_tag_set(&tag_set);
free_data:vfree(device->data);
free_device:kfree(device);
out:return ret;
}
  • 设备结构与内存分配
    使用 kmalloc 分配保存设备信息的结构,再用 vmalloc 分配一块连续的虚拟内存作为存储空间。

  • 初始化 blk-mq
    通过设置 tag_set 的各项参数(例如硬件队列数和队列深度),并调用 blk_mq_alloc_tag_setblk_mq_init_queue 来建立基于 blk-mq 的请求队列

  • 设备注册与 gendisk 初始化

    • 使用 register_blkdev() 动态获取一个主设备号;

    • 分配并设置 gendisk 结构,包括设备号、操作函数、设备容量(通过 set_capacity 将字节数转成扇区数)和请求队列;

    • 最后调用 add_disk() 注册设备,使其在系统中可见(如 /dev/myblk),这里填写的数字为分区数字,用来划分不同的区域。

 模块退出函数

static void __exit myblk_exit(void)
{del_gendisk(mydisk);put_disk(mydisk);unregister_blkdev(major_num, DEVICE_NAME);blk_cleanup_queue(queue);blk_mq_free_tag_set(&tag_set);vfree(device->data);kfree(device);printk(KERN_INFO "myblk: 模块卸载\n");
}

清理顺序
模块卸载时要反向释放在初始化时分配的所有资源:

  • 先通过 del_gendiskput_disk 移除并释放 gendisk 结构;

  • 注销块设备主设备号;

  • 清理请求队列和释放 blk-mq 的 tag_set;

  • 最后释放分配的内存区域。

3.总结

通过对块设备的深入研究,我们对其工作原理、数据传输方式以及在多核系统中的并发性能有了更清晰的认识。​块设备以固定大小的扇区为单位进行数据读写,块设备驱动程序负责响应文件系统的I/O请求,将数据准确地读写到设备的存储区域。​在多核系统中,采用blk-mq(Block Multi-Queue)接口可以提高并发性能,利用多个硬件队列来分发和处理I/O请求。​

在块设备的实现过程中,gendisk结构体用于描述设备的基本信息,如主次设备号、设备名称和容量等。​request结构体则描述具体的I/O请求,包含数据传输的起始扇区、数据长度等信息。​bio(block I/O)结构体用于表示I/O请求的数据,可能包含多个bio_vec,每个bio_vec指向一段连续的内存数据。​

最后再聊聊块设备,其实我自己也迷糊了一会,块设备,到底是干嘛的?

 硬件的块设备就是SSD,HHD这类数据存储设备

软件的块设备其实就是我们的虚拟磁盘,当操作系统操作数据需要与块设备进行I/O操作,而块设备负责管理这些数据,它将数据划分成大小相等的扇区(例如每个扇区为 512B),每个都有对应的标识符,当操作系统或者用户程序需要访问时,就需要通过块设备去进行存取(就是这么简单,可惜之前傻傻分不清还以为是I/O通道,又一阵子以为是u盘这类存储设备)总的来说就是可以读写磁盘的一个设备。

相关文章:

Linux驱动开发 块设备

目录 序言 1.块设备结构 分区(gendisk) 请求(request) 请求队列 1. 多队列架构 2. 默认限制与扩展 bio 2.块设备的使用 头文件与宏定义 blk-mq 相关结构和操作 块设备操作函数 模块初始化函数 模块退出函数 3.总结 序言 块设备&#xff08;如硬盘、虚拟盘&#x…...

简易Minecraft python

废话多说 以下是一个基于Python和ModernGL的简化版3D沙盒游戏框架。由于代码长度限制&#xff0c;这里提供一个核心实现&#xff08;约500行&#xff09;&#xff0c;您可以通过添加更多功能和内容来扩展它&#xff1a; python import pygame import moderngl import numpy a…...

Bethune X 6发布:为小规模数据库环境打造轻量化智能监控巡检利器

3月31日&#xff0c;“奇点时刻・数智跃迁 -- 云和恩墨2025春季产品发布会”通过视频号直播的方式在线上举办。发布会上&#xff0c;云和恩墨副总经理熊军正式发布 zCloud 6.7和zData X 3.3两款产品新版本&#xff0c;同时也带来了 Bethune X 6——这款面向小规模数据库环境的智…...

OpenCV 图形API(15)计算两个矩阵(通常代表二维向量的X和Y分量)每个对应元素之间的相位角(即角度)函数phase()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 描述 cv::gapi::phase 是 OpenCV 的 G-API 模块中的一个函数&#xff0c;用于计算两个矩阵&#xff08;通常代表二维向量的X和Y分量&#xff09;每个对应元…...

一文理解什么是中值模糊

目录 中值模糊的概念 中值模糊&#xff08;Median Blur&#xff09; 中值模糊的原理 示例&#xff1a;33 中值模糊 什么是椒盐噪声 椒盐噪声&#xff08;Salt-and-Pepper Noise&#xff09; 椒盐噪声的特点 OpenCV 中的 cv2.medianBlur() 函数格式 示例代码 中值模糊…...

游戏引擎学习第192天

仓库:https://gitee.com/mrxiao_com/2d_game_4 回顾 我们现在正在编写一些界面代码&#xff0c;主要是用户界面&#xff08;UI&#xff09;&#xff0c;不过这里的UI并不是游戏内的用户界面&#xff0c;而是为开发者设计的用户界面。我们正在尝试做一些小的UI元素&#xff0c…...

通信数据记录仪-产品概念ID

总结: 1、支持高速CAN、支持容错CAN、支持单线CAN(理解是支持不同的协议,CANFD、CAN2.0和LIN?) 2、 通过上位机设计时间...

Mac VM 卸载 win10 安装win7系统

卸载 找到相应直接删除&#xff08;移动到废纸篓&#xff09; 可参考&#xff1a;mac如何卸载虚拟机win 下载 win7下载地址...

基于图扑 HT 技术的电缆厂 3D 可视化管控系统深度解析

在当今数字化浪潮席卷制造业的大背景下&#xff0c;图扑软件&#xff08;Hightopo&#xff09;凭借其自主研发的强大技术&#xff0c;为电缆厂打造了一套先进的 3D 可视化管控系统。该系统基于 HT for Web 技术&#xff0c;为电缆厂的数字化转型提供了有力支撑。 HT 技术核心架…...

《AI大模型开发笔记》MCP快速入门实战(一)

目录 1. MCP入门介绍 2. Function calling技术回顾 3. 大模型Agent开发技术体系回顾 二、 MCP客户端Client开发流程 1. uv工具入门使用指南 1.1 uv入门介绍 1.2 uv安装流程 1.3 uv的基本用法介绍 2.MCP极简客户端搭建流程 2.1 创建 MCP 客户端项目 2.2 创建MCP客户端…...

C++多态:从青铜九鼎到虚函数表的千年演化密码

一、青铜礼器中的多态启示——九鼎的形与神 在故宫博物院深处&#xff0c;九尊青铜鼎静静矗立。这些跨越三千年的礼器&#xff0c;表面斑驳的铜锈下隐藏着惊人的铸造工艺&#xff1a;鼎足采用分铸法预制&#xff0c;器身主体采用浑铸法一次成型&#xff0c;纹饰运用浮雕与阴刻…...

【愚公系列】《高效使用DeepSeek》051-产品创新研发

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...

常见的ETL工具分类整理

一、开源ETL工具 ‌Kettle&#xff08;Pentaho Data Integration&#xff09;--Spoon‌ 设计及架构&#xff1a;面向数据仓库建模的传统ETL工具。使用方式&#xff1a;C/S客户端模式&#xff0c;开发和生产环境需要独立部署&#xff0c;任务编写、调试、修改都在本地。底层架构…...

Smart Link 技术全面解析

1.1 网络冗余技术的演进与需求 1.2 Smart Link 的核心价值与本文目标 第一章 Smart Link 技术概述 2.1 Smart Link 的应用场景与背景 2.2 Smart Link 的基本概念与组网角色 2.3 Smart Link 与传统技术的对比 第二章 Smart Link 工作原理 3.1 Smart Link 组的构成与运行机…...

React面试常考内容【从宏观到微观】

以下是React面试常考内容的系统梳理,从宏观设计思想到微观实现细节,涵盖高频考点及底层原理: 一、宏观层面:React设计哲学与架构 核心设计理念 • 声明式编程:通过描述UI的最终状态而非操作步骤实现高效开发(如JSX声明结构) • 组件化思想:高内聚低耦合的组件构建模式,…...

栈回溯和离线断点

栈回溯和离线断点 栈回溯&#xff08;Stack Backtrace&#xff09; 栈回溯是一种重建函数调用链的技术&#xff0c;对于分析栈溢出的根本原因非常有价值。 实现方式 // 简单的栈回溯实现示例&#xff08;ARM Cortex-M架构&#xff09; void stack_backtrace(void) {uint32_…...

Roo Code(前身为 Roo Cline)一个 AI 驱动的自主编码代理

Roo Code&#xff08;前身为 Roo Cline&#xff09; Roo Code 是一个 AI 驱动的自主编码代理&#xff0c;它存在于您的编辑器中。它可以&#xff1a; 用自然语言沟通直接在您的工作区读写文件运行终端命令自动化浏览器操作与任何 OpenAI 兼容或自定义的 API/模型集成通过自定…...

数字化三维实训室:无穿戴动作捕捉技术如何赋能体育与舞蹈

在高校体育与舞蹈教学中&#xff0c;精准的动作训练至关重要。传统训练方式依赖教练的肉眼观察与手动记录&#xff0c;存在效率低下、误差较大的情况。尤其在快速连续动作或复杂肢体形态的捕捉中&#xff0c;人工判读易受主观经验限制&#xff0c;难以实现标准化评估。面对传统…...

Linux make与makefile 项目自动化构建工具

本文章将对make与makefile进行一些基础的讲解。 假设我们要建造一座房子&#xff0c;建造过程涉及很多步骤&#xff0c;比如打地基、砌墙、安装门窗、粉刷墙壁等。每个步骤都有先后顺序&#xff0c;并且有些步骤可能依赖于其他步骤的完成。比如&#xff0c;你必须先打好地基才…...

leetcode51-N皇后

leetcode 51 思路 本题可以使用回溯算法来解决。回溯算法通过尝试所有可能的解决方案来找到问题的解的算法&#xff0c;当发现当前的选择无法得到有效的解决方案时&#xff0c;就回溯到上一步&#xff0c;尝试其他的选择。对于 N 皇后问题&#xff0c;我们可以逐行放置皇后&…...

linux 命令 awk

awk 是 Linux/Unix 系统中一个强大的文本处理工具&#xff0c;尤其擅长处理结构化文本数据&#xff08;如日志、表格数据&#xff09;。它不仅是命令行工具&#xff0c;还是一种脚本语言&#xff0c;支持变量、条件、循环等编程特性 1. 基本语法 awk [选项] 模式 {动作} 文件名…...

接口自动化学习四:全量字段校验

什么是全量字段校验&#xff1a; 校验接口返回响应结果的全部字段&#xff08;更进一步的断言&#xff09;。 校验内容&#xff1a; 1.字段值 2.字段名或者字段类型 校验流程&#xff1a; 1.定义 校验规则&#xff08;json语法&#xff0c;只能针对json对象校验&#xff0c;如…...

R语言——获取数据1

参考资料&#xff1a;学习R 数据的来源可以由很多。R内置有许多数据集&#xff0c;而在其他的附件包中能找到更多的数据。R能从各式各样的来源中读取&#xff0c;且支持大量的文件格式。 1、内置的数据集 R的基本分发包有一个datasets&#xff0c;里面全是示例数据集。很多其他…...

自编码器(AutoEncoder)概念解析与用法实例:压缩数字图像

目录 1. 前言 2. 自编码器的基本概念 2.1 自编码器的结构 2.2 损失函数 3. 使用 PyTorch 构建自编码器&#xff1a;压缩数字图像 3.1 导入必要的库 3.2 定义自编码器模型 3.3 准备数据集 3.4 训练模型 3.5 可视化重建结果 3.6 完整代码 4. 自编码器的应用场景 5. …...

从零开始打造HTML5拼图游戏:一个Canvas实战项目

从零开始打造HTML5拼图游戏&#xff1a;一个Canvas实战项目 先看效果&#xff1a; 你是否曾经被那些精美的网页拼图游戏所吸引&#xff1f;用 HTML5 的 Canvas 技术&#xff0c;从零开始&#xff0c;教你怎么画图、处理鼠标事件&#xff0c;还有游戏的核心逻辑&#xff0c…...

每日一题洛谷P8649 [蓝桥杯 2017 省 B] k 倍区间c++

P8649 [蓝桥杯 2017 省 B] k 倍区间 - 洛谷 (luogu.com.cn) #include <iostream> #include <vector> using namespace std; #define int long long signed main() {int n, k;cin >> n >> k;vector<int> a(n 1);vector<int> sum(n 1);vec…...

在uniapp中,video比普通的标签层级高解决问题

<view style"position: relative;"><video style"position: absolute;z-index:-1"></video><view style"position: absolute;z-index:999"></view> </view> 上面代码并没有解决view的层级比video高的问题&…...

《探索边缘计算:重塑未来智能物联网的关键技术》

最近研学过程中发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击链接跳转到网站人工智能及编程语言学习教程。读者们可以通过里面的文章详细了解一下人工智能及其编程等教程和学习方法。下面开始对正文内容的…...

Linux(十二)信号

今天我们就要来一起学习信号啦&#xff01;&#xff01;&#xff01;还记得小编在之前的文章中说过的ctrlc吗&#xff1f;之前小编没有详细介绍过&#xff0c;现在我们就要来学习啦&#xff01;&#xff01;&#xff01; 一、信号的基本介绍 首先&#xff0c;小编带领大家先一…...

LeetCode算法题(Go语言实现)_30

题目 给定单链表的头节点 head &#xff0c;将所有索引为奇数的节点和索引为偶数的节点分别组合在一起&#xff0c;然后返回重新排序的列表。 第一个节点的索引被认为是 奇数 &#xff0c; 第二个节点的索引为 偶数 &#xff0c;以此类推。 请注意&#xff0c;偶数组和奇数组内…...